From 97ad5d49b023bd37165eceda79ff5937ae5860d6 Mon Sep 17 00:00:00 2001 From: tersec Date: Wed, 10 Nov 2021 11:41:02 +0000 Subject: [PATCH] kintsugi merge vector tests (#3072) --- beacon_chain/eth1/eth1_monitor.nim | 17 +++++++-- beacon_chain/spec/datatypes/merge.nim | 3 -- scripts/check_merge_test_vectors.sh | 55 +++++++++++++++------------ scripts/run-catalyst.sh | 15 ++++---- tests/test_merge_vectors.nim | 42 ++++++++++---------- vendor/nim-web3 | 2 +- 6 files changed, 74 insertions(+), 60 deletions(-) diff --git a/beacon_chain/eth1/eth1_monitor.nim b/beacon_chain/eth1/eth1_monitor.nim index 4b0b32439..a2ccb54cd 100644 --- a/beacon_chain/eth1/eth1_monitor.nim +++ b/beacon_chain/eth1/eth1_monitor.nim @@ -420,14 +420,25 @@ proc executePayload*(p: Web3DataProviderRef, p.web3.provider.engine_executePayloadV1(payload) proc forkchoiceUpdated*(p: Web3DataProviderRef, - headBlock, finalizedBlock: Eth2Digest): + headBlock, finalizedBlock: Eth2Digest, + timestamp: uint64, + randomData: array[32, byte], + feeRecipient: Eth1Address): Future[engine_api.ForkchoiceUpdatedResponse] = p.web3.provider.engine_forkchoiceUpdatedV1( ForkchoiceStateV1( headBlockHash: headBlock.asBlockHash, - safeBlockHash: finalizedBlock.asBlockHash, + + # https://hackmd.io/@n0ble/kintsugi-spec#Engine-API + # "CL client software MUST use headBlockHash value as a stub for the + # safeBlockHash parameter" + safeBlockHash: headBlock.asBlockHash, + finalizedBlockHash: finalizedBlock.asBlockHash), - none(engine_api.PayloadAttributesV1)) + some(engine_api.PayloadAttributesV1( + timestamp: Quantity timestamp, + random: FixedBytes[32] randomData, + feeRecipient: feeRecipient))) template readJsonField(j: JsonNode, fieldName: string, ValueType: type): untyped = var res: ValueType diff --git a/beacon_chain/spec/datatypes/merge.nim b/beacon_chain/spec/datatypes/merge.nim index 62418cc5b..b422eb184 100644 --- a/beacon_chain/spec/datatypes/merge.nim +++ b/beacon_chain/spec/datatypes/merge.nim @@ -40,9 +40,6 @@ type BloomLogs* = object data*: array[BYTES_PER_LOGS_BLOOM, byte] - # https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.4/src/engine/interop/specification.md#returns - PayloadId* = uint64 - # https://github.com/ethereum/consensus-specs/blob/v1.1.3/specs/merge/beacon-chain.md#executionpayload ExecutionPayload* = object parent_hash*: Eth2Digest diff --git a/scripts/check_merge_test_vectors.sh b/scripts/check_merge_test_vectors.sh index 6a3b9ea0a..d6a5ad405 100755 --- a/scripts/check_merge_test_vectors.sh +++ b/scripts/check_merge_test_vectors.sh @@ -3,40 +3,45 @@ set -Eeuo pipefail # https://notes.ethereum.org/@9AeMAlpyQYaAAyuj47BzRw/rkwW3ceVY # -# git clone --branch merge-interop-spec https://github.com/MariusVanDerWijden/go-ethereum.git +# git clone --branch kintsugi-spec https://github.com/MariusVanDerWijden/go-ethereum.git # # Last checked against geth as of -# commit d6b04900423634d27be1178edf46622394085bb9 (HEAD -> merge-interop-spec, origin/merge-interop-spec) +# commit 98240256ee51811c6b2806783c160aaf6f965f6b (HEAD -> kintsugi-spec, origin/kintsugi-spec) # Author: Marius van der Wijden -# Date: Wed Sep 29 19:24:56 2021 +0200 +# Date: Sat Nov 6 14:28:21 2021 +0100 # -# eth/catalyst: fix random in payload, payloadid as hexutil +# eth/catalyst: remove headHash from payloadAttributes -# Prepare a payload -resp_prepare_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_preparePayload","params":[{"parentHash":"0xa0513a503d5bd6e89a144c3268e5b7e9da9dbf63df125a360e3950a7d0d67131", "timestamp":"0x5", "random":"0x0000000000000000000000000000000000000000000000000000000000000000", "feeRecipient":"0x0000000000000000000000000000000000000000"}],"id":67}' http://localhost:8545) -echo "engine_preparePayload response: ${resp_prepare_payload}" -# Interop version of response, not current main version of response -[[ ${resp_prepare_payload} == '{"jsonrpc":"2.0","id":67,"result":"0x0"}' ]] || (echo "Unexpected response to engine_preparePayload"; false) +# Prepare payload +resp_prepare_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "safeBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a", "finalizedBlockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}, {"timestamp":"0x5", "random":"0x0000000000000000000000000000000000000000000000000000000000000000", "feeRecipient":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}],"id":67}' http://localhost:8545) +echo "engine_forkchoiceUpdatedV1 response: ${resp_prepare_payload}" -# 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:8545) -echo "engine_getPayload response: ${resp_get_payload}" +# Inconsistency in test vectors vs Geth behavior +expected_resp_prepare_payload='{"jsonrpc":"2.0","id":67,"result":{"status":"VALID","payloadId":"0xa247243752eb10b4"}}' +empirical_resp_prepare_payload='{"jsonrpc":"2.0","id":67,"result":{"status":"SUCCESS","payloadId":"0xa247243752eb10b4"}}' +[[ ${resp_prepare_payload} == "${expected_resp_prepare_payload}" ]] || [[ ${resp_prepare_payload} == "${empirical_resp_prepare_payload}" ]] || (echo "Unexpected response to engine_forkchoiceUpdatedV1"; false) -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) +# Get payload +resp_get_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_getPayloadV1","params":["0xa247243752eb10b4"],"id":67}' http://localhost:8545) +echo "engine_getPayloadV1 response: ${resp_get_payload}" -# 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":"0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174","parentHash":"0xa0513a503d5bd6e89a144c3268e5b7e9da9dbf63df125a360e3950a7d0d67131","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x1","gasLimit":"0x989680","gasUsed":"0x0","blockNumber":"0x1","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x0","transactions":[]}],"id":67}' http://localhost:8545) -[[ ${resp_execute_payload} == '{"jsonrpc":"2.0","id":67,"result":{"status":"VALID"}}' ]] || (echo "Unexpected response to engine_executePayload"; false) +expected_resp_get_payload='{"jsonrpc":"2.0","id":67,"result":{"parentHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x7","blockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858","transactions":[]}}' +[[ ${resp_get_payload} == "${expected_resp_get_payload}" ]] || (echo "Unexpected response to engine_getPayloadV1"; false) -# Mark the payload as valid -resp_consensus_validated=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_consensusValidated","params":[{"blockHash":"0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", "status":"VALID"}],"id":67}' http://localhost:8545) -[[ ${resp_consensus_validated} == '{"jsonrpc":"2.0","id":67,"result":null}' ]] || (echo "Unexpected response to engine_consensusValidated"; false) +# Execute payload +resp_execute_payload=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_executePayloadV1","params":[{"parentHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a","coinbase":"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b","stateRoot":"0xca3149fa9e37db08d1cd49c9061db1002ef1cd58db2210f2115c8c989b2bdf45","receiptRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","random":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x5","extraData":"0x","baseFeePerGas":"0x7","blockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858","transactions":[]}],"id":67}' http://localhost:8545) +echo "engine_executePayloadV1 response: ${resp_execute_payload}" + +# SUCCESS vs VALID again, but in the other direction +expected_resp_execute_payload='{"jsonrpc":"2.0","id":67,"result":{"status":"SUCCESS","latestValidHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858"}}' +empirical_resp_execute_payload='{"jsonrpc":"2.0","id":67,"result":{"status":"VALID","latestValidHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858"}}' +[[ ${resp_execute_payload} == "${expected_resp_execute_payload}" ]] || [[ ${resp_execute_payload} == "${empirical_resp_execute_payload}" ]] || (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":"0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", "finalizedBlockHash":"0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174"}],"id":67}' http://localhost:8545) -[[ ${resp_consensus_validated} == '{"jsonrpc":"2.0","id":67,"result":null}' ]] || (echo "Unexpected response to engine_forkChoiceUpdated"; false) +resp_update_forkchoice=$(curl -sX POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"engine_forkchoiceUpdatedV1","params":[{"headBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "safeBlockHash":"0x3559e851470f6e7bbed1db474980683e8c315bfce99b2a6ef47c057c04de7858", "finalizedBlockHash":"0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a"}, null],"id":67}' http://localhost:8545) +echo "engine_forkchoiceUpdatedV1 response: ${resp_update_forkchoice}" -echo "Execution test vectors for Merge passed" +expected_resp_update_forkchoice='{"jsonrpc":"2.0","id":67,"result":{"status":"SUCCESS","payloadId":"0x"}}' +[[ ${resp_update_forkchoice} == "${expected_resp_update_forkchoice}" ]] || (echo "Unexpected response to engine_forkchoiceUpdatedV1"; false) + +echo "kintsugi test vectors passed" diff --git a/scripts/run-catalyst.sh b/scripts/run-catalyst.sh index 3b03148a8..450a3573f 100755 --- a/scripts/run-catalyst.sh +++ b/scripts/run-catalyst.sh @@ -1,21 +1,20 @@ #!/usr/bin/env bash # set -Eeuo pipefail -# https://notes.ethereum.org/_UH57VUPRrC-re3ubtmo2w +# https://notes.ethereum.org/@9AeMAlpyQYaAAyuj47BzRw/rkwW3ceVY#Genesis +# Genesis block hash: 0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a +# To start miner, run miner.start() # To increase verbosity: debug.verbosity(4) -# MetaMask seed phrase for address with balance is: -# lecture manual soon title cloth uncle gesture cereal common fruit tooth crater GENESISJSON=$(mktemp) GETHDATADIR=$(mktemp -d) echo \{\ - \"config\": \{\ + \"config\": \{\ \"chainId\":1,\ \"homesteadBlock\":0,\ \"eip150Block\":0,\ - \"eip150Hash\": \"0x0000000000000000000000000000000000000000000000000000000000000000\",\ \"eip155Block\":0,\ \"eip158Block\":0,\ \"byzantiumBlock\":0,\ @@ -29,13 +28,13 @@ echo \{\ \"period\": 5,\ \"epoch\": 30000\ \},\ - \"terminalTotalDifficulty\":10\ + \"terminalTotalDifficulty\":0\ \},\ \"nonce\":\"0x42\",\ \"timestamp\":\"0x0\",\ \"extraData\":\"0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\ \"gasLimit\":\"0x1C9C380\",\ - \"difficulty\":\"0x0\",\ + \"difficulty\":\"0x400000000\",\ \"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\ \"coinbase\":\"0x0000000000000000000000000000000000000000\",\ \"alloc\":\{\ @@ -54,4 +53,4 @@ echo \{\ ~/execution_clients/go-ethereum/build/bin/geth --catalyst --http --ws -http.api "engine" --datadir "${GETHDATADIR}" account import <(echo 45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8) # Start the node (and press enter once to unlock the account) -~/execution_clients/go-ethereum/build/bin/geth --catalyst --http --ws -ws.api "eth,net,engine" --datadir "${GETHDATADIR}" --allow-insecure-unlock --unlock "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" --password "" --nodiscover console +~/execution_clients/go-ethereum/build/bin/geth --catalyst --http --ws --http.api "eth,net,engine" -ws.api "eth,net,engine" --datadir "${GETHDATADIR}" --allow-insecure-unlock --unlock "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" --password "" --nodiscover console diff --git a/tests/test_merge_vectors.nim b/tests/test_merge_vectors.nim index b65b97d40..d05d320b2 100644 --- a/tests/test_merge_vectors.nim +++ b/tests/test_merge_vectors.nim @@ -14,37 +14,39 @@ suite "Merge test vectors": let web3Provider = (waitFor Web3DataProvider.new( default(Eth1Address), "ws://127.0.0.1:8546")).get - test "preparePayload, getPayload, executePayload, and forkchoiceUpdated": + test "getPayload, executePayload, and forkchoiceUpdated": + const feeRecipient = + Eth1Address.fromHex("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") let - existingBlock = waitFor web3Provider.getBlockByNumber(5) - payloadId = waitFor web3Provider.preparePayload( + existingBlock = waitFor web3Provider.getBlockByNumber(1) + payloadId = waitFor web3Provider.forkchoiceUpdated( + existingBlock.hash.asEth2Digest, existingBlock.hash.asEth2Digest, existingBlock.timestamp.uint64 + 12, default(Eth2Digest).data, # Random - Eth1Address.fromHex("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) # Fee recipient + feeRecipient) payload = waitFor web3Provider.getPayload( - Quantity(payloadId.payloadId)) + Quantity(payloadId.payloadId.get)) payloadStatus = waitFor web3Provider.executePayload(payload) fcupdatedStatus = waitFor web3Provider.forkchoiceUpdated( - payload.blockHash.asEth2Digest, payload.blockHash.asEth2Digest) - - payloadId2 = waitFor web3Provider.preparePayload( + payload.blockHash.asEth2Digest, payload.blockHash.asEth2Digest, existingBlock.timestamp.uint64 + 24, default(Eth2Digest).data, # Random - Eth1Address.fromHex("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) # Fee recipient + feeRecipient) + payload2 = waitFor web3Provider.getPayload( - Quantity(payloadId2.payloadId)) + Quantity(fcupdatedStatus.payloadId.get)) payloadStatus2 = waitFor web3Provider.executePayload(payload2) fcupdatedStatus2 = waitFor web3Provider.forkchoiceUpdated( - payload2.blockHash.asEth2Digest, payload2.blockHash.asEth2Digest) + payload2.blockHash.asEth2Digest, + payload2.blockHash.asEth2Digest, + existingBlock.timestamp.uint64 + 36, + default(Eth2Digest).data, # Random + feeRecipient) - check: payloadStatus.status == "VALID" - - test "getPayload unknown payload": - try: - let res = waitFor web3Provider.getPayload(Quantity(100000)) - doAssert false - except ValueError as e: - # expected outcome - echo e.msg + check: + payloadStatus.status == "VALID" + fcupdatedStatus.status == "SUCCESS" + payloadStatus2.status == "VALID" + fcupdatedStatus2.status == "SUCCESS" diff --git a/vendor/nim-web3 b/vendor/nim-web3 index 94635bbe2..183cc8f40 160000 --- a/vendor/nim-web3 +++ b/vendor/nim-web3 @@ -1 +1 @@ -Subproject commit 94635bbe2017d0bbdd69c872a6a14cbc46c276c0 +Subproject commit 183cc8f407082a44fec18492a8c18a8261cdd27f