diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index c2c78826..a258ccd4 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -29,11 +29,8 @@ jobs: - run: yarn install - run: yarn run build - run: yarn workspace @godwoken-web3/api-server run test tests/db/helpers.test.ts - - run: yarn workspace @godwoken-web3/api-server run test tests/utils/erc20.test.ts - - run: yarn workspace @godwoken-web3/api-server run test tests/base/types/uint32.test.ts - - run: yarn workspace @godwoken-web3/api-server run test tests/base/types/uint64.test.ts - - run: yarn workspace @godwoken-web3/api-server run test tests/base/types/uint128.test.ts - - run: yarn workspace @godwoken-web3/api-server run test tests/base/types/uint256.test.ts + - run: yarn workspace @godwoken-web3/api-server run test tests/utils + - run: yarn workspace @godwoken-web3/api-server run test tests/base/types - run: yarn run fmt - run: yarn run lint - run: git diff --exit-code diff --git a/README.md b/README.md index 0a8f083c..4990194d 100644 --- a/README.md +++ b/README.md @@ -9,25 +9,18 @@ A Web3 RPC compatible layer build upon Godwoken/Polyjuice. ```bash $ cat > ./packages/api-server/.env < + GODWOKEN_JSON_RPC= GODWOKEN_READONLY_JSON_RPC= -ETH_ACCOUNT_LOCK_HASH= -ROLLUP_TYPE_HASH= -ROLLUP_CONFIG_HASH= -CHAIN_ID= -CREATOR_ACCOUNT_ID= -DEFAULT_FROM_ID= -POLYJUICE_VALIDATOR_TYPE_HASH= -L2_SUDT_VALIDATOR_SCRIPT_TYPE_HASH= -ETH_ADDRESS_REGISTRY_ACCOUNT_ID= SENTRY_DNS= SENTRY_ENVIRONMENT=, NEW_RELIC_LICENSE_KEY= NEW_RELIC_APP_NAME= -CLUSTER_COUNT= -REDIS_URL=redis://user:password@localhost:6379 + PG_POOL_MAX= +CLUSTER_COUNT= GAS_PRICE_CACHE_SECONDS= EXTRA_ESTIMATE_GAS= ENABLE_CACHE_ETH_CALL= @@ -76,11 +69,11 @@ chain_id= EOF ``` -Or just run script, copy configs from `packages/api-server/.env` file. +Or just run script, generate configs from `packages/api-server/.env` file and godwoken rpc `gw_get_node_info`. ```bash node scripts/generate-indexer-config.js -``` +``` ### Start Indexer diff --git a/packages/api-server/src/app/app.ts b/packages/api-server/src/app/app.ts index 21c1ff01..08960f9f 100644 --- a/packages/api-server/src/app/app.ts +++ b/packages/api-server/src/app/app.ts @@ -8,7 +8,9 @@ import Sentry from "@sentry/node"; import { applyRateLimitByIp } from "../rate-limit"; import { initSentry } from "../sentry"; import { envConfig } from "../base/env-config"; +import { gwConfig } from "../base/index"; import { expressLogger, logger } from "../base/logger"; +import { Server } from "http"; let newrelic: any | undefined = undefined; if (envConfig.newRelicLicenseKey) { @@ -132,4 +134,29 @@ app.use(function ( res.render("error"); }); -export { app }; +let server: Server | undefined; + +async function startServer(port: number): Promise { + try { + await gwConfig.init(); + logger.info("godwoken config initialized!"); + } catch (err) { + logger.error("godwoken config initialize failed:", err); + process.exit(1); + } + server = app.listen(port, () => { + const addr = (server as Server).address(); + const bind = + typeof addr === "string" ? "pipe " + addr : "port " + addr!.port; + logger.info("godwoken-web3-api:server Listening on " + bind); + }); +} + +function isListening() { + if (server == null) { + return false; + } + return server.listening; +} + +export { startServer, isListening }; diff --git a/packages/api-server/src/app/www.ts b/packages/api-server/src/app/www.ts index be6a0f32..582ffa0a 100644 --- a/packages/api-server/src/app/www.ts +++ b/packages/api-server/src/app/www.ts @@ -2,19 +2,10 @@ * Module dependencies. */ -import { logger } from "../base/logger"; -import { app } from "./app"; +import { startServer } from "./app"; /** * Get port from environment and store in Express. */ const port: number = +(process.env.PORT || "3000"); -const server = app.listen(port, () => { - const addr = server.address(); - const bind = typeof addr === "string" ? "pipe " + addr : "port " + addr!.port; - logger.info("godwoken-web3-api:server Listening on " + bind); -}); - -export const isListening = function () { - return server.listening; -}; +startServer(port); diff --git a/packages/api-server/src/base/address.ts b/packages/api-server/src/base/address.ts index 102c9e52..f25c31a8 100644 --- a/packages/api-server/src/base/address.ts +++ b/packages/api-server/src/base/address.ts @@ -3,6 +3,7 @@ import { GodwokenClient } from "@godwoken-web3/godwoken"; import { Store } from "../cache/store"; import { COMPATIBLE_DOCS_URL } from "../methods/constant"; import { envConfig } from "./env-config"; +import { gwConfig } from "./index"; import { logger } from "./logger"; import { Uint32 } from "./types/uint"; @@ -14,7 +15,7 @@ scriptHashCache.init(); // Only support eth address now! export class EthRegistryAddress { - private registryId: number = +envConfig.ethAddressRegistryAccountId; + private registryId: number = +gwConfig.accounts.ethAddrReg.id; private addressByteSize: number = 20; public readonly address: HexString; @@ -86,7 +87,7 @@ export async function ethAddressToAccountId( godwokenClient: GodwokenClient ): Promise { if (ethAddress === "0x") { - return +envConfig.creatorAccountId; + return +gwConfig.accounts.polyjuiceCreator.id; } if (ethAddress === ZERO_ETH_ADDRESS) { @@ -111,9 +112,9 @@ export async function ethAddressToAccountId( export function ethEoaAddressToScriptHash(address: string) { const script: Script = { - code_hash: envConfig.ethAccountLockHash, + code_hash: gwConfig.eoaScripts.eth.typeHash, hash_type: "type", - args: envConfig.rollupTypeHash + address.slice(2), + args: gwConfig.rollupCell.typeHash + address.slice(2), }; const scriptHash = utils.computeScriptHash(script); return scriptHash; diff --git a/packages/api-server/src/base/env-config.ts b/packages/api-server/src/base/env-config.ts index c8597a67..e3e466bb 100644 --- a/packages/api-server/src/base/env-config.ts +++ b/packages/api-server/src/base/env-config.ts @@ -5,18 +5,7 @@ dotenv.config({ path: "./.env" }); export const envConfig = { databaseUrl: getRequired("DATABASE_URL"), - ethAccountLockHash: getRequired("ETH_ACCOUNT_LOCK_HASH"), - rollupTypeHash: getRequired("ROLLUP_TYPE_HASH"), godwokenJsonRpc: getRequired("GODWOKEN_JSON_RPC"), - creatorAccountId: getRequired("CREATOR_ACCOUNT_ID"), - chainId: getRequired("CHAIN_ID"), - defaultFromId: getRequired("DEFAULT_FROM_ID"), - l2SudtValidatorScriptTypeHash: getRequired( - "L2_SUDT_VALIDATOR_SCRIPT_TYPE_HASH" - ), - ethAddressRegistryAccountId: getRequired("ETH_ADDRESS_REGISTRY_ACCOUNT_ID"), - polyjuiceValidatorTypeHash: getOptional("POLYJUICE_VALIDATOR_TYPE_HASH"), - rollupConfigHash: getOptional("ROLLUP_CONFIG_HASH"), newRelicLicenseKey: getOptional("NEW_RELIC_LICENSE_KEY"), clusterCount: getOptional("CLUSTER_COUNT"), redisUrl: getOptional("REDIS_URL"), diff --git a/packages/api-server/src/base/gw-config.ts b/packages/api-server/src/base/gw-config.ts new file mode 100644 index 00000000..f011abf6 --- /dev/null +++ b/packages/api-server/src/base/gw-config.ts @@ -0,0 +1,428 @@ +import { utils, HexNumber, Script, HexString } from "@ckb-lumos/base"; +import { + BackendInfo, + EoaScript, + GwScript, + NodeInfo, + RollupCell, + RollupConfig, +} from "./types/node-info"; +import { + NodeMode, + BackendType, + EoaScriptType, + GwScriptType, + GodwokenClient, + NodeInfo as GwNodeInfo, +} from "@godwoken-web3/godwoken"; +import { CKB_SUDT_ID } from "../methods/constant"; +import { Uint32 } from "./types/uint"; +import { snakeToCamel } from "../util"; + +// source: https://github.com/nervosnetwork/godwoken/commit/d6c98d8f8a199b6ec29bc77c5065c1108220bb0a#diff-c56fda2ca3b1366049c88e633389d9b6faa8366151369fd7314c81f6e389e5c7R5 +const BUILTIN_ETH_ADDR_REG_ACCOUNT_ID = 2; + +export class GwConfig { + rpc: GodwokenClient; + private iNodeInfo: NodeInfo | undefined; + private iWeb3ChainId: HexNumber | undefined; + private iAccounts: ConfigAccounts | undefined; + private iEoaScripts: ConfigEoaScripts | undefined; + private iBackends: ConfigBackends | undefined; + private iGwScripts: ConfigGwScripts | undefined; + private iRollupConfig: RollupConfig | undefined; + private iRollupCell: RollupCell | undefined; + private iNodeMode: NodeMode | undefined; + private iNodeVersion: string | undefined; + + constructor(rpcOrUrl: GodwokenClient | string) { + if (typeof rpcOrUrl === "string") { + this.rpc = new GodwokenClient(rpcOrUrl); + return; + } + + this.rpc = rpcOrUrl; + } + + async init(): Promise { + this.iNodeInfo = await this.getNodeInfoFromRpc(); + + const ethAddrReg = await this.fetchEthAddrRegAccount(); + const creator = await this.fetchCreatorAccount(); + const defaultFrom = await this.fetchDefaultFromAccount(); + + this.iAccounts = { + polyjuiceCreator: creator, + ethAddrReg, + defaultFrom, + }; + + this.iEoaScripts = toConfigEoaScripts(this.nodeInfo); + this.iGwScripts = toConfigGwScripts(this.nodeInfo); + this.iBackends = toConfigBackends(this.nodeInfo); + this.iWeb3ChainId = this.nodeInfo.rollupConfig.chainId; + this.iRollupCell = this.nodeInfo.rollupCell; + this.iRollupConfig = this.nodeInfo.rollupConfig; + this.iNodeMode = this.nodeInfo.mode; + this.iNodeVersion = this.nodeInfo.version; + + return this; + } + + public get web3ChainId(): HexNumber { + return this.iWeb3ChainId!; + } + + public get accounts(): ConfigAccounts { + return this.iAccounts!; + } + + public get backends(): ConfigBackends { + return this.iBackends!; + } + + public get eoaScripts(): ConfigEoaScripts { + return this.iEoaScripts!; + } + + public get gwScripts(): ConfigGwScripts { + return this.iGwScripts!; + } + + public get rollupConfig(): RollupConfig { + return this.iRollupConfig!; + } + + public get rollupCell(): RollupCell { + return this.iRollupCell!; + } + + public get nodeMode(): NodeMode { + return this.iNodeMode!; + } + + public get nodeVersion(): string { + return this.iNodeVersion!; + } + + private get nodeInfo(): NodeInfo { + return this.iNodeInfo!; + } + + private async getNodeInfoFromRpc() { + const nodeInfo = await this.rpc.getNodeInfo(); + return toApiNodeInfo(nodeInfo); + } + + private async fetchCreatorAccount() { + const ckbSudtId = new Uint32(+CKB_SUDT_ID).toLittleEndian(); + + const creatorScriptArgs = + this.nodeInfo.rollupCell.typeHash + ckbSudtId.slice(2); + + const polyjuiceValidatorTypeHash = this.nodeInfo.backends.find( + (b) => b.backendType === BackendType.Polyjuice + )?.validatorScriptTypeHash; + + if (polyjuiceValidatorTypeHash == null) { + throw new Error( + `[GwConfig => fetchCreatorAccount] polyjuiceValidatorTypeHash is null! ${JSON.stringify( + this.nodeInfo.backends, + null, + 2 + )}` + ); + } + + const script: Script = { + code_hash: polyjuiceValidatorTypeHash, + hash_type: "type", + args: creatorScriptArgs, + }; + + const scriptHash = utils.computeScriptHash(script); + + const creatorId = await this.rpc.getAccountIdByScriptHash(scriptHash); + if (creatorId == null) { + throw new Error( + `[${ + GwConfig.name + }] can't find creator account id by script hash ${scriptHash}, script detail: ${JSON.stringify( + script, + null, + 2 + )}` + ); + } + const creatorIdHex = "0x" + BigInt(creatorId).toString(16); + return new Account(creatorIdHex, scriptHash); + } + + private async fetchEthAddrRegAccount() { + const registryScriptArgs = this.nodeInfo.rollupCell.typeHash; + + const ethAddrRegValidatorTypeHash = this.nodeInfo.backends.find( + (b) => b.backendType === BackendType.EthAddrReg + )?.validatorScriptTypeHash; + if (ethAddrRegValidatorTypeHash == null) { + throw new Error( + `[GwConfig => fetchEthAddrRegAccount] ethAddrRegValidatorTypeHash is null! ${JSON.stringify( + this.nodeInfo.backends, + null, + 2 + )}` + ); + } + + const script: Script = { + code_hash: ethAddrRegValidatorTypeHash, + hash_type: "type", + args: registryScriptArgs, + }; + + const scriptHash = utils.computeScriptHash(script); + + const regId = await this.rpc.getAccountIdByScriptHash(scriptHash); + if (regId == null) { + throw new Error( + `[${ + GwConfig.name + }] can't find ethAddrReg account id by script hash ${scriptHash}, script detail: ${JSON.stringify( + script, + null, + 2 + )}` + ); + } + + if (regId !== BUILTIN_ETH_ADDR_REG_ACCOUNT_ID) { + throw new Error( + `[${ + GwConfig.name + }] ethAddrReg account id is not equal to builtin id ${BUILTIN_ETH_ADDR_REG_ACCOUNT_ID}, script detail: ${JSON.stringify( + script, + null, + 2 + )}` + ); + } + + const regIdHex = "0x" + BigInt(regId).toString(16); + return new Account(regIdHex, scriptHash); + } + + // we search the first account id = 3, if it is eoa account, use it, otherwise continue with id + 1; + private async fetchDefaultFromAccount() { + const ethAccountLockTypeHash = this.nodeInfo.eoaScripts.find( + (s) => s.eoaType === EoaScriptType.Eth + )?.typeHash; + + if (ethAccountLockTypeHash == null) { + throw new Error( + `[GwConfig => fetchDefaultFromAccount] ethAccountLockTypeHash is null! ${JSON.stringify( + this.nodeInfo.eoaScripts, + null, + 2 + )}` + ); + } + + const firstEoaAccount = await findFirstEoaAccountId( + this.rpc, + ethAccountLockTypeHash + ); + + if (firstEoaAccount == null) { + throw new Error("can not find first eoa account."); + } + + return firstEoaAccount; + } +} + +export class Account { + id: HexNumber; + scriptHash: HexString; + + constructor(id: HexNumber, scriptHash: HexString) { + this.id = id; + this.scriptHash = scriptHash; + } +} + +export interface ConfigAccounts { + polyjuiceCreator: Account; + ethAddrReg: Account; + defaultFrom: Account; +} + +export interface ConfigBackends { + sudt: Omit; + meta: Omit; + polyjuice: Omit; + ethAddrReg: Omit; +} + +function toConfigBackends(nodeInfo: NodeInfo) { + const sudt = nodeInfo.backends.filter( + (b) => b.backendType === BackendType.Sudt + )[0]; + const meta = nodeInfo.backends.filter( + (b) => b.backendType === BackendType.Meta + )[0]; + const polyjuice = nodeInfo.backends.filter( + (b) => b.backendType === BackendType.Polyjuice + )[0]; + const ethAddrReg = nodeInfo.backends.filter( + (b) => b.backendType === BackendType.EthAddrReg + )[0]; + + const backends: ConfigBackends = { + sudt: { + validatorCodeHash: sudt.validatorCodeHash, + generatorCodeHash: sudt.generatorCodeHash, + validatorScriptTypeHash: sudt.validatorScriptTypeHash, + }, + meta: { + validatorCodeHash: meta.validatorCodeHash, + generatorCodeHash: meta.generatorCodeHash, + validatorScriptTypeHash: meta.validatorScriptTypeHash, + }, + polyjuice: { + validatorCodeHash: polyjuice.validatorCodeHash, + generatorCodeHash: polyjuice.generatorCodeHash, + validatorScriptTypeHash: polyjuice.validatorScriptTypeHash, + }, + ethAddrReg: { + validatorCodeHash: ethAddrReg.validatorCodeHash, + generatorCodeHash: ethAddrReg.generatorCodeHash, + validatorScriptTypeHash: ethAddrReg.validatorScriptTypeHash, + }, + }; + return backends; +} + +export interface ConfigGwScripts { + deposit: Omit; + withdraw: Omit; + stateValidator: Omit; + stakeLock: Omit; + custodianLock: Omit; + challengeLock: Omit; + l1Sudt: Omit; + l2Sudt: Omit; + omniLock: Omit; +} + +function toConfigGwScripts(nodeInfo: NodeInfo) { + const deposit = findGwScript(GwScriptType.Deposit, nodeInfo); + const withdraw = findGwScript(GwScriptType.Withdraw, nodeInfo); + const stateValidator = findGwScript(GwScriptType.StateValidator, nodeInfo); + const stakeLock = findGwScript(GwScriptType.StakeLock, nodeInfo); + const custodianLock = findGwScript(GwScriptType.CustodianLock, nodeInfo); + const challengeLock = findGwScript(GwScriptType.ChallengeLock, nodeInfo); + const l1Sudt = findGwScript(GwScriptType.L1Sudt, nodeInfo); + const l2Sudt = findGwScript(GwScriptType.L2Sudt, nodeInfo); + const omniLock = findGwScript(GwScriptType.OmniLock, nodeInfo); + + const configGwScripts: ConfigGwScripts = { + deposit: { + script: deposit.script, + typeHash: deposit.typeHash, + }, + withdraw: { + script: withdraw.script, + typeHash: withdraw.typeHash, + }, + stateValidator: { + script: stateValidator.script, + typeHash: stateValidator.typeHash, + }, + stakeLock: { + script: stakeLock.script, + typeHash: stakeLock.typeHash, + }, + custodianLock: { + script: custodianLock.script, + typeHash: custodianLock.typeHash, + }, + challengeLock: { + script: challengeLock.script, + typeHash: challengeLock.typeHash, + }, + l1Sudt: { + script: l1Sudt.script, + typeHash: l1Sudt.typeHash, + }, + l2Sudt: { + script: l2Sudt.script, + typeHash: l2Sudt.typeHash, + }, + omniLock: { + script: omniLock.script, + typeHash: omniLock.typeHash, + }, + }; + return configGwScripts; +} + +function findGwScript(type: GwScriptType, nodeInfo: NodeInfo): GwScript { + const script = nodeInfo.gwScripts.find((s) => s.scriptType === type); + if (script == null) { + throw new Error(`[GwConfig => findGwScript] can not find script ${type}`); + } + return script; +} + +export interface ConfigEoaScripts { + eth: Omit; +} + +function toConfigEoaScripts(nodeInfo: NodeInfo) { + const eth = nodeInfo.eoaScripts.find((e) => e.eoaType === EoaScriptType.Eth); + if (eth == null) { + throw new Error("no Eth eoa script!"); + } + + const configEoas: ConfigEoaScripts = { + eth: { + typeHash: eth.typeHash, + script: eth.script, + }, + }; + return configEoas; +} + +function toApiNodeInfo(nodeInfo: GwNodeInfo): NodeInfo { + return snakeToCamel(nodeInfo, ["code_hash", "hash_type"]); +} + +async function findFirstEoaAccountId( + rpc: GodwokenClient, + ethAccountLockTypeHash: HexString, + startAccountId: number = 3, + maxTry: number = 20 +) { + for (let id = startAccountId; id < maxTry; id++) { + const scriptHash = await rpc.getScriptHash(id); + if (scriptHash == null) { + continue; + } + const script = await rpc.getScript(scriptHash); + if (script == null) { + continue; + } + if (script.code_hash === ethAccountLockTypeHash) { + const accountIdHex = "0x" + BigInt(id).toString(16); + return new Account(accountIdHex, scriptHash); + } + + await asyncSleep(500); + } + + return null; +} + +const asyncSleep = async (ms = 0) => { + return new Promise((r) => setTimeout(() => r("ok"), ms)); +}; diff --git a/packages/api-server/src/base/index.ts b/packages/api-server/src/base/index.ts new file mode 100644 index 00000000..90ec7a23 --- /dev/null +++ b/packages/api-server/src/base/index.ts @@ -0,0 +1,4 @@ +import { envConfig } from "./env-config"; +import { GwConfig } from "./gw-config"; + +export const gwConfig = new GwConfig(envConfig.godwokenJsonRpc); diff --git a/packages/api-server/src/base/types/node-info.ts b/packages/api-server/src/base/types/node-info.ts new file mode 100644 index 00000000..437416f4 --- /dev/null +++ b/packages/api-server/src/base/types/node-info.ts @@ -0,0 +1,48 @@ +import { Hash, HexNumber, Script } from "@ckb-lumos/base"; +import { + EoaScriptType, + BackendType, + NodeMode, + GwScriptType, +} from "@godwoken-web3/godwoken"; + +export interface EoaScript { + typeHash: Hash; + script: Script; + eoaType: EoaScriptType; +} + +export interface BackendInfo { + validatorCodeHash: Hash; + generatorCodeHash: Hash; + validatorScriptTypeHash: Hash; + backendType: BackendType; +} + +export interface GwScript { + typeHash: Hash; + script: Script; + scriptType: GwScriptType; +} + +export interface RollupCell { + typeHash: Hash; + typeScript: Script; +} + +export interface RollupConfig { + requiredStakingCapacity: HexNumber; + challengeMaturityBlocks: HexNumber; + finalityBlocks: HexNumber; + rewardBurnRate: HexNumber; + chainId: HexNumber; +} +export interface NodeInfo { + backends: Array; + eoaScripts: Array; + gwScripts: Array; + rollupCell: RollupCell; + rollupConfig: RollupConfig; + version: string; + mode: NodeMode; +} diff --git a/packages/api-server/src/convert-tx.ts b/packages/api-server/src/convert-tx.ts index d2645ee7..a553d404 100644 --- a/packages/api-server/src/convert-tx.ts +++ b/packages/api-server/src/convert-tx.ts @@ -11,7 +11,7 @@ import { ethAddressToAccountId, ethEoaAddressToScriptHash, } from "./base/address"; -import { envConfig } from "./base/env-config"; +import { gwConfig } from "./base"; import { logger } from "./base/logger"; import { COMPATIBLE_DOCS_URL } from "./methods/constant"; import { verifyGasLimit } from "./methods/validator"; @@ -189,7 +189,7 @@ async function parseRawTransactionData( let toId: HexNumber | undefined; if (to === DEPLOY_TO_ADDRESS) { args_7 = "0x03"; - toId = "0x" + BigInt(envConfig.creatorAccountId).toString(16); + toId = gwConfig.accounts.polyjuiceCreator.id; } else { args_7 = "0x00"; toId = await getAccountIdByEthAddress(to, rpc); @@ -219,7 +219,7 @@ async function parseRawTransactionData( args_data.slice(2); const godwokenRawL2Tx: RawL2Transaction = { - chain_id: "0x" + BigInt(envConfig.chainId).toString(16), + chain_id: gwConfig.web3ChainId, from_id: fromId, to_id: toId, nonce: nonce === "0x" ? "0x0" : nonce, diff --git a/packages/api-server/src/filter-web3-tx.ts b/packages/api-server/src/filter-web3-tx.ts index da9f4198..b34162b3 100644 --- a/packages/api-server/src/filter-web3-tx.ts +++ b/packages/api-server/src/filter-web3-tx.ts @@ -11,7 +11,6 @@ import { U64, } from "@godwoken-web3/godwoken"; import { Reader } from "@ckb-lumos/toolkit"; -import { envConfig } from "./base/env-config"; import { EthTransaction, EthTransactionReceipt } from "./base/types/api"; import { Uint128, Uint256, Uint32, Uint64 } from "./base/types/uint"; import { PolyjuiceSystemLog, PolyjuiceUserLog } from "./base/types/gw-log"; @@ -22,6 +21,7 @@ import { POLYJUICE_SYSTEM_LOG_FLAG, POLYJUICE_USER_LOG_FLAG, } from "./methods/constant"; +import { gwConfig } from "./base/index"; import { logger } from "./base/logger"; import { EthRegistryAddress } from "./base/address"; @@ -49,14 +49,14 @@ export async function filterWeb3Transaction( } // skip tx with non eth_account_lock from_id - if (fromScript.code_hash !== envConfig.ethAccountLockHash) { + if (fromScript.code_hash !== gwConfig.eoaScripts.eth.typeHash) { return undefined; } const fromScriptArgs: HexString = fromScript.args; if ( fromScriptArgs.length !== 106 || - fromScriptArgs.slice(0, 66) !== envConfig.rollupTypeHash + fromScriptArgs.slice(0, 66) !== gwConfig.rollupCell.typeHash ) { logger.error("Wrong from_address's script args:", fromScriptArgs); return undefined; @@ -84,7 +84,9 @@ export async function filterWeb3Transaction( const nonce: HexU32 = l2Tx.raw.nonce; - if (toScript.code_hash === envConfig.polyjuiceValidatorTypeHash) { + if ( + toScript.code_hash === gwConfig.backends.polyjuice.validatorScriptTypeHash + ) { const l2TxArgs: HexNumber = l2Tx.raw.args; const polyjuiceArgs = decodePolyjuiceArgs(l2TxArgs); @@ -181,7 +183,7 @@ export async function filterWeb3Transaction( return [ethTx, receipt]; } else if ( toId === +CKB_SUDT_ID && - toScript.code_hash === envConfig.l2SudtValidatorScriptTypeHash + toScript.code_hash === gwConfig.gwScripts.l2Sudt.typeHash ) { const sudtArgs = new schemas.SUDTArgs(new Reader(l2Tx.raw.args)); if (sudtArgs.unionType() === "SUDTTransfer") { diff --git a/packages/api-server/src/methods/modules/eth.ts b/packages/api-server/src/methods/modules/eth.ts index 9f145dfd..a12b4cd6 100644 --- a/packages/api-server/src/methods/modules/eth.ts +++ b/packages/api-server/src/methods/modules/eth.ts @@ -75,6 +75,7 @@ import { calcEthTxHash, generateRawTransaction } from "../../convert-tx"; import { ethAddressToAccountId, EthRegistryAddress } from "../../base/address"; import { keccakFromString } from "ethereumjs-util"; import { DataCacheConstructor, RedisDataCache } from "../../cache/data"; +import { gwConfig } from "../../base/index"; import { logger } from "../../base/logger"; const Config = require("../../../config/eth.json"); @@ -237,7 +238,7 @@ export class Eth { } chainId(args: []): HexNumber { - return "0x" + BigInt(envConfig.chainId).toString(16); + return gwConfig.web3ChainId!; } /** @@ -1403,7 +1404,7 @@ async function buildEthCallTx( } if (!fromAddress) { - fromId = +envConfig.defaultFromId; + fromId = +gwConfig.accounts.defaultFrom.id; logger.debug(`use default fromId: ${fromId}`); } @@ -1431,7 +1432,7 @@ async function buildEthCallTx( data ); const rawL2Transaction = buildRawL2Transaction( - BigInt(envConfig.chainId), + BigInt(gwConfig.web3ChainId), fromId, toId, nonce, diff --git a/packages/api-server/src/methods/modules/net.ts b/packages/api-server/src/methods/modules/net.ts index e6caa57d..b1377da8 100644 --- a/packages/api-server/src/methods/modules/net.ts +++ b/packages/api-server/src/methods/modules/net.ts @@ -1,6 +1,6 @@ import { HexNumber } from "@ckb-lumos/base"; -import { envConfig } from "../../base/env-config"; -const server = require("../../../bin/www"); +import { isListening } from "../../app/app"; +import { gwConfig } from "../../base/index"; export class Net { constructor() {} @@ -11,8 +11,8 @@ export class Net { * @param {Function} [cb] A function with an error object as the first argument and the * net version as the second argument */ - version(args: []): HexNumber { - return "0x" + BigInt(envConfig.chainId).toString(16); + version(_args: []): HexNumber { + return gwConfig.web3ChainId!; } /** @@ -21,7 +21,7 @@ export class Net { * @param {Function} [cb] A function with an error object as the first argument and the * current peer nodes number as the second argument */ - peerCount(args: []): HexNumber { + peerCount(_args: []): HexNumber { return "0x0"; } @@ -31,7 +31,7 @@ export class Net { * @param {Function} [cb] A function with an error object as the first argument and the * boolean as the second argument */ - listening(args: []): boolean { - return server.isListening(); + listening(_args: []): boolean { + return isListening(); } } diff --git a/packages/api-server/src/methods/modules/poly.ts b/packages/api-server/src/methods/modules/poly.ts index fd34d36c..17f23f98 100644 --- a/packages/api-server/src/methods/modules/poly.ts +++ b/packages/api-server/src/methods/modules/poly.ts @@ -1,8 +1,8 @@ -import { Hash, HexNumber, Address } from "@ckb-lumos/base"; -import { toHexNumber } from "../../base/types/uint"; +import { Hash, HexNumber } from "@ckb-lumos/base"; import { envConfig } from "../../base/env-config"; -import { Web3Error } from "../error"; +import { MethodNotSupportError, Web3Error } from "../error"; import { GodwokenClient } from "@godwoken-web3/godwoken"; +import { gwConfig } from "../../base/index"; const { version: web3Version } = require("../../../package.json"); export class Poly { @@ -17,7 +17,7 @@ export class Poly { async getCreatorId(_args: []): Promise { try { - const creatorIdHex = toHexNumber(BigInt(envConfig.creatorAccountId)); + const creatorIdHex = gwConfig.accounts.polyjuiceCreator.id; return creatorIdHex; } catch (err: any) { throw new Web3Error(err.message); @@ -25,60 +25,49 @@ export class Poly { } // from in eth_call is optional, DEFAULT_FROM_ADDRESS fills it when empty - async getDefaultFromId(_args: []): Promise
{ - return envConfig.defaultFromId; + async getDefaultFromId(_args: []): Promise { + return gwConfig.accounts.defaultFrom.id; } - async getContractValidatorTypeHash(args: []): Promise { - if (envConfig.polyjuiceValidatorTypeHash) { - return envConfig.polyjuiceValidatorTypeHash; - } - throw new Web3Error("POLYJUICE_VALIDATOR_TYPE_HASH not found!"); + async getContractValidatorTypeHash(_args: []): Promise { + return gwConfig.backends.polyjuice.validatorScriptTypeHash; } - async getRollupTypeHash(args: []): Promise { - if (envConfig.rollupTypeHash) { - return envConfig.rollupTypeHash; - } - throw new Web3Error("ROLLUP_TYPE_HASH not found!"); + async getRollupTypeHash(_args: []): Promise { + return gwConfig.rollupCell.typeHash; } - async getRollupConfigHash(args: []): Promise { - if (envConfig.rollupConfigHash) { - return envConfig.rollupConfigHash; - } - throw new Web3Error("ROLLUP_CONFIG_HASH not found!"); + async getRollupConfigHash(_args: []): Promise { + throw new MethodNotSupportError("ROLLUP_CONFIG_HASH not supported!"); } - async getEthAccountLockHash(args: []): Promise { - if (envConfig.ethAccountLockHash) { - return envConfig.ethAccountLockHash; - } - throw new Web3Error("ETH_ACCOUNT_LOCK_HASH not found!"); + async getEthAccountLockHash(_args: []): Promise { + return gwConfig.eoaScripts.eth.typeHash; } - async getChainInfo(args: []): Promise { - try { - const chainInfo = { - rollupScriptHash: envConfig.rollupTypeHash || null, - rollupConfigHash: envConfig.rollupConfigHash || null, - ethAccountLockTypeHash: envConfig.ethAccountLockHash || null, - polyjuiceContractTypeHash: envConfig.polyjuiceValidatorTypeHash || null, - polyjuiceCreatorId: envConfig.creatorAccountId || null, - chainId: envConfig.chainId || null, - }; - return chainInfo; - } catch (error: any) { - throw new Web3Error(error.message); - } + async getChainInfo(_args: []): Promise { + throw new MethodNotSupportError( + "getChainInfo is deprecated! please use poly_version" + ); } async version() { - const godwokenVersion = await this.rpc.getNodeInfo(); return { - web3Version, - web3IndexerVersion: web3Version, // indexer and api-server should use the same version - godwokenVersion, + versions: { + web3Version, + web3IndexerVersion: web3Version, // indexer and api-server should use the same version + godwokenVersion: gwConfig.nodeVersion, + }, + nodeInfo: { + nodeMode: gwConfig.nodeMode, + rollupCell: gwConfig.rollupCell, + rollupConfig: gwConfig.rollupConfig, + gwScripts: gwConfig.gwScripts, + eoaScripts: gwConfig.eoaScripts, + backends: gwConfig.backends, + accounts: gwConfig.accounts, + web3ChainId: gwConfig.web3ChainId, + }, }; } } diff --git a/packages/api-server/src/util.ts b/packages/api-server/src/util.ts index 0a5813fe..a63a602f 100644 --- a/packages/api-server/src/util.ts +++ b/packages/api-server/src/util.ts @@ -40,22 +40,46 @@ export function toSnake(s: string) { return s.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); } -export function snakeToCamel(t: object) { - // db schema: snake_name => json rpc: camelName +// convert object key snake_name => camelName +export function snakeToCamel( + t: object, + excludeKeys: string[] = [], + depthLimit: number = 10 // prevent memory leak for recursive +) { + if (depthLimit === 0) { + throw new Error("[snakeToCamel] recursive depth reached max limit."); + } + let camel: any = {}; - Object.keys(t).map((key) => { - //@ts-ignore - camel[toCamel(key)] = t[key]; + Object.entries(t).map(([key, value]) => { + let newValue = + typeof value === "object" + ? snakeToCamel(value, excludeKeys, depthLimit - 1) + : value; + const newKey = excludeKeys.includes(key) ? key : toCamel(key); + camel[newKey] = Array.isArray(value) ? Object.values(newValue) : newValue; }); return camel; } -export function camelToSnake(t: object) { - // json rpc: camelName => db schema: snake_name +// convert object key camelName => snake_name +export function camelToSnake( + t: object, + excludeKeys: string[] = [], + depthLimit: number = 10 // prevent memory leak for recursive +) { + if (depthLimit === 0) { + throw new Error("[camelToSnake] recursive depth reached max limit."); + } + let snake: any = {}; - Object.keys(t).map((key) => { - //@ts-ignore - snake[toSnake(key)] = t[key]; + Object.entries(t).map(([key, value]) => { + let newValue = + typeof value === "object" + ? camelToSnake(value, excludeKeys, depthLimit - 1) + : value; + const newKey = excludeKeys.includes(key) ? key : toSnake(key); + snake[newKey] = Array.isArray(value) ? Object.values(newValue) : newValue; }); return snake; } diff --git a/packages/api-server/tests/utils/config.test.ts b/packages/api-server/tests/utils/config.test.ts new file mode 100644 index 00000000..7668db7d --- /dev/null +++ b/packages/api-server/tests/utils/config.test.ts @@ -0,0 +1,208 @@ +import { HexString, Script } from "@ckb-lumos/base"; +import { + BackendType, + EoaScriptType, + GodwokenClient, + GwScriptType, + NodeInfo, + NodeMode, +} from "@godwoken-web3/godwoken"; +import test from "ava"; +import { GwConfig } from "../../src/base/gw-config"; + +const script: Script = { + code_hash: "0x", + hash_type: "type", + args: "0x", +}; + +const gwConfig = new GwConfig("http://host:8119"); + +let mockRpc: GodwokenClient = gwConfig.rpc; + +mockRpc.getAccountIdByScriptHash = async (scriptHash: HexString) => { + switch (scriptHash) { + case "0x5df8df09ec23819836b888f575ca4154a2af1f1d4720bca91a5fc9f5f7d9921f": + return 3; + + case "0x7df8df09ec23819836b888f575ca4154a2af1f1d4720bca91a5fc9f5f7d9921d": + return 4; + + case "0xb5f81e2d10af9600194606989583ae8cc3fcb822a24fdea95f42da5ea18606da": + return 2; + + default: + throw new Error( + `getAccountIdByScriptHash not mock for script hash ${scriptHash}` + ); + } +}; + +mockRpc.getScriptHash = async (accountId: number) => { + switch (accountId) { + case 4: + return "0x7df8df09ec23819836b888f575ca4154a2af1f1d4720bca91a5fc9f5f7d9921d"; + + case 2: + return "0xb5f81e2d10af9600194606989583ae8cc3fcb822a24fdea95f42da5ea18606da"; + + case 3: + return "0x5df8df09ec23819836b888f575ca4154a2af1f1d4720bca91a5fc9f5f7d9921f"; + + default: + throw new Error(`getScriptHash not mock for account id ${accountId}`); + } +}; + +mockRpc.getScript = async (scriptHash: HexString) => { + switch (scriptHash) { + case "0x31e7f492d2b22220cad86b7cef30a45ce8df34b00a8ba0d0c5dfd92e7392023a": + return { + code_hash: + "0x9b599c7df5d7b813f7f9542a5c8a0c12b65261a081b1dba02c2404802f772a15", + hash_type: "type", + args: "0x4ed4a999f0046230d67503c07f1e64f2b2ad1440f758ebfc97282be40f74673c00000010000003", + }; + + case "0x5df8df09ec23819836b888f575ca4154a2af1f1d4720bca91a5fc9f5f7d9921f": + return { + code_hash: + "0x1272c80507fe5e6cf33cf3e5da6a5f02430de40abb14410ea0459361bf74ebe0", + hash_type: "type", + args: "0x4ed4a999f0046230d67503c07f1e64f2b2ad1440f758ebfc97282be40f74673c0xFb2C72d3ffe10Ef7c9960272859a23D24db9e04A", + }; + + default: + throw new Error(`getScript not mock for scriptHash ${scriptHash}`); + } +}; + +mockRpc.getNodeInfo = async () => { + const nodeInfo: NodeInfo = { + backends: [ + { + validator_code_hash: "", + generator_code_hash: "", + validator_script_type_hash: + "0x32923ebad8e5417ae072decc89774324ec4a623f57af5cee6e2901d29d8e6691", + backend_type: BackendType.Meta, + }, + { + validator_code_hash: "", + generator_code_hash: "", + validator_script_type_hash: + "0x9b599c7df5d7b813f7f9542a5c8a0c12b65261a081b1dba02c2404802f772a15", + backend_type: BackendType.Polyjuice, + }, + { + validator_code_hash: "", + generator_code_hash: "", + validator_script_type_hash: + "0x696447c51fdb84d0e59850b26bc431425a74daaac070f2b14f5602fbb469912a", + backend_type: BackendType.Sudt, + }, + { + validator_code_hash: "", + generator_code_hash: "", + validator_script_type_hash: + "0x59ecd45fc257a761d992507ef2e1acccf43221567f6cf3b1fc6fb9352a7a0ca3", + backend_type: BackendType.EthAddrReg, + }, + ], + eoa_scripts: [ + { + type_hash: + "0x1272c80507fe5e6cf33cf3e5da6a5f02430de40abb14410ea0459361bf74ebe0", + script, + eoa_type: EoaScriptType.Eth, + }, + ], + gw_scripts: [ + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.Deposit, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.Withdraw, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.StateValidator, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.StakeLock, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.CustodianLock, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.ChallengeLock, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.L1Sudt, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.L2Sudt, + }, + { + type_hash: + "0xcddb997266a74a5e940a240d63ef8aa89d116999044e421dc337ead16ea870eb", + script, + script_type: GwScriptType.OmniLock, + }, + ], + rollup_cell: { + type_hash: + "0x4ed4a999f0046230d67503c07f1e64f2b2ad1440f758ebfc97282be40f74673c", + type_script: script, + }, + rollup_config: { + required_staking_capacity: "0x2540be400", + challenge_maturity_blocks: "0x64", + finality_blocks: "0x3", + reward_burn_rate: "0x32", + chain_id: "0x116e8", + }, + version: "v1.1.0", + mode: NodeMode.FullNode, + }; + return nodeInfo; +}; + +test("init gw config", async (t) => { + const config = await gwConfig.init(); + t.deepEqual(config.eoaScripts, { + eth: { + script, + typeHash: + "0x1272c80507fe5e6cf33cf3e5da6a5f02430de40abb14410ea0459361bf74ebe0", + }, + }); + t.is(config.accounts.polyjuiceCreator.id, "0x4"); + t.is(config.accounts.defaultFrom.id, "0x3"); + t.is(config.accounts.ethAddrReg.id, "0x2"); + t.is(config.nodeMode, NodeMode.FullNode); + t.is(config.web3ChainId, "0x116e8"); +}); diff --git a/packages/api-server/tests/utils/convention.test.ts b/packages/api-server/tests/utils/convention.test.ts new file mode 100644 index 00000000..f63da3b3 --- /dev/null +++ b/packages/api-server/tests/utils/convention.test.ts @@ -0,0 +1,130 @@ +import { snakeToCamel, camelToSnake } from "../../src/util"; +import test from "ava"; + +test("snakeToCamel", (t) => { + const obj = { + hello_world: "hello world", + hello_earth: { + hello_human: { + bob_person: { + alias_name: "bob", + age_now: 34, + }, + }, + hello_cat: { + bob_cat: { + alias_name: "bob", + age_now: 2, + }, + }, + hello_array: [{ first_array: 1 }], + }, + }; + const expectObj = { + helloWorld: "hello world", + helloEarth: { + helloHuman: { + bobPerson: { + aliasName: "bob", + ageNow: 34, + }, + }, + helloCat: { + bobCat: { + aliasName: "bob", + ageNow: 2, + }, + }, + helloArray: [{ firstArray: 1 }], + }, + }; + t.deepEqual(snakeToCamel(obj), expectObj); +}); + +test("camelToSnake", (t) => { + const expectObj = { + hello_world: "hello world", + hello_earth: { + hello_human: { + bob_person: { + alias_name: "bob", + age_now: 34, + }, + }, + hello_cat: { + bob_cat: { + alias_name: "bob", + age_now: 2, + }, + }, + hello_array: [{ first_array: 1 }], + }, + }; + const obj = { + helloWorld: "hello world", + helloEarth: { + helloHuman: { + bobPerson: { + aliasName: "bob", + ageNow: 34, + }, + }, + helloCat: { + bobCat: { + aliasName: "bob", + ageNow: 2, + }, + }, + helloArray: [{ firstArray: 1 }], + }, + }; + t.deepEqual(camelToSnake(obj), expectObj); +}); + +test("overflow", (t) => { + const overflowDepthObj = { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: ["what a small world!"], + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const depthObj = { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: { + hello_world: ["what a small world!"], + }, + }, + }, + }, + }, + }, + }, + }; + + t.throws(() => snakeToCamel(overflowDepthObj), { + instanceOf: Error, + message: "[snakeToCamel] recursive depth reached max limit.", + }); + t.notThrows(() => snakeToCamel(depthObj)); +}); diff --git a/packages/godwoken/src/client.ts b/packages/godwoken/src/client.ts index d2b702c6..b625ad1d 100644 --- a/packages/godwoken/src/client.ts +++ b/packages/godwoken/src/client.ts @@ -6,6 +6,7 @@ import { L2Transaction, L2TransactionReceipt, L2TransactionWithStatus, + NodeInfo, RawL2Transaction, RegistryAddress, RunResult, @@ -168,7 +169,7 @@ export class GodwokenClient { return await this.rpcCall("get_transaction_receipt", hash); } - public async getNodeInfo() { + public async getNodeInfo(): Promise { return await this.rpcCall("get_node_info"); } diff --git a/packages/godwoken/src/types.ts b/packages/godwoken/src/types.ts index 301b0bfa..a115e6f5 100644 --- a/packages/godwoken/src/types.ts +++ b/packages/godwoken/src/types.ts @@ -1,4 +1,4 @@ -import { Hash, HexNumber, HexString } from "@ckb-lumos/base"; +import { Hash, HexNumber, HexString, Script } from "@ckb-lumos/base"; export type U32 = number; export type U64 = bigint; @@ -56,6 +56,74 @@ export interface AccountMerkleState { count: HexU32; } +export enum NodeMode { + FullNode = "fullnode", + ReadOnly = "readonly", + Test = "test", +} + +export enum EoaScriptType { + Eth = "eth", +} + +export interface EoaScript { + type_hash: HexString; + script: Script; + eoa_type: EoaScriptType; +} + +export enum BackendType { + Unknown = "unknown", + Meta = "meta", + Sudt = "sudt", + Polyjuice = "polyjuice", + EthAddrReg = "eth_addr_reg", +} +export interface BackendInfo { + validator_code_hash: HexString; + generator_code_hash: HexString; + validator_script_type_hash: HexString; + backend_type: BackendType; +} + +export enum GwScriptType { + Deposit = "deposit", + Withdraw = "withdraw", + StateValidator = "state_validator", + StakeLock = "stake_lock", + CustodianLock = "custodian_lock", + ChallengeLock = "challenge_lock", + L1Sudt = "l1_sudt", + L2Sudt = "l2_sudt", + OmniLock = "omni_lock", +} +export interface GwScript { + type_hash: HexString; + script: Script; + script_type: GwScriptType; +} + +export interface RollupCell { + type_hash: HexString; + type_script: Script; +} + +export interface RollupConfig { + required_staking_capacity: HexNumber; + challenge_maturity_blocks: HexNumber; + finality_blocks: HexNumber; + reward_burn_rate: HexNumber; + chain_id: HexNumber; +} +export interface NodeInfo { + backends: Array; + eoa_scripts: Array; + gw_scripts: Array; + rollup_cell: RollupCell; + rollup_config: RollupConfig; + version: string; + mode: NodeMode; +} export interface RegistryAddress { registry_id: HexU32; address: HexString; diff --git a/scripts/generate-indexer-config.js b/scripts/generate-indexer-config.js index 632d8a36..2c85d97c 100644 --- a/scripts/generate-indexer-config.js +++ b/scripts/generate-indexer-config.js @@ -1,41 +1,101 @@ const dotenv = require("dotenv"); -const path = require("path") -const fs = require("fs") +const path = require("path"); +const fs = require("fs"); +const http = require("http"); +const crypto = require("crypto"); -const envPath = path.join(__dirname, "../packages/api-server/.env") +const envPath = path.join(__dirname, "../packages/api-server/.env"); -dotenv.config({path: envPath}) +dotenv.config({ path: envPath }); -const wsRpcUrl = process.argv[2]; +function sendJsonRpc(rpcUrl, method, params) { + const url = new URL(rpcUrl); -let config = { - l2_sudt_type_script_hash: process.env.L2_SUDT_VALIDATOR_SCRIPT_TYPE_HASH, - polyjuice_type_script_hash: process.env.POLYJUICE_VALIDATOR_TYPE_HASH, - rollup_type_hash: process.env.ROLLUP_TYPE_HASH, - eth_account_lock_hash: process.env.ETH_ACCOUNT_LOCK_HASH, - godwoken_rpc_url: process.env.GODWOKEN_JSON_RPC, - pg_url: process.env.DATABASE_URL, - chain_id: process.env.CHAIN_ID, - sentry_dsn: process.env.SENTRY_DNS, - sentry_environment: process.env.SENTRY_ENVIRONMENT, -} + return new Promise((resolve, reject) => { + const data = JSON.stringify({ + id: crypto.randomUUID(), + jsonrpc: "2.0", + method: method, + params: params, + }); + + const options = { + hostname: url.hostname, + port: url.port, + method: "POST", + headers: { + "Content-Type": "application/json", + "Content-Length": data.length, + }, + }; + + const req = http.request(options, (res) => { + res.on("data", (d) => { + const res = JSON.parse(d.toString()); + if (res.error) { + reject(res); + } else { + resolve(res.result); + } + }); + }); -if (wsRpcUrl) { - config.ws_rpc_url = wsRpcUrl; + req.on("error", (error) => { + console.error(error); + }); + + req.write(data); + req.end(); + }); } -let tomlStr = ""; +const run = async () => { + const nodeInfo = await sendJsonRpc( + process.env.GODWOKEN_JSON_RPC, + "gw_get_node_info", + [] + ); + + const wsRpcUrl = process.argv[2]; + + let config = { + l2_sudt_type_script_hash: nodeInfo.gw_scripts.find( + (s) => s.script_type === "l2_sudt" + ).type_hash, + polyjuice_type_script_hash: nodeInfo.backends.find( + (s) => s.backend_type === "polyjuice" + ).validator_script_type_hash, + rollup_type_hash: nodeInfo.rollup_cell.type_hash, + eth_account_lock_hash: nodeInfo.eoa_scripts.find( + (s) => s.eoa_type === "eth" + ).type_hash, + chain_id: +nodeInfo.rollup_config.chain_id, -for (const [key, value] of Object.entries(config)) { - console.log(`[${key}]: ${value}`) - if(value != null && key === "chain_id"){ - tomlStr += `${key}=${Number(value)}\n`; - continue; + godwoken_rpc_url: process.env.GODWOKEN_JSON_RPC, + pg_url: process.env.DATABASE_URL, + sentry_dsn: process.env.SENTRY_DNS, + sentry_environment: process.env.SENTRY_ENVIRONMENT, + }; + + if (wsRpcUrl) { + config.ws_rpc_url = wsRpcUrl; } - if (value != null) { - tomlStr += `${key}="${value}"\n`; + + let tomlStr = ""; + + for (const [key, value] of Object.entries(config)) { + console.log(`[${key}]: ${value}`); + if (value != null && key === "chain_id") { + tomlStr += `${key}=${Number(value)}\n`; + continue; + } + if (value != null) { + tomlStr += `${key}="${value}"\n`; + } } -} -const outputPath = path.join(__dirname, "../indexer-config.toml"); -fs.writeFileSync(outputPath, tomlStr); + const outputPath = path.join(__dirname, "../indexer-config.toml"); + fs.writeFileSync(outputPath, tomlStr); +}; + +run();