diff --git a/package.json b/package.json index a5606a85..d5e32fb0 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,6 @@ "eslint": "^7.16.0", "prettier": "^2.2.1" }, - "version": "0.6.0-rc6", + "version": "0.6.0-rc7", "author": "hupeng " } diff --git a/packages/api-server/package.json b/packages/api-server/package.json index b09dc46c..63637252 100644 --- a/packages/api-server/package.json +++ b/packages/api-server/package.json @@ -1,6 +1,6 @@ { "name": "@godwoken-web3/api-server", - "version": "0.6.0-rc6", + "version": "0.6.0-rc7", "private": true, "scripts": { "start": "concurrently \"tsc -w\" \"DEBUG=godwoken-web3-api:server nodemon ./bin/www\"", @@ -16,9 +16,9 @@ }, "dependencies": { "@ckb-lumos/base": "^0.16.0", - "@godwoken-web3/godwoken": "0.6.0-rc6", + "@godwoken-web3/godwoken": "0.6.0-rc7", "@newrelic/native-metrics": "^7.0.1", - "@polyjuice-provider/base": "^0.0.1-rc7", + "@polyjuice-provider/base": "^0.0.1-rc10", "@sentry/node": "^6.11.0", "blake2b": "2.1.3", "ckb-js-toolkit": "^0.10.2", diff --git a/packages/api-server/src/base/address.ts b/packages/api-server/src/base/address.ts index 97add62f..a9368128 100644 --- a/packages/api-server/src/base/address.ts +++ b/packages/api-server/src/base/address.ts @@ -48,3 +48,16 @@ export function isAddressMatch( const computedShortAddress = ethAddressToShortAddress(ethAddress); return shortAddress === computedShortAddress; } + +export async function isShortAddressOnChain( + godwokenClient: GodwokenClient, + shortAddress: HexString +) { + const scriptHash = await godwokenClient.getScriptHashByShortAddress( + shortAddress + ); + if (scriptHash == null) { + return false; + } + return true; +} diff --git a/packages/api-server/src/db/accounts.ts b/packages/api-server/src/db/accounts.ts index 06ac285d..c2b25a4e 100644 --- a/packages/api-server/src/db/accounts.ts +++ b/packages/api-server/src/db/accounts.ts @@ -46,10 +46,9 @@ export class AccountsQuery { shortAddress: HexString ): Promise { const result = await this.knex(ACCOUNTS_TABLE_NAME) - .where("eth_address", ethAddress) - .orWhere("gw_short_address", shortAddress) + .where("eth_address", toBuffer(ethAddress)) + .orWhere("gw_short_address", toBuffer(shortAddress)) .first(); - return result != null; } diff --git a/packages/api-server/src/methods/modules/eth.ts b/packages/api-server/src/methods/modules/eth.ts index bf855c9d..b8c52dd3 100644 --- a/packages/api-server/src/methods/modules/eth.ts +++ b/packages/api-server/src/methods/modules/eth.ts @@ -45,7 +45,7 @@ import { EthTransactionReceipt, } from "../../base/types/api"; import { filterWeb3Transaction } from "../../filter-web3-tx"; -import { Abi } from "@polyjuice-provider/base"; +import { Abi, ShortAddress, ShortAddressType } from "@polyjuice-provider/base"; import { SUDT_ERC20_PROXY_ABI, allowedAddresses } from "../../erc20"; import { FilterManager } from "../../cache"; import { toHex } from "../../util"; @@ -1078,9 +1078,14 @@ async function ethCallTx( } const abi = new Abi(SUDT_ERC20_PROXY_ABI); - const ethToGwAddr = async (addr: HexString): Promise => { + + // TODO: save addressMapping into db when encounter not-exist-eth-eoa-address + const ethToGwAddr = async (addr: HexString): Promise => { const result = await allTypeEthAddressToShortAddress(rpc, addr); - return result!; + return { + value: result!, + type: ShortAddressType.eoaAddress, // TODO: return correct address type + }; }; // TODO: find by db.addresses when not found @@ -1114,7 +1119,7 @@ async function ethCallTx( blockNumber ); - const abiItem = abi.get_intereted_abi_item_by_encoded_data(data); + const abiItem = abi.get_interested_abi_item_by_encoded_data(data); if (abiItem && isEthWallet) { const returnDataWithShortAddress = diff --git a/packages/api-server/src/methods/modules/poly.ts b/packages/api-server/src/methods/modules/poly.ts index 28bd4e74..90cee99c 100644 --- a/packages/api-server/src/methods/modules/poly.ts +++ b/packages/api-server/src/methods/modules/poly.ts @@ -1,16 +1,38 @@ import { middleware, validators } from "../validator"; -import { Hash, HexNumber, Address } from "@ckb-lumos/base"; +import { Hash, HexNumber, Address, HexString } from "@ckb-lumos/base"; import { toHexNumber } from "../../base/types/uint"; import { envConfig } from "../../base/env-config"; -import { InternalError, InvalidParamsError, Web3Error } from "../error"; +import { + InternalError, + InvalidParamsError, + RpcError, + Web3Error, +} from "../error"; import { Query } from "../../db"; -import { isAddressMatch } from "../../base/address"; +import { isAddressMatch, isShortAddressOnChain } from "../../base/address"; +import { GW_RPC_REQUEST_ERROR } from "../error-code"; +import { + decodeArgs, + deserializeL2TransactionWithAddressMapping, + deserializeRawL2TransactionWithAddressMapping, + deserializeAbiItem, + getAddressesFromInputDataByAbi, + EMPTY_ABI_ITEM_SERIALIZE_STR, +} from "@polyjuice-provider/base"; +import { AbiItem } from "@polyjuice-provider/godwoken/lib/abiTypes"; +import { + L2TransactionWithAddressMapping, + RawL2TransactionWithAddressMapping, +} from "@polyjuice-provider/godwoken/lib/addressTypes"; +import { GodwokenClient } from "@godwoken-web3/godwoken"; export class Poly { private query: Query; + private rpc: GodwokenClient; constructor() { this.query = new Query(envConfig.databaseUrl); + this.rpc = new GodwokenClient(envConfig.godwokenJsonRpc); this.getEthAddressByGodwokenShortAddress = middleware( this.getEthAddressByGodwokenShortAddress.bind(this), @@ -50,14 +72,48 @@ export class Poly { } } + async submitL2Transaction(args: any[]) { + try { + const data = args[0]; + const txWithAddressMapping: L2TransactionWithAddressMapping = + deserializeL2TransactionWithAddressMapping(data); + const l2Tx = txWithAddressMapping.tx; + const result = await this.rpc.submitL2Transaction(l2Tx); + // if result is fine, then tx is legal, we can start thinking to store the address mapping + await saveAddressMapping(this.query, this.rpc, txWithAddressMapping); + return result; + } catch (error) { + parseError(error); + } + } + + async executeRawL2Transaction(args: any[]) { + try { + const data = args[0]; + const txWithAddressMapping: RawL2TransactionWithAddressMapping = + deserializeRawL2TransactionWithAddressMapping(data); + const rawL2Tx = txWithAddressMapping.raw_tx; + const result = await this.rpc.executeRawL2Transaction(rawL2Tx); + // if result is fine, then tx is legal, we can start thinking to store the address mapping + await saveAddressMapping(this.query, this.rpc, txWithAddressMapping); + return result; + } catch (error) { + parseError(error); + } + } + async saveEthAddressGodwokenShortAddressMapping( args: [string, string] ): Promise { + // TODO: remove this function later + // throw new Web3Error( + // "this method is deprecated! please upgrade @polyjuice-provider over 0.0.1-rc10 version! see: https://www.npmjs.com/org/polyjuice-provider" + // ); try { const ethAddress = args[0]; const godwokenShortAddress = args[1]; - // todo: save before check if it not exsit; - // TODO: check exists + + // check if it exist const exists = await this.query.accounts.exists( ethAddress, godwokenShortAddress @@ -142,3 +198,115 @@ export class Poly { } } } + +async function saveAddressMapping( + query: Query, + rpc: GodwokenClient, + txWithAddressMapping: + | L2TransactionWithAddressMapping + | RawL2TransactionWithAddressMapping +) { + console.log(JSON.stringify(txWithAddressMapping, null, 2)); + + if ( + txWithAddressMapping.addresses.length === "0x0" || + txWithAddressMapping.addresses.data.length === 0 + ) { + console.log(`empty addressMapping, abort saving.`); + return; + } + + if (txWithAddressMapping.extra === EMPTY_ABI_ITEM_SERIALIZE_STR) { + console.log(`addressMapping without abiItem, abort saving.`); + return; + } + + let rawTx; + if ("raw_tx" in txWithAddressMapping) { + rawTx = txWithAddressMapping.raw_tx; + } else { + rawTx = txWithAddressMapping.tx.raw; + } + const ethTxData = decodeArgs(rawTx.args).data; + const abiItemStr = txWithAddressMapping.extra; + const abiItem: AbiItem = deserializeAbiItem(abiItemStr); + const addressesFromEthTxData = getAddressesFromInputDataByAbi( + ethTxData, + abiItem + ); + if (addressesFromEthTxData.length === 0) { + console.log( + `eth tx data ${ethTxData} contains no valid address, abort saving.` + ); + return; + } + + await Promise.all( + txWithAddressMapping.addresses.data.map(async (item) => { + const ethAddress: HexString = item.eth_address; + const godwokenShortAddress: HexString = item.gw_short_address; + + if (!addressesFromEthTxData.includes(godwokenShortAddress)) { + console.log( + `illegal address mapping, since godwoken_short_address ${godwokenShortAddress} is not in the ethTxData. expected addresses: ${JSON.stringify( + addressesFromEthTxData, + null, + 2 + )}` + ); + return; + } + + try { + const exists = await query.accounts.exists( + ethAddress, + godwokenShortAddress + ); + if (exists) { + console.log( + `abort saving, since godwoken_short_address ${godwokenShortAddress} is already saved on database.` + ); + return; + } + if (!isAddressMatch(ethAddress, godwokenShortAddress)) { + throw new Error( + `eth_address ${ethAddress} and godwoken_short_address ${godwokenShortAddress} unmatched! abort saving!` + ); + } + const isExistOnChain = await isShortAddressOnChain( + rpc, + godwokenShortAddress + ); + if (isExistOnChain) { + console.log( + `abort saving, since godwoken_short_address ${godwokenShortAddress} is already on chain.` + ); + return; + } + + await query.accounts.save(ethAddress, godwokenShortAddress); + console.log( + `poly_save: insert one record, [${godwokenShortAddress}]: ${ethAddress}` + ); + return; + } catch (error) { + console.log( + `abort saving addressMapping [${godwokenShortAddress}]: ${ethAddress} , will keep saving the rest. =>`, + error + ); + } + }) + ); +} + +function parseError(error: any): void { + const prefix = "JSONRPCError: server error "; + let message: string = error.message; + if (message.startsWith(prefix)) { + const jsonErr = message.slice(prefix.length); + const err = JSON.parse(jsonErr); + throw new RpcError(err.code, err.message); + } + + throw new RpcError(GW_RPC_REQUEST_ERROR, error.message); +} diff --git a/packages/godwoken/package.json b/packages/godwoken/package.json index 44c5f590..620dc100 100644 --- a/packages/godwoken/package.json +++ b/packages/godwoken/package.json @@ -1,6 +1,6 @@ { "name": "@godwoken-web3/godwoken", - "version": "0.6.0-rc6", + "version": "0.6.0-rc7", "private": true, "main": "lib/index.js", "scripts": { diff --git a/yarn.lock b/yarn.lock index 5531aca8..02445728 100644 --- a/yarn.lock +++ b/yarn.lock @@ -327,13 +327,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@polyjuice-provider/base@^0.0.1-rc7": - version "0.0.1-rc7" - resolved "https://registry.yarnpkg.com/@polyjuice-provider/base/-/base-0.0.1-rc7.tgz#5df16cccc178f049192139758ecd7852e4529480" - integrity sha512-wFwDBf2xpDBySAjBKI0A1ugfJ8398MW+LiLPQiJoY1Ca1e85+I6OMYCjQGGGYB2gbALWKwAcplin7RVbGiajPA== +"@polyjuice-provider/base@^0.0.1-rc10": + version "0.0.1-rc10" + resolved "https://registry.yarnpkg.com/@polyjuice-provider/base/-/base-0.0.1-rc10.tgz#23e8c74993eaaaa18a391a103e18f1aedcc369f4" + integrity sha512-2cGKXh2ST57qMlthL8tKVv5/JM+RtrJnIjifD1Sx7zWpKuFa+m6MWjwK+LUNWJzO3/6NKjhotIEMOqr3jI++HQ== dependencies: "@ckb-lumos/base" "^0.16.0" - "@polyjuice-provider/godwoken" "^0.0.1-rc7" + "@polyjuice-provider/godwoken" "^0.0.1-rc10" buffer "^6.0.3" encoding "^0.1.13" eth-sig-util "^3.0.1" @@ -342,10 +342,10 @@ web3 "^1.3.4" xhr2-cookies "^1.1.0" -"@polyjuice-provider/godwoken@^0.0.1-rc7": - version "0.0.1-rc7" - resolved "https://registry.yarnpkg.com/@polyjuice-provider/godwoken/-/godwoken-0.0.1-rc7.tgz#1c7ff81d92061adbbd1da51721d1545d20e4a2a1" - integrity sha512-jBQ1Ivf1TbcX935bUh+QPWwCdueQEsEmLzU6gU3/luTVNwBHHrNElwMDtoqrTVi0akA9CRMWoNMKzSTjNnTNNw== +"@polyjuice-provider/godwoken@^0.0.1-rc10": + version "0.0.1-rc10" + resolved "https://registry.yarnpkg.com/@polyjuice-provider/godwoken/-/godwoken-0.0.1-rc10.tgz#ca927aac16d80046440b278cdba7f08b85c5c832" + integrity sha512-8Em+jlKqaoDqFM3Ab+fax3dV6gZbMB0oAdsHgxyuiP0XmNBccZRA1PnHwgTzB1hcauqAz4J9IbO92tDY717Vfg== dependencies: "@ckb-lumos/base" "^0.16.0" ckb-js-toolkit "^0.9.3"