diff --git a/nimbus/graphql/ethapi.nim b/nimbus/graphql/ethapi.nim index 7c2c9b8ee..afb2216fd 100644 --- a/nimbus/graphql/ethapi.nim +++ b/nimbus/graphql/ethapi.nim @@ -49,6 +49,7 @@ type blockNumber: BlockNumber receipt: Receipt gasUsed: GasInt + baseFee: Option[UInt256] LogNode = ref object of Node log: Log @@ -88,14 +89,15 @@ proc accountNode(ctx: GraphqlContextRef, acc: Account, address: EthAddress, db: db: db ) -proc txNode(ctx: GraphqlContextRef, tx: Transaction, index: int, blockNumber: BlockNumber): Node = +proc txNode(ctx: GraphqlContextRef, tx: Transaction, index: int, blockNumber: BlockNumber, baseFee: Option[UInt256]): Node = TxNode( kind: nkMap, typeName: ctx.ids[ethTransaction], pos: Pos(), tx: tx, index: index, - blockNumber: blockNumber + blockNumber: blockNumber, + baseFee: baseFee ) proc logNode(ctx: GraphqlContextRef, log: Log, index: int, tx: TxNode): Node = @@ -254,7 +256,7 @@ proc getTxs(ctx: GraphqlContextRef, header: BlockHeader): RespResult = var index = 0 for n in getBlockTransactionData(ctx.chainDB, header.txRoot): let tx = rlp.decode(n, Transaction) - list.add txNode(ctx, tx, index, header.blockNumber) + list.add txNode(ctx, tx, index, header.blockNumber, header.fee) inc index index = 0 @@ -276,7 +278,7 @@ proc getTxAt(ctx: GraphqlContextRef, header: BlockHeader, index: int): RespResul try: var tx: Transaction if getTransaction(ctx.chainDB, header.txRoot, index, tx): - let txn = txNode(ctx, tx, index, header.blockNumber) + let txn = txNode(ctx, tx, index, header.blockNumber, header.fee) var i = 0 var prevUsed = 0.GasInt @@ -595,9 +597,46 @@ proc txValue(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} bigIntNode(tx.tx.value) proc txGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = - let ctx = GraphqlContextRef(ud) let tx = TxNode(parent) - bigIntNode(tx.tx.gasPrice) + if tx.tx.txType == TxEip1559: + if tx.baseFee.isNone: + return bigIntNode(tx.tx.gasPrice) + + let baseFee = tx.baseFee.get().truncate(GasInt) + let priorityFee = min(tx.tx.maxPriorityFee, tx.tx.maxFee - baseFee) + bigIntNode(priorityFee + baseFee) + else: + bigIntNode(tx.tx.gasPrice) + +proc txMaxFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = + let tx = TxNode(parent) + if tx.tx.txType == TxEip1559: + bigIntNode(tx.tx.maxFee) + else: + ok(respNull()) + +proc txMaxPriorityFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = + let tx = TxNode(parent) + if tx.tx.txType == TxEip1559: + bigIntNode(tx.tx.maxPriorityFee) + else: + ok(respNull()) + +proc txEffectiveGasPrice(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = + let tx = TxNode(parent) + if tx.baseFee.isNone: + return bigIntNode(tx.tx.gasPrice) + + let baseFee = tx.baseFee.get().truncate(GasInt) + let priorityFee = min(tx.tx.maxPriorityFee, tx.tx.maxFee - baseFee) + bigIntNode(priorityFee + baseFee) + +proc txChainId(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = + let tx = TxNode(parent) + if tx.tx.txType == TxLegacy: + ok(respNull()) + else: + longNode(tx.tx.chainId.uint64) proc txGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = let ctx = GraphqlContextRef(ud) @@ -706,7 +745,11 @@ const txProcs = { "s": txS, "v": txV, "type": txType, - "accessList": txAccessList + "accessList": txAccessList, + "maxFeePerGas": txMaxFeePerGas, + "maxPriorityFeePerGas": txMaxPriorityFeePerGas, + "effectiveGasPrice": txEffectiveGasPrice, + "chainID": txChainId } proc aclAddress(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = @@ -919,6 +962,13 @@ proc blockEstimateGas(ud: RootRef, params: Args, parent: Node): RespResult {.api except Exception as em: err("estimateGas error: " & em.msg) +proc blockBaseFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = + let h = HeaderNode(parent) + if h.header.fee.isSome: + bigIntNode(h.header.fee.get) + else: + ok(respNull()) + const blockProcs = { "parent": blockParent, "number": blockNumberImpl, @@ -946,7 +996,8 @@ const blockProcs = { "logs": blockLogs, "account": blockAccount, "call": blockCall, - "estimateGas": blockEstimateGas + "estimateGas": blockEstimateGas, + "baseFeePerGas": blockBaseFeePerGas } proc callResultData(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = @@ -1118,6 +1169,14 @@ proc querySyncing(ud: RootRef, params: Args, parent: Node): RespResult {.apiPrag let ctx = GraphqlContextRef(ud) ok(respMap(ctx.ids[ethSyncState])) +proc queryMaxPriorityFeePerGas(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = + # TODO: stub, missing impl + err("not implemented") + +proc queryChainId(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = + let ctx = GraphqlContextRef(ud) + longNode(ctx.chainDB.config.chainId.uint64) + const queryProcs = { "account": queryAccount, "block": queryBlock, @@ -1127,7 +1186,9 @@ const queryProcs = { "logs": queryLogs, "gasPrice": queryGasPrice, "protocolVersion": queryProtocolVersion, - "syncing": querySyncing + "syncing": querySyncing, + "maxPriorityFeePerGas": queryMaxPriorityFeePerGas, + "chainID": queryChainId } proc sendRawTransaction(ud: RootRef, params: Args, parent: Node): RespResult {.apiPragma.} = diff --git a/nimbus/graphql/ethapi.ql b/nimbus/graphql/ethapi.ql index 082951e31..0e68f910c 100644 --- a/nimbus/graphql/ethapi.ql +++ b/nimbus/graphql/ethapi.ql @@ -66,7 +66,7 @@ type Log { type AccessTuple { # access list address address: Address! - + # access list storage keys, null if not present storageKeys: [Bytes32!] } @@ -97,6 +97,12 @@ type Transaction { # GasPrice is the price offered to miners for gas, in wei per unit. gasPrice: BigInt! + # MaxFeePerGas is the maximum fee per gas offered to include a transaction, in wei. + maxFeePerGas: BigInt + + # MaxPriorityFeePerGas is the maximum miner tip per gas offered to include a transaction, in wei. + maxPriorityFeePerGas: BigInt + # Gas is the maximum amount of gas this transaction can consume. gas: Long! @@ -124,6 +130,14 @@ type Transaction { # will be null. cumulativeGasUsed: Long + # EffectiveGasPrice is actual value per gas deducted from the sender's + # account. Before EIP-1559, this is equal to the transaction's gas price. + # After EIP-1559, it is baseFeePerGas + min(maxFeePerGas - baseFeePerGas, + # maxPriorityFeePerGas). Legacy transactions and EIP-2930 transactions are + # coerced into the EIP-1559 format by setting both maxFeePerGas and + # maxPriorityFeePerGas as the transaction's gas price. + effectiveGasPrice: BigInt + # CreatedContract is the account that was created by a contract creation # transaction. If the transaction was not a contract creation transaction, # or it has not yet been mined, this field will be null. @@ -147,6 +161,10 @@ type Transaction { # EIP 2930: optional access list, null if not present accessList: [AccessTuple!] + + # If type == 0, chainID returns null. + # If type > 0, chainID returns replay protection chainID + chainID: Long } # BlockFilterCriteria encapsulates log filter criteria for a filter applied @@ -209,6 +227,9 @@ type Block { # GasUsed is the amount of gas that was used executing transactions in this block. gasUsed: Long! + # BaseFeePerGas is the fee perunit of gas burned by the protocol in this block. + baseFeePerGas: BigInt + # Timestamp is the unix timestamp at which this block was mined. timestamp: BigInt! @@ -282,6 +303,12 @@ input CallData { # GasPrice is the price, in wei, offered for each unit of gas. gasPrice: BigInt + # MaxFeePerGas is the maximum fee per gas offered, in wei. + maxFeePerGas: BigInt + + # MaxPriorityFeePerGas is the maximum miner tip per gas offered, in wei. + maxPriorityFeePerGas: BigInt + # Value is the value, in wei, sent along with the call. value: BigInt @@ -394,11 +421,18 @@ type Query { # ensure a transaction is mined in a timely fashion. gasPrice: BigInt! + # MaxPriorityFeePerGas returns the node's estimate of a gas tip sufficient + # to ensure a transaction is mined in a timely fashion. + maxPriorityFeePerGas: BigInt! + # ProtocolVersion returns the current wire protocol version number. protocolVersion: Int! # Syncing returns information on the current synchronisation state. syncing: SyncState + + # ChainID returns the current chain ID for transaction replay protection. + chainID: Long! } type Mutation { diff --git a/tests/graphql/queries.toml b/tests/graphql/queries.toml index 8e3345542..5b55170f3 100644 --- a/tests/graphql/queries.toml +++ b/tests/graphql/queries.toml @@ -45,6 +45,7 @@ name = "query.block(number)" code = """ { + chainID block(number: 3) { __typename number @@ -68,6 +69,7 @@ } extraData gasLimit + baseFeePerGas gasUsed timestamp logsBloom @@ -104,6 +106,7 @@ """ result = """ { + "chainID":1, "block":{ "__typename":"Block", "number":3, @@ -127,6 +130,7 @@ }, "extraData":"0x42", "gasLimit":3141592, + "baseFeePerGas":null, "gasUsed":21000, "timestamp":"0x54c99839", "logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -203,6 +207,11 @@ address storageKeys } + + maxFeePerGas + maxPriorityFeePerGas + effectiveGasPrice + chainID } transactionAt(index: 0) { __typename @@ -244,7 +253,11 @@ "s":"0x773806df18e22db29acde1dd96c0418e28738af7f520e5e2c5c673494029e5", "v":"0x1b", "type":0, - "accessList":null + "accessList":null, + "maxFeePerGas":null, + "maxPriorityFeePerGas":null, + "effectiveGasPrice":"0x3e8", + "chainID":null } ], "transactionAt":{