diff --git a/.gitignore b/.gitignore index 3994f3e8..cc65e1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ lib/ packages/api-server/allowed-addresses.json crates/**/target target -indexer-config.toml \ No newline at end of file +indexer-config.toml + +*.env +*.cpuprofile diff --git a/README.md b/README.md index b15839b0..334d60fb 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ ENABLE_CACHE_POLY_EXECUTE_RAW_L2Tx= LOG_FORMAT= MAX_SOCKETS= +MIN_GAS_PRICE= EOF $ yarn diff --git a/packages/api-server/src/base/env-config.ts b/packages/api-server/src/base/env-config.ts index 7e11ccb5..97a961e0 100644 --- a/packages/api-server/src/base/env-config.ts +++ b/packages/api-server/src/base/env-config.ts @@ -30,6 +30,7 @@ export const envConfig = { ), logLevel: getOptional("LOG_LEVEL"), logFormat: getOptional("LOG_FORMAT"), + minGasPrice: getOptional("MIN_GAS_PRICE"), }; function getRequired(name: string): string { diff --git a/packages/api-server/src/convert-tx.ts b/packages/api-server/src/convert-tx.ts index 74e315ea..7d4c1585 100644 --- a/packages/api-server/src/convert-tx.ts +++ b/packages/api-server/src/convert-tx.ts @@ -4,6 +4,7 @@ import { rlp } from "ethereumjs-util"; import keccak256 from "keccak256"; import * as secp256k1 from "secp256k1"; import { logger } from "./base/logger"; +import { verifyGasPrice } from "./methods/validator"; export const EMPTY_ETH_ADDRESS = "0x" + "00".repeat(20); @@ -128,6 +129,8 @@ function encodePolyjuiceTransaction(tx: PolyjuiceTransaction) { async function parseRawTransactionData(rawTx: PolyjuiceTransaction, rpc: RPC) { const { nonce, gasPrice, gasLimit, to: toA, value, data, v, r, s } = rawTx; + verifyGasPrice(gasPrice === "0x" ? "0x0" : gasPrice, 0); + let real_v = "0x00"; if (+v % 2 === 0) { real_v = "0x01"; diff --git a/packages/api-server/src/methods/constant.ts b/packages/api-server/src/methods/constant.ts index 647de490..6dd0ea7c 100644 --- a/packages/api-server/src/methods/constant.ts +++ b/packages/api-server/src/methods/constant.ts @@ -1,7 +1,6 @@ // source: https://github.com/nervosnetwork/godwoken-polyjuice/blob/main/docs/EVM-compatible.md export const POLY_MAX_BLOCK_GAS_LIMIT = 12500000; export const POLY_MAX_TRANSACTION_GAS_LIMIT = 12500000; -export const POLY_MIN_GAS_PRICE = 0; export const POLY_BLOCK_DIFFICULTY = BigInt("2500000000000000"); export const DEFAULT_EMPTY_ETH_ADDRESS = `0x${"0".repeat(40)}`; diff --git a/packages/api-server/src/methods/modules/eth.ts b/packages/api-server/src/methods/modules/eth.ts index ff69fcd3..de743ab6 100644 --- a/packages/api-server/src/methods/modules/eth.ts +++ b/packages/api-server/src/methods/modules/eth.ts @@ -306,8 +306,7 @@ export class Eth { } let medianGasPrice = await this.query.getMedianGasPrice(); - // set min to 1 - const minGasPrice = BigInt(1); + const minGasPrice = BigInt(envConfig.minGasPrice || 0); if (medianGasPrice < minGasPrice) { medianGasPrice = minGasPrice; } @@ -1375,7 +1374,9 @@ async function buildEthCallTx( const fromAddress = txCallObj.from || envConfig.defaultFromAddress; const toAddress = txCallObj.to || "0x" + "00".repeat(20); const gas = txCallObj.gas || "0x1000000"; - const gasPrice = txCallObj.gasPrice || "0x1"; + const gasPrice = + txCallObj.gasPrice || + "0x" + BigInt(envConfig.minGasPrice || 0).toString(16); const value = txCallObj.value || "0x0"; const data = txCallObj.data || "0x0"; let fromId: number | undefined; diff --git a/packages/api-server/src/methods/modules/gw.ts b/packages/api-server/src/methods/modules/gw.ts index edc03d0c..f41321b1 100644 --- a/packages/api-server/src/methods/modules/gw.ts +++ b/packages/api-server/src/methods/modules/gw.ts @@ -1,11 +1,17 @@ import { parseGwRpcError } from "../gw-error"; import { RPC } from "@godwoken-web3/godwoken"; -import { middleware } from "../validator"; +import { middleware, verifyGasPrice } from "../validator"; import { HexNumber } from "@ckb-lumos/base"; import { Store } from "../../cache/store"; import { envConfig } from "../../base/env-config"; import { CACHE_EXPIRED_TIME_MILSECS, GW_RPC_KEY } from "../../cache/constant"; import { logger } from "../../base/logger"; +import { + L2Transaction, + RawL2Transaction, +} from "@godwoken-web3/godwoken/schemas"; +import { Reader } from "@ckb-lumos/toolkit"; +import { decodeArgs } from "@polyjuice-provider/base"; export class Gw { private rpc: RPC; @@ -305,6 +311,14 @@ export class Gw { */ async execute_raw_l2transaction(args: any[]) { try { + // validate minimal gas price + const serializedRawL2Tx = args[0]; + const rawL2Tx = new RawL2Transaction(new Reader(serializedRawL2Tx)); + const { gas_price } = decodeArgs( + new Reader(rawL2Tx.getArgs().raw()).serializeJson() + ); + verifyGasPrice(gas_price === "0x" ? "0x0" : gas_price, 0); + args[1] = formatHexNumber(args[1]); const result = await this.readonlyRpc.gw_execute_raw_l2transaction( @@ -323,6 +337,14 @@ export class Gw { */ async submit_l2transaction(args: any[]) { try { + // validate minimal gas price + const l2Tx = new L2Transaction(new Reader(args[0])); + const polyArgs = new Reader( + l2Tx.getRaw().getArgs().raw() + ).serializeJson(); + const { gas_price } = decodeArgs(polyArgs); + verifyGasPrice(gas_price === "0x" ? "0x0" : gas_price, 0); + const result = await this.rpc.gw_submit_l2transaction(...args); return result; } catch (error) { diff --git a/packages/api-server/src/methods/modules/poly.ts b/packages/api-server/src/methods/modules/poly.ts index a4c05940..b3cd1920 100644 --- a/packages/api-server/src/methods/modules/poly.ts +++ b/packages/api-server/src/methods/modules/poly.ts @@ -1,4 +1,4 @@ -import { middleware, validators } from "../validator"; +import { middleware, validators, verifyGasPrice } from "../validator"; import { Hash, HexNumber, Address, HexString } from "@ckb-lumos/base"; import { toHexNumber, Uint64 } from "../../base/types/uint"; import { envConfig } from "../../base/env-config"; @@ -86,6 +86,11 @@ export class Poly { const txWithAddressMapping: L2TransactionWithAddressMapping = deserializeL2TransactionWithAddressMapping(data); const l2Tx = txWithAddressMapping.tx; + + // validate minimal gas price + const { gas_price } = decodeArgs(l2Tx.raw.args); + verifyGasPrice(gas_price === "0x" ? "0x0" : gas_price, 0); + 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); @@ -145,6 +150,11 @@ export class Poly { const txWithAddressMapping: RawL2TransactionWithAddressMapping = deserializeRawL2TransactionWithAddressMapping(serializeRawL2Tx); const rawL2Tx = txWithAddressMapping.raw_tx; + + // validate minimal gas price + const { gas_price } = decodeArgs(rawL2Tx.args); + verifyGasPrice(gas_price === "0x" ? "0x0" : gas_price, 0); + const jsonResult = await this.rpc.executeRawL2Transaction( rawL2Tx, blockNumber diff --git a/packages/api-server/src/methods/validator.ts b/packages/api-server/src/methods/validator.ts index 8d6606ba..ad74f51b 100644 --- a/packages/api-server/src/methods/validator.ts +++ b/packages/api-server/src/methods/validator.ts @@ -2,6 +2,8 @@ import { validateHexNumber, validateHexString } from "../util"; import { BlockParameter } from "./types"; import { logger } from "../base/logger"; import { InvalidParamsError, RpcError } from "./error"; +import { HexString } from "@ckb-lumos/base"; +import { envConfig } from "../base/env-config"; /** * middleware for parameters validation @@ -374,3 +376,15 @@ function validateAddress(address: string): boolean { function invalidParamsError(index: number, message: string): void { throw new InvalidParamsError(`invalid argument ${index}: ${message}`); } + +export function verifyGasPrice(gasPrice: HexString, index: number) { + verifyHexNumber(gasPrice, index); + + if (envConfig.minGasPrice != null && +gasPrice < +envConfig.minGasPrice) { + return invalidParamsError( + index, + `minimal gas price ${+envConfig.minGasPrice} required. got ${+gasPrice}` + ); + } + return undefined; +}