From 3cbb920406b76ae284357f6cb4c9be927a920b88 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Mon, 24 Jan 2022 15:08:33 +0200 Subject: [PATCH] Migrate to Engine API spec version v1.0.0-alpha.5; More progress towards working M1 --- config.nims | 3 + nimbus/config.nim | 18 ++++++ nimbus/nimbus.nim | 36 ++++++++--- nimbus/p2p/chain/chain_desc.nim | 5 +- nimbus/p2p/chain/persist_blocks.nim | 5 +- nimbus/p2p/validate.nim | 2 +- nimbus/rpc/engine_api.nim | 66 ++++++++++++++------ nimbus/rpc/hexstrings.nim | 16 ++++- nimbus/rpc/p2p.nim | 8 ++- nimbus/rpc/rpc_types.nim | 9 ++- nimbus/rpc/rpc_utils.nim | 4 +- nimbus/sealer.nim | 73 +++++++++++++---------- tests/amphora/check-merge-test-vectors.sh | 14 ++--- tests/amphora/launch-nimbus.sh | 1 + tests/test_rpc.nim | 2 +- vendor/nim-web3 | 2 +- 16 files changed, 189 insertions(+), 75 deletions(-) diff --git a/config.nims b/config.nims index 392624a7e..d771ec39c 100644 --- a/config.nims +++ b/config.nims @@ -62,6 +62,9 @@ if not defined(macosx): # light-weight stack traces using libbacktrace and libunwind --define:nimStackTraceOverride switch("import", "libbacktrace") + else: + --stacktrace:on + --linetrace:on --define:nimOldCaseObjects # https://github.com/status-im/nim-confutils/issues/9 # libnimbus.so needs position-independent code diff --git a/nimbus/config.nim b/nimbus/config.nim index a267f441c..10e5bcd87 100644 --- a/nimbus/config.nim +++ b/nimbus/config.nim @@ -103,6 +103,7 @@ const defaultEthWsPort = 8546 defaultEthGraphqlPort = 8547 defaultEngineApiPort = 8550 + defaultEngineApiWsPort = 8551 defaultListenAddress = (static ValidIpAddress.init("0.0.0.0")) defaultAdminListenAddress = (static ValidIpAddress.init("127.0.0.1")) defaultListenAddressDesc = $defaultListenAddress & ", meaning all network interfaces" @@ -328,6 +329,23 @@ type defaultValueDesc: defaultAdminListenAddressDesc name: "engine-api-address" .}: ValidIpAddress + engineApiWsEnabled* {. + desc: "Enable the WebSocket Engine API" + defaultValue: false + name: "engine-api-ws" .}: bool + + engineApiWsPort* {. + desc: "Listening port for the WebSocket Engine API" + defaultValue: defaultEngineApiWsPort + defaultValueDesc: $defaultEngineApiWsPort + name: "engine-api-ws-port" .}: Port + + engineApiWsAddress* {. + desc: "Listening address for the WebSocket Engine API" + defaultValue: defaultAdminListenAddress + defaultValueDesc: defaultAdminListenAddressDesc + name: "engine-api-ws-address" .}: ValidIpAddress + nodeKeyHex* {. desc: "P2P node private key (as 32 bytes hex string)" defaultValue: "" diff --git a/nimbus/nimbus.nim b/nimbus/nimbus.nim index 7c8459b9f..4650dcab7 100644 --- a/nimbus/nimbus.nim +++ b/nimbus/nimbus.nim @@ -40,6 +40,7 @@ type NimbusNode = ref object rpcServer: RpcHttpServer engineApiServer: RpcHttpServer + engineApiWsServer: RpcWebSocketServer ethNode: EthereumNode state: NimbusState graphqlServer: GraphqlHttpServerRef @@ -157,7 +158,8 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf, # Enable RPC APIs based on RPC flags and protocol flags let rpcFlags = conf.getRpcFlags() - if RpcFlag.Eth in rpcFlags and ProtocolFlag.Eth in protocols: + if (RpcFlag.Eth in rpcFlags and ProtocolFlag.Eth in protocols) or + (conf.engineApiPort == conf.rpcPort): setupEthRpc(nimbus.ethNode, nimbus.ctx, chainDB, nimbus.txPool, nimbus.rpcServer) if RpcFlag.Debug in rpcFlags: setupDebugRpc(chainDB, nimbus.rpcServer) @@ -176,7 +178,8 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf, # Enable Websocket RPC APIs based on RPC flags and protocol flags let wsFlags = conf.getWsFlags() - if RpcFlag.Eth in wsFlags and ProtocolFlag.Eth in protocols: + if (RpcFlag.Eth in wsFlags and ProtocolFlag.Eth in protocols) or + (conf.engineApiWsPort == conf.wsPort): setupEthRpc(nimbus.ethNode, nimbus.ctx, chainDB, nimbus.txPool, nimbus.wsRpcServer) if RpcFlag.Debug in wsFlags: setupDebugRpc(chainDB, nimbus.wsRpcServer) @@ -219,13 +222,30 @@ proc localServices(nimbus: NimbusNode, conf: NimbusConf, nimbus.sealingEngine.start() if conf.engineApiEnabled: - nimbus.engineApiServer = newRpcHttpServer([ - initTAddress(conf.engineApiAddress, conf.engineApiPort) - ]) - setupEngineAPI(nimbus.sealingEngine, nimbus.engineApiServer) - nimbus.engineAPiServer.start() + if conf.engineApiPort != conf.rpcPort: + nimbus.engineApiServer = newRpcHttpServer([ + initTAddress(conf.engineApiAddress, conf.engineApiPort) + ]) + setupEngineAPI(nimbus.sealingEngine, nimbus.engineApiServer) + setupEthRpc(nimbus.ethNode, nimbus.ctx, chainDB, nimbus.txPool, nimbus.engineApiServer) + nimbus.engineApiServer.start() + else: + setupEngineAPI(nimbus.sealingEngine, nimbus.rpcServer) + info "Starting engine API server", port = conf.engineApiPort + if conf.engineApiWsEnabled: + if conf.engineApiWsPort != conf.wsPort: + nimbus.engineApiWsServer = newRpcWebSocketServer( + initTAddress(conf.engineApiWsAddress, conf.engineApiWsPort)) + setupEngineAPI(nimbus.sealingEngine, nimbus.engineApiWsServer) + setupEthRpc(nimbus.ethNode, nimbus.ctx, chainDB, nimbus.txPool, nimbus.engineApiWsServer) + nimbus.engineApiWsServer.start() + else: + setupEngineAPI(nimbus.sealingEngine, nimbus.wsRpcServer) + + info "Starting WebSocket engine API server", port = conf.engineApiWsPort + # metrics server if conf.metricsEnabled: info "Starting metrics HTTP server", address = conf.metricsAddress, port = conf.metricsPort @@ -283,6 +303,8 @@ proc stop*(nimbus: NimbusNode, conf: NimbusConf) {.async, gcsafe.} = await nimbus.engineAPiServer.stop() if conf.wsEnabled: nimbus.wsRpcServer.stop() + if conf.engineApiWsEnabled: + nimbus.engineApiWsServer.stop() if conf.graphqlEnabled: await nimbus.graphqlServer.stop() if conf.engineSigner != ZERO_ADDRESS: diff --git a/nimbus/p2p/chain/chain_desc.nim b/nimbus/p2p/chain/chain_desc.nim index 1d2673bb8..53d3f3bd8 100644 --- a/nimbus/p2p/chain/chain_desc.nim +++ b/nimbus/p2p/chain/chain_desc.nim @@ -80,8 +80,9 @@ func toNextFork(n: BlockNumber): uint64 = else: result = n.truncate(uint64) -func isBlockAfterTtd*(c: Chain, blockNum: BlockNumber): bool = - c.ttdReachedAt.isSome and blockNum > c.ttdReachedAt.get +func isBlockAfterTtd*(c: Chain, blockHeader: BlockHeader): bool = + # TODO: This should be fork aware + c.ttdReachedAt.isSome and blockHeader.blockNumber > c.ttdReachedAt.get func getNextFork(c: ChainConfig, fork: ChainFork): uint64 = let next: array[ChainFork, uint64] = [ diff --git a/nimbus/p2p/chain/persist_blocks.nim b/nimbus/p2p/chain/persist_blocks.nim index a1617b37f..99c4bf074 100644 --- a/nimbus/p2p/chain/persist_blocks.nim +++ b/nimbus/p2p/chain/persist_blocks.nim @@ -74,7 +74,8 @@ proc persistBlocksImpl(c: Chain; headers: openarray[BlockHeader]; return validationResult if c.extraValidation and c.verifyFrom <= header.blockNumber: - if c.db.config.poaEngine: + let isBlockAfterTtd = c.isBlockAfterTtd(header) + if c.db.config.poaEngine and not isBlockAfterTtd: var parent = if 0 < i: @[headers[i-1]] else: @[] let rc = c.clique.cliqueVerify(header,parent) if rc.isOK: @@ -90,7 +91,7 @@ proc persistBlocksImpl(c: Chain; headers: openarray[BlockHeader]; header, body, checkSealOK = false, # TODO: how to checkseal from here - ttdReached = c.isBlockAfterTtd(header.blockNumber), + ttdReached = isBlockAfterTtd, pow = c.pow) if res.isErr: debug "block validation error", diff --git a/nimbus/p2p/validate.nim b/nimbus/p2p/validate.nim index da999e56a..3a203756a 100644 --- a/nimbus/p2p/validate.nim +++ b/nimbus/p2p/validate.nim @@ -34,7 +34,7 @@ export {.push raises: [Defect].} const - daoForkBlockExtraData = + daoForkBlockExtraData* = byteutils.hexToByteArray[13](DAOForkBlockExtra).toSeq # ------------------------------------------------------------------------------ diff --git a/nimbus/rpc/engine_api.nim b/nimbus/rpc/engine_api.nim index 0509ee565..8601274c6 100644 --- a/nimbus/rpc/engine_api.nim +++ b/nimbus/rpc/engine_api.nim @@ -40,7 +40,7 @@ proc calcRootHashRlp*(items: openArray[seq[byte]]): Hash256 = tr.put(rlp.encode(i), t) return tr.rootHash() -proc toBlockHeader(payload: ExecutionPayload): eth_types.BlockHeader = +proc toBlockHeader(payload: ExecutionPayloadV1): eth_types.BlockHeader = discard payload.random # TODO: What should this be used for? let transactions = seq[seq[byte]](payload.transactions) @@ -49,10 +49,10 @@ proc toBlockHeader(payload: ExecutionPayload): eth_types.BlockHeader = EthBlockHeader( parentHash : payload.parentHash.asEthHash, ommersHash : EMPTY_UNCLE_HASH, - coinbase : EthAddress payload.coinbase, + coinbase : EthAddress payload.feeRecipient, stateRoot : payload.stateRoot.asEthHash, txRoot : txRoot, - receiptRoot : payload.receiptRoot.asEthHash, + receiptRoot : payload.receiptsRoot.asEthHash, bloom : distinctBase(payload.logsBloom), difficulty : default(DifficultyInt), blockNumber : payload.blockNumber.distinctBase.u256, @@ -65,40 +65,53 @@ proc toBlockHeader(payload: ExecutionPayload): eth_types.BlockHeader = fee : some payload.baseFeePerGas ) -proc toBlockBody(payload: ExecutionPayload): BlockBody = +proc toBlockBody(payload: ExecutionPayloadV1): BlockBody = # TODO the transactions from the payload have to be converted here discard payload.transactions -proc setupEngineAPI*(sealingEngine: SealingEngineRef, server: RpcServer) = +proc setupEngineAPI*( + sealingEngine: SealingEngineRef, + server: RpcServer) = - var payloadsInstance = newClone(newSeq[ExecutionPayload]()) + var payloadsInstance = newClone(newSeq[ExecutionPayloadV1]()) template payloads: auto = payloadsInstance[] - server.rpc("engine_getPayload") do(payloadId: Quantity) -> ExecutionPayload: - if payloadId.uint64 > high(int).uint64 or - int(payloadId) >= payloads.len: - raise (ref InvalidRequest)(code: UNKNOWN_PAYLOAD, msg: "Unknown payload") + # https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.5/src/engine/specification.md#engine_getpayloadv1 + server.rpc("engine_getPayloadV1") do(payloadIdBytes: FixedBytes[8]) -> ExecutionPayloadV1: + let payloadId = uint64.fromBytesBE(distinctBase payloadIdBytes) + if payloadId > payloads.len.uint64: + raise (ref InvalidRequest)(code: engineApiUnknownPayload, msg: "Unknown payload") return payloads[int payloadId] - server.rpc("engine_executePayload") do(payload: ExecutionPayload) -> ExecutePayloadResponse: + # https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.5/src/engine/specification.md#engine_executepayloadv1 + server.rpc("engine_executePayloadV1") do(payload: ExecutionPayloadV1) -> ExecutePayloadResponse: # TODO if payload.transactions.len > 0: # Give us a break, a block with transcations? instructions to execute? # Nah, we are syncing! - return ExecutePayloadResponse(status: $PayloadExecutionStatus.syncing) + return ExecutePayloadResponse(status: PayloadExecutionStatus.syncing) + + # TODO check whether we are syncing + let headers = [payload.toBlockHeader] bodies = [payload.toBlockBody] if rlpHash(headers[0]) != payload.blockHash.asEthHash: - return ExecutePayloadResponse(status: $PayloadExecutionStatus.invalid) + return ExecutePayloadResponse(status: PayloadExecutionStatus.invalid, + validationError: some "payload root doesn't match its contents") if sealingEngine.chain.persistBlocks(headers, bodies) != ValidationResult.OK: - return ExecutePayloadResponse(status: $PayloadExecutionStatus.invalid) + # TODO Provide validationError and latestValidHash + return ExecutePayloadResponse(status: PayloadExecutionStatus.invalid) - return ExecutePayloadResponse(status: $PayloadExecutionStatus.valid) + return ExecutePayloadResponse(status: PayloadExecutionStatus.valid, + latestValidHash: some payload.blockHash) - server.rpc("engine_forkchoiceUpdated") do(update: ForkChoiceUpdate): + # https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.5/src/engine/specification.md#engine_forkchoiceupdatedv1 + server.rpc("engine_forkchoiceUpdatedV1") do( + update: ForkchoiceStateV1, + payloadAttributes: Option[PayloadAttributesV1]) -> ForkchoiceUpdatedResponse: let db = sealingEngine.chain.db newHead = update.headBlockHash.asEthHash @@ -107,5 +120,24 @@ proc setupEngineAPI*(sealingEngine: SealingEngineRef, server: RpcServer) = # histories that are no longer relevant discard update.finalizedBlockHash + # TODO Check whether we are syncing + if not db.setHead(newHead): - raise (ref InvalidRequest)(code: UNKNOWN_HEADER, msg: "Uknown head block hash") + return ForkchoiceUpdatedResponse(status: ForkchoiceUpdatedStatus.syncing) + + if payloadAttributes.isSome: + let payloadId = uint64 payloads.len + + var payload: ExecutionPayloadV1 + let generatePayloadRes = sealingEngine.generateExecutionPayload( + payloadAttributes.get, + payload) + if generatePayloadRes.isErr: + raise newException(CatchableError, generatePayloadRes.error) + + payloads.add payload + + return ForkchoiceUpdatedResponse(status: ForkchoiceUpdatedStatus.success, + payloadId: some payloadId.toBytesBE.PayloadID) + else: + return ForkchoiceUpdatedResponse(status: ForkchoiceUpdatedStatus.success) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index 47461729a..1a422646a 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -27,7 +27,9 @@ ]# import - stint, stew/byteutils, eth/[keys, rlp], eth/common/eth_types + stint, stew/byteutils, eth/[keys, rlp], + eth/common/eth_types, + json_serialization type HexQuantityStr* = distinct string @@ -300,3 +302,15 @@ proc fromJson*(n: JsonNode, argName: string, result: var Hash256) = proc fromJson*(n: JsonNode, argName: string, result: var JsonNode) = result = n + +proc writeValue*(writer: var JsonWriter, value: HexQuantityStr) = + writeValue(writer, string value) + +proc writeValue*(writer: var JsonWriter, value: HexDataStr) = + writeValue(writer, string value) + +proc writeValue*(writer: var JsonWriter, value: EthAddressStr) = + writeValue(writer, string value) + +proc writeValue*(writer: var JsonWriter, value: EthHashStr) = + writeValue(writer, string value) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 84ee596af..da73819da 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -8,8 +8,10 @@ # those terms. import - times, options, tables, + times, tables, json_rpc/rpcserver, hexstrings, stint, stew/byteutils, + json_serialization, web3/conversions, json_serialization/std/options, + eth/common/eth_types_json_serialization, eth/[common, keys, rlp, p2p], nimcrypto, ".."/[transaction, vm_state, constants, utils, context], ../db/[db_chain, state_db], @@ -297,7 +299,9 @@ proc setupEthRpc*(node: EthereumNode, ctx: EthContext, chain: BaseChainDB, txPoo hash = data.toHash if chain.getBlockHeader(hash, header): - result = some(populateBlockObject(header, chain, fullTransactions)) + result = some populateBlockObject(header, chain, fullTransactions) + else: + result = none BlockObject server.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> Option[BlockObject]: ## Returns information about a block by block number. diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index 025287789..f4e5a7a65 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -1,6 +1,11 @@ import hexstrings, options, eth/[common, keys, rlp], json +from + web3/ethtypes import FixedBytes + +export FixedBytes + #[ Notes: * Some of the types suppose 'null' when there is no appropriate value. @@ -49,7 +54,7 @@ type parentHash*: Hash256 # hash of the parent block. nonce*: Option[HexDataStr] # hash of the generated proof-of-work. null when its pending block. sha3Uncles*: Hash256 # SHA3 of the uncles data in the block. - logsBloom*: Option[BloomFilter] # the bloom filter for the logs of the block. null when its pending block. + logsBloom*: FixedBytes[256] # the bloom filter for the logs of the block. null when its pending block. transactionsRoot*: Hash256 # the root of the transaction trie of the block. stateRoot*: Hash256 # the root of the final state trie of the block. receiptsRoot*: Hash256 # the root of the receipts trie of the block. @@ -107,7 +112,7 @@ type gasUsed*: HexQuantityStr # the amount of gas used by this specific transaction alone. contractAddress*: Option[EthAddress] # the contract address created, if the transaction was a contract creation, otherwise null. logs*: seq[Log] # list of log objects which this transaction generated. - logsBloom*: BloomFilter # bloom filter for light clients to quickly retrieve related logs. + logsBloom*: FixedBytes[256] # bloom filter for light clients to quickly retrieve related logs. root*: Option[Hash256] # post-transaction stateroot (pre Byzantium). status*: Option[int] # 1 = success, 0 = failure. diff --git a/nimbus/rpc/rpc_utils.nim b/nimbus/rpc/rpc_utils.nim index 23607f228..5c3dc1230 100644 --- a/nimbus/rpc/rpc_utils.nim +++ b/nimbus/rpc/rpc_utils.nim @@ -137,7 +137,7 @@ proc populateBlockObject*(header: BlockHeader, chain: BaseChainDB, fullTx: bool, result.parentHash = header.parentHash result.nonce = some(hexDataStr(header.nonce)) result.sha3Uncles = header.ommersHash - result.logsBloom = some(header.bloom) + result.logsBloom = FixedBytes[256] header.bloom result.transactionsRoot = header.txRoot result.stateRoot = header.stateRoot result.receiptsRoot = header.receiptRoot @@ -183,7 +183,7 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction, txInde result.contractAddress = some(contractAddress) result.logs = receipt.logs - result.logsBloom = receipt.bloom + result.logsBloom = FixedBytes[256] receipt.bloom # post-transaction stateroot (pre Byzantium). if receipt.hasStateRoot: diff --git a/nimbus/sealer.nim b/nimbus/sealer.nim index 0cd48157e..b18bad330 100644 --- a/nimbus/sealer.nim +++ b/nimbus/sealer.nim @@ -16,12 +16,12 @@ import clique_desc, clique_cfg, clique_sealer], - ./p2p/gaslimit, + ./p2p/[gaslimit, validate], "."/[chain_config, utils, context], "."/utils/tx_pool from web3/ethtypes as web3types import nil -from web3/engine_api_types import ExecutionPayload, PayloadAttributes +from web3/engine_api_types import ExecutionPayloadV1, PayloadAttributesV1 type EngineState* = enum @@ -85,8 +85,7 @@ proc prepareHeader(engine: SealingEngineRef, parent.gasLimit, gasFloor = DEFAULT_GAS_LIMIT, gasCeil = DEFAULT_GAS_LIMIT), - # TODO: extraData can be configured via cli - #extraData : engine.extra, + extraData : daoForkBlockExtraData, coinbase : coinbase, timestamp : timestamp, ommersHash : EMPTY_UNCLE_HASH, @@ -106,30 +105,29 @@ proc prepareHeader(engine: SealingEngineRef, # TODO: desiredLimit can be configured by user, gasCeil header.gasLimit = calcGasLimit1559(parentGasLimit, desiredLimit = DEFAULT_GAS_LIMIT) - # TODO Post merge, clique should not be executing - let clique = engine.chain.clique - let res = clique.prepare(parent, header) - if res.isErr: - return err($res.error) - - if engine.chain.isBlockAfterTtd(header.blockNumber): + if engine.chain.isBlockAfterTtd(header): header.difficulty = DifficultyInt.zero + header.mixDigest = default(Hash256) + header.nonce = default(BlockNonce) + else: + let res = engine.chain.clique.prepare(parent, header) + if res.isErr: + return err($res.error) ok(header) proc generateBlock(engine: SealingEngineRef, coinbase: EthAddress, parentBlockHeader: BlockHeader, - outBlock: var EthBlock): Result[void, string] = + outBlock: var EthBlock, + timestamp = getTime()): Result[void, string] = # deviation from standard block generator # - no local and remote transactions inclusion(need tx pool) # - no receipts from tx # - no DAO hard fork # - no local and remote uncles inclusion - let clique = engine.chain.clique - let time = getTime() - let res = prepareHeader(engine, coinbase, parentBlockHeader, time) + let res = prepareHeader(engine, coinbase, parentBlockHeader, timestamp) if res.isErr: return err("error prepare header") @@ -137,10 +135,11 @@ proc generateBlock(engine: SealingEngineRef, header: res.get() ) - # TODO Post merge, Clique should not be executing - let sealRes = clique.seal(outBlock) - if sealRes.isErr: - return err("error sealing block header: " & $sealRes.error) + if not engine.chain.isBlockAfterTtd(outBlock.header): + # TODO Post merge, Clique should not be executing + let sealRes = engine.chain.clique.seal(outBlock) + if sealRes.isErr: + return err("error sealing block header: " & $sealRes.error) debug "generated block", blockNumber = outBlock.header.blockNumber, @@ -151,10 +150,11 @@ proc generateBlock(engine: SealingEngineRef, proc generateBlock(engine: SealingEngineRef, coinbase: EthAddress, parentHash: Hash256, - outBlock: var EthBlock): Result[void, string] = + outBlock: var EthBlock, + timestamp = getTime()): Result[void, string] = var parentBlockHeader: BlockHeader if engine.chain.db.getBlockHeader(parentHash, parentBlockHeader): - generateBlock(engine, coinbase, parentBlockHeader, outBlock) + generateBlock(engine, coinbase, parentBlockHeader, outBlock, timestamp) else: # TODO: # This hack shouldn't be necessary if the database can find @@ -167,8 +167,10 @@ proc generateBlock(engine: SealingEngineRef, proc generateBlock(engine: SealingEngineRef, coinbase: EthAddress, - outBlock: var EthBlock): Result[void, string] = - generateBlock(engine, coinbase, engine.chain.currentBlock(), outBlock) + outBlock: var EthBlock, + timestamp = getTime()): Result[void, string] = + generateBlock(engine, coinbase, engine.chain.currentBlock(), + outBlock, timestamp) proc sealingLoop(engine: SealingEngineRef): Future[void] {.async.} = let clique = engine.chain.clique @@ -217,13 +219,22 @@ proc sealingLoop(engine: SealingEngineRef): Future[void] {.async.} = info "block generated", number=blk.header.blockNumber +template unsafeQuantityToInt64(q: web3types.Quantity): int64 = + int64 q + proc generateExecutionPayload*(engine: SealingEngineRef, - payloadAttrs: PayloadAttributes, - payloadRes: var ExecutionPayload): Result[void, string] = + payloadAttrs: PayloadAttributesV1, + payloadRes: var ExecutionPayloadV1): Result[void, string] = + let headBlock = try: engine.chain.db.getCanonicalHead() + except CatchableError: return err "No head block in database" + var blk: EthBlock - let blkRes = engine.generateBlock(EthAddress payloadAttrs.feeRecipient, - payloadAttrs.parentHash.asEthHash, - blk) + let blkRes = engine.generateBlock( + EthAddress payloadAttrs.suggestedFeeRecipient, + headBlock, + blk, + fromUnix(payloadAttrs.timestamp.unsafeQuantityToInt64)) + if blkRes.isErr: error "sealing engine generateBlock error", msg = blkRes.error return blkRes @@ -233,10 +244,12 @@ proc generateExecutionPayload*(engine: SealingEngineRef, ]) payloadRes.parentHash = Web3BlockHash blk.header.parentHash.data - payloadRes.coinbase = Web3Address blk.header.coinbase + payloadRes.feeRecipient = Web3Address blk.header.coinbase payloadRes.stateRoot = Web3BlockHash blk.header.stateRoot.data - payloadRes.receiptRoot = Web3BlockHash blk.header.receiptRoot.data + payloadRes.receiptsRoot = Web3BlockHash blk.header.receiptRoot.data payloadRes.logsBloom = Web3Bloom blk.header.bloom + # TODO Check the extra data length here + # payloadres.extraData = web3types.DynamicBytes[256] blk.header.extraData payloadRes.random = web3types.FixedBytes[32](payloadAttrs.random) payloadRes.blockNumber = Web3Quantity blk.header.blockNumber.truncate(uint64) payloadRes.gasLimit = Web3Quantity blk.header.gasLimit diff --git a/tests/amphora/check-merge-test-vectors.sh b/tests/amphora/check-merge-test-vectors.sh index 82a577e10..e3106f45a 100755 --- a/tests/amphora/check-merge-test-vectors.sh +++ b/tests/amphora/check-merge-test-vectors.sh @@ -13,20 +13,20 @@ set -Eeuo pipefail # eth/catalyst: fix random in payload, payloadid as hexutil # Get the payload -resp_get_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayload","params":["0x0"],"id":67}' http://localhost:8550) -echo "engine_getPayload response: ${resp_get_payload}" +resp_get_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayloadV1","params":["0x0"],"id":67}' http://localhost:8550) +echo "engine_getPayloadV1 response: ${resp_get_payload}" expected_resp_get_payload='{"jsonrpc":"2.0","id":67,"result":{"blockHash":"0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174","parentHash":"0xa0513a503d5bd6e89a144c3268e5b7e9da9dbf63df125a360e3950a7d0d67131","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x989680","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x0","transactions":[]}}' empirical_resp_get_payload='{"jsonrpc":"2.0","id":67,"result":{"blockHash":"0x7a694c5e6e372e6f865b073c101c2fba01f899f16480eb13f7e333a3b7e015bc","parentHash":"0xa0513a503d5bd6e89a144c3268e5b7e9da9dbf63df125a360e3950a7d0d67131","coinbase":"0x0000000000000000000000000000000000000000","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x989680","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x0","transactions":[]}}' -[[ ${resp_get_payload} == ${expected_resp_get_payload} ]] || [[ ${resp_get_payload} == ${empirical_resp_get_payload} ]] || (echo "Unexpected response to engine_getPayload"; false) +[[ ${resp_get_payload} == ${expected_resp_get_payload} ]] || [[ ${resp_get_payload} == ${empirical_resp_get_payload} ]] || (echo "Unexpected response to engine_getPayloadV1"; false) # Execute the payload # Needed two tweaks vs upstream note: (a) add blockNumber field and (b) switch receiptRoots to receiptRoot -resp_execute_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_executePayload","params":[{"blockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858","parentHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x7","transactions":[]}],"id":67}' http://localhost:8550) -[[ ${resp_execute_payload} == '{"jsonrpc":"2.0","id":67,"result":{"status":"VALID"}}' ]] || (echo "Unexpected response to engine_executePayload"; false) +resp_execute_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_executePayloadV1","params":[{"blockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858","parentHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x7","transactions":[]}],"id":67}' http://localhost:8550) +[[ ${resp_execute_payload} == '{"jsonrpc":"2.0","id":67,"result":{"status":"VALID"}}' ]] || (echo "Unexpected response to engine_executePayloadV1"; false) # Update the fork choice -resp_fork_choice_updated=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdated","params":[{"headBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "finalizedBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858"}],"id":67}' http://localhost:8550) -[[ ${resp_consensus_validated} == '{"jsonrpc":"2.0","id":67,"result":null}' ]] || (echo "Unexpected response to engine_forkChoiceUpdated"; false) +resp_fork_choice_updated=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "finalizedBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858"}],"id":67}' http://localhost:8550) +[[ ${resp_consensus_validated} == '{"jsonrpc":"2.0","id":67,"result":null}' ]] || (echo "Unexpected response to engine_forkchoiceUpdatedV1"; false) echo "Execution test vectors for Merge passed" diff --git a/tests/amphora/launch-nimbus.sh b/tests/amphora/launch-nimbus.sh index ca4841f80..71d012594 100755 --- a/tests/amphora/launch-nimbus.sh +++ b/tests/amphora/launch-nimbus.sh @@ -20,6 +20,7 @@ $SCRIPT_DIR/../../build/nimbus \ --custom-network:"$SCRIPT_DIR/amphora-interop-genesis-m1.json" \ --network:0 \ --engine-api \ + --engine-api-port:8545 \ --rpc \ --nat:none --discovery:none \ --import-key:"$SCRIPT_DIR/signer-key.txt" \ diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 718d30add..c59044b44 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -9,7 +9,7 @@ import asynctest, json, strformat, strutils, options, tables, os, nimcrypto, stew/byteutils, times, json_rpc/[rpcserver, rpcclient], eth/common as eth_common, - eth/[rlp, keys, trie/db, p2p/private/p2p_types], + eth/[rlp, keys, trie/db, p2p/private/p2p_types], web3/conversions, ../nimbus/rpc/[common, p2p, hexstrings, rpc_types, rpc_utils], ../nimbus/[constants, config, genesis, utils, transaction, vm_state, vm_types], diff --git a/vendor/nim-web3 b/vendor/nim-web3 index 16ae908e4..7daafe7b9 160000 --- a/vendor/nim-web3 +++ b/vendor/nim-web3 @@ -1 +1 @@ -Subproject commit 16ae908e49dad3dd1be1d536798a2b0ae39250df +Subproject commit 7daafe7b9cdfc3743c4b924eca6621dc928cf138