From 895c17d5d2ae140b0d85a6783c9b9f58275b1309 Mon Sep 17 00:00:00 2001 From: daniellehrner Date: Wed, 17 Jul 2024 21:00:48 +0200 Subject: [PATCH] EIP 7702 (#7237) * EIP 7702 first draft Signed-off-by: Daniel Lehrner * added CHANGELOG.md entry Signed-off-by: Daniel Lehrner * bug fixes, added first tests Signed-off-by: Daniel Lehrner * container verify GitHub workflow (#7239) Container verification step in release process automated with the container verify GitHub workflow. New workflow is triggered at the end of the release workflow which will check the release container images starts successfully. Verification test only checks container starts and reach the Ethereum main loop Signed-off-by: Chaminda Divitotawela Signed-off-by: Daniel Lehrner * Investigate chain halts when syncing (#7162) Fix some reasons for chain download halts when syncing Signed-off-by: stefan.pingel@consensys.net Signed-off-by: Stefan Pingel <16143240+pinges@users.noreply.github.com> Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * Check for EOFCreate subcontainer rules (#7232) Check and test for the unused container rule, and only returncontract targets can have truncated data rule. Also test the other subcontainer rules in unit tests. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * Remove deprecation message for `--Xp2p-peer-lower-bound` (#7247) Signed-off-by: Gabriel-Trintinalia Signed-off-by: Daniel Lehrner * less invasive code injection approach Signed-off-by: Daniel Lehrner * added missing java doc & fixed test Signed-off-by: Daniel Lehrner * added (currently non-working) acceptance test, some bug fixes in the transaction validation and tx pool logic Signed-off-by: Daniel Lehrner * fix spotless Signed-off-by: Daniel Lehrner * updated acceptance test, still not working, newPayload request seems to be necessary before final fork choice update Signed-off-by: Daniel Lehrner * use correct world state to inject temporary code, inject code in existing accounts as well Signed-off-by: Daniel Lehrner * renamed test service to prague, because the engine versions used are only available in the prague hard fork Signed-off-by: Daniel Lehrner * fixed acceptance test, some bug fixes if authorized account does not yet exist Signed-off-by: Daniel Lehrner * Add build version option to prefix git hash with custom version property (#7222) * Add build version option to prefix git hash with custom version property * Refactor to make appending the git hash a boolean property. Include a commented-out example of how to use the properties in the gradle file Signed-off-by: Matthew Whitehead Signed-off-by: Daniel Lehrner * Handle invalid snap getTrieNode requests with empty paths gracefully (#7221) Signed-off-by: Jason Frame Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * fix typos in CHANGELOG (#7226) Signed-off-by: Ties <71668189+TiesD@users.noreply.github.com> Co-authored-by: Matt Nelson <85905982+non-fungible-nelson@users.noreply.github.com> Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * feat: Add network option for LUKSO Mainnet (#7223) * Add option for LUKSO network Signed-off-by: Wolmin * Add tests for LUKSO Signed-off-by: Wolmin * Apply spotless Signed-off-by: Wolmin * Add changelog entry Signed-off-by: Wolmin * Fix duplicate func Signed-off-by: Wolmin * Fix changelog Signed-off-by: Wolmin * Add bootnodes to genesis Signed-off-by: Wolmin --------- Signed-off-by: Wolmin Signed-off-by: Wolmin <44748271+Wolmin@users.noreply.github.com> Signed-off-by: Daniel Lehrner * Update Docker base image to Ubuntu 24.04 (#7251) Signed-off-by: Fabio Di Fabio Signed-off-by: Daniel Lehrner * Reconfigure how Protocol Specs are created WRT EVM condiguration (#7245) Make the max code size and max initcode size a part of the EVM configuration. As part of the change we need to move the tasks CodeFactory once handled as a static class and move it into the EVM. This has a nice follow on effect that we don't need to pass in max EOF versions or max code sizes anymore. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * Fix the wrong 'Identifier' and 'Synchronizer' usage (#7252) * fix the synchronizer usage Signed-off-by: Leni * fix Identifier usage Signed-off-by: Leni --------- Signed-off-by: Leni Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * Fix flaky SECP256R1 test (#7249) Signed-off-by: Daniel Lehrner Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * update to work with the new max retries value (#7253) Signed-off-by: Justin Florentine Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * Temporary CancunEOF fork for EOF testing. (#7227) Add Genesis ("CancunEOFTime") and reference test ("CancunEOF") support for a temporary Cancun+EOF fork, in anticipation of potential devnets. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * fixed bug introduced through merge of main, made acceptance test easier to understand Signed-off-by: Daniel Lehrner * added missing java docs Signed-off-by: Daniel Lehrner * removed unnecessary tag Signed-off-by: Daniel Lehrner * make encodeSingleSetCode public again Signed-off-by: Daniel Lehrner * Snapserver responses to return at least one response (#7190) Signed-off-by: Jason Frame Signed-off-by: Daniel Lehrner * copy setCodeTransactionPayloads as well Signed-off-by: Daniel Lehrner * fixed bug during tests with forrest db Signed-off-by: Daniel Lehrner * Snapserver GetTrieNodes request to handle short hash for storage (#7264) Signed-off-by: Jason Frame Signed-off-by: Daniel Lehrner * javadoc: Adding javadoc for ethstats module (#7269) * javadoc: Adding javadoc for ethstats module --------- Signed-off-by: Usman Saleem Signed-off-by: Daniel Lehrner * Fix javadoc for ethereum:core top level package (#7270) * javadoc - Apply javadoc to ethereum core top level package --------- Signed-off-by: Usman Saleem Signed-off-by: Daniel Lehrner * Disable Flaky tests - permissioning (#7256) * disable some flaky tests Signed-off-by: Sally MacFarlane * correct name for test Signed-off-by: Sally MacFarlane * formatting Signed-off-by: Sally MacFarlane * disable some flaky tests Signed-off-by: Sally MacFarlane --------- Signed-off-by: Sally MacFarlane Co-authored-by: Usman Saleem Signed-off-by: Daniel Lehrner * Add bootnodes to the maintained peer list (#7257) * Add bootnodes to the maintained peer list Signed-off-by: Matthew Whitehead * Update unit tests Signed-off-by: Matthew Whitehead * Add entry in changelog Signed-off-by: Matthew Whitehead * Tweak unit test Signed-off-by: Matthew Whitehead * Refactor to keep common steps the same for both cases Signed-off-by: Matthew Whitehead * Add debug log, call sanitizePeers() only once Signed-off-by: Matthew Whitehead --------- Signed-off-by: Matthew Whitehead Signed-off-by: Daniel Lehrner * Fix javadoc for ethereum api module, graphql package (#7272) * javadoc - Adding missing javadocs ethereum:api graphql package Signed-off-by: Usman Saleem Signed-off-by: Daniel Lehrner * T8n support for isStateTest and empty accounts (#7275) Update t8n executor to support new isStateTest env flag that will disable extra-transactional processing such as block rewards and beacon root. Also, make sure such extra-transactional commits don't create empty accounts. Signed-off-by: Danno Ferrin Co-authored-by: Usman Saleem Signed-off-by: Daniel Lehrner * Promote storage x-trie-log subcommand to trie-log (#7278) Signed-off-by: Simon Dudley Signed-off-by: Daniel Lehrner * Evm tool readme update (#7274) * update paths to binary. update docker build to use java 21 * updated suggested jdk --------- Signed-off-by: Justin Florentine Signed-off-by: Daniel Lehrner * javadoc - Add missing javadoc for evmtool module (#7277) Signed-off-by: Usman Saleem Signed-off-by: Daniel Lehrner * Rename ValidatorPublicKey to ValidatorPubKey (#7280) Adapt to EIP-7002 name change for validator public key in all places. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * Add info-level diagnostic logs to aid with resolving stalled BFT chains (#7271) * Add info-level diagnostic logs to aid with resolving stalled BFT chains Signed-off-by: Matthew Whitehead * Add javadoc Signed-off-by: Matthew Whitehead --------- Signed-off-by: Matthew Whitehead Signed-off-by: Matt Whitehead Signed-off-by: Daniel Lehrner * Update EIP-2935 contract (#7281) Use the updated contract and address for EIP-2539. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * add evmtool compability, fixing bugs related to sender recovery of 7702 txs and handling authorizations to empty accounts Signed-off-by: Daniel Lehrner * Deeper tracing of self-destructed accounts (#7284) Consider previously self-destructed accounts when creating accounts. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * next release version after 24.7.0 (#7285) Signed-off-by: garyschulte Signed-off-by: Daniel Lehrner * Add experimental `--Xsnapsync-bft-enabled` which enables snap sync for BFT chains (#7140) * Create a BFT-specific pivot block handler Signed-off-by: Matthew Whitehead * Change visibility Signed-off-by: Matthew Whitehead * Refactor sync-peer-count internal variable to match name, add experimental flag to enabled snap + BFT Signed-off-by: Matthew Whitehead * Merge with main Signed-off-by: Matthew Whitehead * Fix uppercase Signed-off-by: Matthew Whitehead * Address synchronization issue with trie pruning. Create BFT-specific account range handler. Add pipeline name and logs Signed-off-by: Matthew Whitehead * Remove debug log Signed-off-by: Matthew Whitehead * fixing snapsync for empty state Signed-off-by: Karim Taam * Don't queue up events we can't handle Signed-off-by: Matthew Whitehead * Fix timing window where a validator with an empty data dir sometimes falls back to full sync if peer status isn't received quickly enough Signed-off-by: Matthew Whitehead * Remove BFT-specific account request class. Not needed Signed-off-by: Matthew Whitehead * Refactor some more 'fast' sync variables that are common to all pivot-based sync types Signed-off-by: Matthew Whitehead * In FULL sync mode, disable bonsai-limit-trie-logs-enabled instead of failing to start Signed-off-by: Matthew Whitehead * Add javadoc comments, clarify overriding bonsai-limit-trie-logs-enabled Signed-off-by: Matthew Whitehead * Add BFT pivot block selector tests Signed-off-by: Matthew Whitehead * Fix failure error message Signed-off-by: Matthew Whitehead * Remove the unnamed Pipe constructor and update tests to set a pipe name Signed-off-by: Matthew Whitehead * Revert some info logs back to debug given the feedback on noise in the logs syncing with holesky Signed-off-by: Matthew Whitehead * Refactor fastSyncPivotDistance to syncPivotDistance Signed-off-by: Matthew Whitehead * Incomplete refactoring Signed-off-by: Matthew Whitehead * Update BFT event queueing tests Signed-off-by: Matthew Whitehead * Event queue test fixes Signed-off-by: Matthew Whitehead * Remove automatic setting of bonsai-limit-trie-logs-enabled to false if sync-mode = FULL (moving to another PR) Signed-off-by: Matthew Whitehead --------- Signed-off-by: Matthew Whitehead Signed-off-by: Karim Taam Signed-off-by: Matt Whitehead Co-authored-by: Karim Taam Signed-off-by: Daniel Lehrner * Turn off CicleCI for Besu (#7291) All the CI jobs run in GitHub actions and Circle CI it no longer needed in Besu project Signed-off-by: Chaminda Divitotawela Signed-off-by: Daniel Lehrner * Check for snap server (#6609) * EthPeer add isServingSnap to be able to make sure that we have enough snap servers connected when we are snap syncing Signed-off-by: stefan.pingel@consensys.net Signed-off-by: Sally MacFarlane Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * Implement System Calls (#7263) Signed-off-by: Gabriel-Trintinalia Signed-off-by: Daniel Lehrner * wrap WorldUpdater inside a WorldUpdaterService to inject the authorized code whenever needed Signed-off-by: Daniel Lehrner * Update limit trie logs validation message for sync-mode=FULL (#7279) Signed-off-by: Simon Dudley Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * Execute requests before block persist (#7295) Signed-off-by: Gabriel-Trintinalia Signed-off-by: Daniel Lehrner * fixed MainnetTransactionProcessor retrieval of correctn `to` account with injected code, fixed code injection Signed-off-by: Daniel Lehrner * only first authorization is accepted, all the following ones are ignored Signed-off-by: Daniel Lehrner * don't cache account with empty code Signed-off-by: Daniel Lehrner * revert wrapping of world updater, as its `updater()` method creates a type of nesting that is not compatible with wrapping it. Instead a service is injected in the world updater to inject the code into the authorized accounts Signed-off-by: Daniel Lehrner * Fixed outdated tech redirect link. (#7297) * fix wiki link Signed-off-by: Snazzy * fix format Signed-off-by: Snazzy * change knownHash Signed-off-by: Snazzy --------- Signed-off-by: Snazzy Signed-off-by: Daniel Lehrner * Increment private nonce even if transaction failed. (#6593) Increment private nonce even if transaction failed Signed-off-by: George Tebrean Signed-off-by: stefan.pingel@consensys.net Co-authored-by: stefan.pingel@consensys.net Co-authored-by: Stefan Pingel <16143240+pinges@users.noreply.github.com> Signed-off-by: Daniel Lehrner * feat: Enhance --profile to load external profiles (#7292) * feat: --profile can load external profiles * fix external profile name method * fix ProfilesCompletionCandidate * test: Add unit tests * changelog: Update changelog * test: Fix TomlConfigurationDefaultProviderTest * test: Fix BesuCommandTest --------- Signed-off-by: Usman Saleem Signed-off-by: Daniel Lehrner * Fix status badge for documentation (#7304) Documentation has been moved to GitHub pages and no longer use readthedocs. Updated the README status badge for docs with correct link Signed-off-by: Chaminda Divitotawela Signed-off-by: Daniel Lehrner * [MINOR] Fixed some typos (#7299) * typos Signed-off-by: Sally MacFarlane --------- Signed-off-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * refactored to share one AuthorizedAccountService between the different instances of the world updater, renamed some classes Signed-off-by: Daniel Lehrner * spotless Signed-off-by: Daniel Lehrner * load code for authorization at the beginning of the transaction Signed-off-by: Daniel Lehrner * ignore authorization if chain id doesn't match Signed-off-by: Daniel Lehrner * cache authority address, evmtool: do not fail if sender address is wrong Signed-off-by: Daniel Lehrner * Add evmtool block-test subcommand (#7293) * Add evmtool block-test subcommand Add an evmtool subcommand that will run non-hive blockchain tests. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * Make the retrying snap tasks switching (#7307) * make snap tasks switching Signed-off-by: stefan.pingel@consensys.net Signed-off-by: Daniel Lehrner * 6612: Remove deprecated sync modes and related helper methods (#7309) * 6612: Remove deprecated sync modes and related helper methods Signed-off-by: Matilda-Clerke Signed-off-by: Daniel Lehrner * EOF Reference Test Fixes (#7306) Fix a number of issues found in reference tests and evmone tests. - Be tolerant of more nulls in json - Support ContainerKind in reference tests - re-order EXTCALL oeprands - correct return value for REVERT in EXT*CALL - re-order EOFCREATE code validation Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * test template refactor, bump besu-native to 0.9.2 (#7315) Signed-off-by: garyschulte Signed-off-by: Daniel Lehrner * Feature/use gnark-crypto for eip-2537 (#7316) * use gnark-crypto for bls precompiles Signed-off-by: garyschulte Signed-off-by: Daniel Lehrner * 6612 update changelog with removed syncmodes (#7320) * 6612: Update changelog with removal of deprecated sync modes Signed-off-by: Matilda Clerke * 6612: Update changelog with removal of deprecated sync modes Signed-off-by: Matilda Clerke * 6612: Update changelog with removal of deprecated sync modes Signed-off-by: Matilda Clerke --------- Signed-off-by: Matilda Clerke Signed-off-by: Daniel Lehrner * Update datacopy (#7319) Check for OOG earlier in DataCopy. Add unit tests to cover operation branches. Signed-off-by: Danno Ferrin Signed-off-by: Daniel Lehrner * disable flaky test (#7308) * disable flaky test Signed-off-by: Sally MacFarlane * disable flaky test Signed-off-by: Sally MacFarlane --------- Signed-off-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * Update unit test (#7317) * Update parameterized unit tests so the enumerate with --dry-run * Update the prague-withdrawal.json unit test to handle current code Signed-off-by: Danno Ferrin Co-authored-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * persist accounts that have storage updates, but no nonce, balance nor code Signed-off-by: Daniel Lehrner * Revert "persist accounts that have storage updates, but no nonce, balance nor code" This reverts commit 9c9121a115ecb09f1c3add1fff4da3b102f8b429. Signed-off-by: Daniel Lehrner * removed PKI backed QBFT (#7310) * removed PKI backed QBFT Signed-off-by: Sally MacFarlane * changelog Signed-off-by: Sally MacFarlane --------- Signed-off-by: Sally MacFarlane Signed-off-by: Daniel Lehrner * EIP-7251 add consolidation request type (#7266) * add request type for consolidations, encoder, decoder and tests * added raw tx for consolidation * add consolidation reqs to EngineGetPayloadResultV4 * set storage slot value to 0 initially and value for tx * updates plugin api Signed-off-by: Justin Florentine Signed-off-by: Sally MacFarlane --------- Signed-off-by: Sally MacFarlane Signed-off-by: Justin Florentine Co-authored-by: Justin Florentine Signed-off-by: Daniel Lehrner * fix: `eth_call` deserialization to correctly ignore unknown fields in the transaction object (#7323) * fix: Use Builder for JsonCallParameter * changelog * add additional unit tests * fix: Update builder to withGas to match the json eth_call --------- Signed-off-by: Usman Saleem Signed-off-by: Daniel Lehrner * Stop transaction selection on TX_EVALUATION_TOO_LONG (#7330) Signed-off-by: Fabio Di Fabio Signed-off-by: Daniel Lehrner * message frame buider will create AuthorizedCodeService by itsef if it isn't set Signed-off-by: Daniel Lehrner * get correct nonce for authorization Signed-off-by: Daniel Lehrner * nonce only returns a vaid nonce, new method nonceList returns all the nonces Signed-off-by: Daniel Lehrner * plugs leaky abstraction Signed-off-by: Justin Florentine * some renaming, acceptance tests checks for exact balance of tx sponsor at the end. Signed-off-by: Daniel Lehrner * inject the 7702 code into DiffBasedWorldStateUpdateAccumulator.createAccount Signed-off-by: Daniel Lehrner * spotless Signed-off-by: Justin Florentine * spotless fix, removed todos Signed-off-by: Daniel Lehrner * make AuthorityProcessor & chain id for it optional Signed-off-by: Daniel Lehrner --------- Signed-off-by: Daniel Lehrner Signed-off-by: Chaminda Divitotawela Signed-off-by: stefan.pingel@consensys.net Signed-off-by: Stefan Pingel <16143240+pinges@users.noreply.github.com> Signed-off-by: Danno Ferrin Signed-off-by: Gabriel-Trintinalia Signed-off-by: Matthew Whitehead Signed-off-by: Jason Frame Signed-off-by: Ties <71668189+TiesD@users.noreply.github.com> Signed-off-by: Wolmin Signed-off-by: Wolmin <44748271+Wolmin@users.noreply.github.com> Signed-off-by: Fabio Di Fabio Signed-off-by: Leni Signed-off-by: Justin Florentine Signed-off-by: Usman Saleem Signed-off-by: Sally MacFarlane Signed-off-by: Simon Dudley Signed-off-by: Matt Whitehead Signed-off-by: garyschulte Signed-off-by: Karim Taam Signed-off-by: Snazzy Signed-off-by: George Tebrean Signed-off-by: Matilda-Clerke Signed-off-by: Matilda Clerke Co-authored-by: Chaminda Divitotawela Co-authored-by: Stefan Pingel <16143240+pinges@users.noreply.github.com> Co-authored-by: Sally MacFarlane Co-authored-by: Danno Ferrin Co-authored-by: Gabriel-Trintinalia Co-authored-by: Matt Whitehead Co-authored-by: Jason Frame Co-authored-by: Ties <71668189+TiesD@users.noreply.github.com> Co-authored-by: Matt Nelson <85905982+non-fungible-nelson@users.noreply.github.com> Co-authored-by: Wolmin <44748271+Wolmin@users.noreply.github.com> Co-authored-by: Fabio Di Fabio Co-authored-by: leniram159 Co-authored-by: Justin Florentine Co-authored-by: Usman Saleem Co-authored-by: Simon Dudley Co-authored-by: garyschulte Co-authored-by: Karim Taam Co-authored-by: gringsam Co-authored-by: George Tebrean <99179176+gtebrean@users.noreply.github.com> Co-authored-by: stefan.pingel@consensys.net Co-authored-by: Matilda-Clerke --- CHANGELOG.md | 1 + .../ethereum/PragueAcceptanceTestHelper.java | 193 +++++++++++++ .../SetCodeTransactionAcceptanceTest.java | 125 +++++++++ .../src/test/resources/dev/dev_prague.json | 112 ++++++++ .../options/TransactionPoolOptionsTest.java | 2 +- .../besu/datatypes/SetCodeAuthorization.java | 82 ++++++ .../besu/datatypes/Transaction.java | 14 + .../besu/datatypes/TransactionType.java | 18 +- .../engine/EngineForkchoiceUpdatedV3.java | 14 +- .../results/EnginePayloadStatusResult.java | 9 +- .../results/TransactionCompleteResult.java | 14 +- .../ethereum/core/SetCodeAuthorization.java | 260 ++++++++++++++++++ .../besu/ethereum/core/Transaction.java | 89 +++++- .../encoding/SetCodeTransactionDecoder.java | 116 ++++++++ .../encoding/SetCodeTransactionEncoder.java | 89 ++++++ .../core/encoding/TransactionDecoder.java | 4 +- .../core/encoding/TransactionEncoder.java | 4 +- .../ethereum/mainnet/AuthorityProcessor.java | 88 ++++++ .../mainnet/MainnetProtocolSpecs.java | 20 +- .../mainnet/MainnetTransactionProcessor.java | 52 +++- .../mainnet/MainnetTransactionValidator.java | 3 +- .../PrivateMutableWorldStateUpdater.java | 20 +- .../DiffBasedWorldStateUpdateAccumulator.java | 6 +- .../ethereum/core/BlockDataGenerator.java | 1 + .../ethereum/core/TransactionTestFixture.java | 2 + .../SetCodeTransactionDecoderTest.java | 120 ++++++++ .../SetCodeTransactionEncoderTest.java | 122 ++++++++ .../MainnetTransactionProcessorTest.java | 4 +- .../eth/transactions/PendingTransaction.java | 5 + .../layered/BaseTransactionPoolTest.java | 2 +- .../eth/transactions/layered/LayersTest.java | 1 + .../hyperledger/besu/evmtool/T8nExecutor.java | 69 ++++- .../evm/account/AuthorizedCodeAccount.java | 100 +++++++ .../account/MutableAuthorizedCodeAccount.java | 138 ++++++++++ .../besu/evm/fluent/SimpleWorld.java | 23 +- .../besu/evm/frame/MessageFrame.java | 39 ++- .../besu/evm/gascalculator/GasCalculator.java | 10 + .../gascalculator/PragueGasCalculator.java | 7 + .../evm/operation/AbstractCallOperation.java | 1 + .../operation/AbstractCreateOperation.java | 1 + .../operation/AbstractExtCallOperation.java | 1 + .../evm/worldstate/AbstractWorldUpdater.java | 22 +- .../evm/worldstate/AuthorizedCodeService.java | 116 ++++++++ .../besu/evm/worldstate/JournaledUpdater.java | 22 +- .../besu/evm/worldstate/WorldUpdater.java | 8 + .../hyperledger/besu/evm/toy/ToyWorld.java | 22 +- 46 files changed, 2108 insertions(+), 63 deletions(-) create mode 100644 acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/PragueAcceptanceTestHelper.java create mode 100644 acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java create mode 100644 acceptance-tests/tests/src/test/resources/dev/dev_prague.json create mode 100644 datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java create mode 100644 evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e0269472a5f..a6b28d0a5c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Add `block-test` subcommand to the evmtool which runs blockchain reference tests [#7310](https://github.com/hyperledger/besu/pull/7310) - Implement gnark-crypto for eip-2537 [#7316](https://github.com/hyperledger/besu/pull/7316) - Improve blob size transaction selector [#7312](https://github.com/hyperledger/besu/pull/7312) +- Added EIP-7702 [#7237](https://github.com/hyperledger/besu/pull/7237) ### Bug fixes - Fix `eth_call` deserialization to correctly ignore unknown fields in the transaction object. [#7323](https://github.com/hyperledger/besu/pull/7323) diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/PragueAcceptanceTestHelper.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/PragueAcceptanceTestHelper.java new file mode 100644 index 00000000000..fa190f3de17 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/PragueAcceptanceTestHelper.java @@ -0,0 +1,193 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.ethereum; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthTransactions; + +import java.io.IOException; +import java.util.Optional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.web3j.protocol.core.methods.response.EthBlock; + +public class PragueAcceptanceTestHelper { + protected static final MediaType MEDIA_TYPE_JSON = + MediaType.parse("application/json; charset=utf-8"); + + private final OkHttpClient httpClient; + private final ObjectMapper mapper; + private final BesuNode besuNode; + private final EthTransactions ethTransactions; + + private long blockTimeStamp = 0; + + PragueAcceptanceTestHelper(final BesuNode besuNode, final EthTransactions ethTransactions) { + this.besuNode = besuNode; + this.ethTransactions = ethTransactions; + httpClient = new OkHttpClient(); + mapper = new ObjectMapper(); + } + + public void buildNewBlock() throws IOException { + final EthBlock.Block block = besuNode.execute(ethTransactions.block()); + + blockTimeStamp += 1; + final Call buildBlockRequest = + createEngineCall(createForkChoiceRequest(block.getHash(), blockTimeStamp)); + + final String payloadId; + try (final Response buildBlockResponse = buildBlockRequest.execute()) { + payloadId = + mapper + .readTree(buildBlockResponse.body().string()) + .get("result") + .get("payloadId") + .asText(); + + assertThat(payloadId).isNotEmpty(); + } + + waitFor(500); + + final Call getPayloadRequest = createEngineCall(createGetPayloadRequest(payloadId)); + + final ObjectNode executionPayload; + final String newBlockHash; + final String parentBeaconBlockRoot; + try (final Response getPayloadResponse = getPayloadRequest.execute()) { + assertThat(getPayloadResponse.code()).isEqualTo(200); + + executionPayload = + (ObjectNode) + mapper + .readTree(getPayloadResponse.body().string()) + .get("result") + .get("executionPayload"); + + newBlockHash = executionPayload.get("blockHash").asText(); + parentBeaconBlockRoot = executionPayload.remove("parentBeaconBlockRoot").asText(); + + assertThat(newBlockHash).isNotEmpty(); + } + + final Call newPayloadRequest = + createEngineCall( + createNewPayloadRequest(executionPayload.toString(), parentBeaconBlockRoot)); + try (final Response newPayloadResponse = newPayloadRequest.execute()) { + assertThat(newPayloadResponse.code()).isEqualTo(200); + } + + final Call moveChainAheadRequest = createEngineCall(createForkChoiceRequest(newBlockHash)); + + try (final Response moveChainAheadResponse = moveChainAheadRequest.execute()) { + assertThat(moveChainAheadResponse.code()).isEqualTo(200); + } + } + + private Call createEngineCall(final String request) { + return httpClient.newCall( + new Request.Builder() + .url(besuNode.engineRpcUrl().get()) + .post(RequestBody.create(request, MEDIA_TYPE_JSON)) + .build()); + } + + private String createForkChoiceRequest(final String blockHash) { + return createForkChoiceRequest(blockHash, null); + } + + private String createForkChoiceRequest(final String parentBlockHash, final Long timeStamp) { + final Optional maybeTimeStamp = Optional.ofNullable(timeStamp); + + String forkChoiceRequest = + "{" + + " \"jsonrpc\": \"2.0\"," + + " \"method\": \"engine_forkchoiceUpdatedV3\"," + + " \"params\": [" + + " {" + + " \"headBlockHash\": \"" + + parentBlockHash + + "\"," + + " \"safeBlockHash\": \"" + + parentBlockHash + + "\"," + + " \"finalizedBlockHash\": \"" + + parentBlockHash + + "\"" + + " }"; + + if (maybeTimeStamp.isPresent()) { + forkChoiceRequest += + " ,{" + + " \"timestamp\": \"" + + Long.toHexString(maybeTimeStamp.get()) + + "\"," + + " \"prevRandao\": \"0x0000000000000000000000000000000000000000000000000000000000000000\"," + + " \"suggestedFeeRecipient\": \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\"," + + " \"withdrawals\": []," + + " \"parentBeaconBlockRoot\": \"0x0000000000000000000000000000000000000000000000000000000000000000\"" + + " }"; + } + + forkChoiceRequest += " ]," + " \"id\": 67" + "}"; + + return forkChoiceRequest; + } + + private String createGetPayloadRequest(final String payloadId) { + return "{" + + " \"jsonrpc\": \"2.0\"," + + " \"method\": \"engine_getPayloadV4\"," + + " \"params\": [\"" + + payloadId + + "\"]," + + " \"id\": 67" + + "}"; + } + + private String createNewPayloadRequest( + final String executionPayload, final String parentBeaconBlockRoot) { + return "{" + + " \"jsonrpc\": \"2.0\"," + + " \"method\": \"engine_newPayloadV4\"," + + " \"params\": [" + + executionPayload + + ",[]," + + "\"" + + parentBeaconBlockRoot + + "\"" + + "]," + + " \"id\": 67" + + "}"; + } + + private static void waitFor(final long durationMilliSeconds) { + try { + Thread.sleep(durationMilliSeconds); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java new file mode 100644 index 00000000000..f1b2558a977 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java @@ -0,0 +1,125 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.ethereum; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.SetCodeAuthorization; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.account.Account; +import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.web3j.protocol.core.methods.response.TransactionReceipt; + +public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { + private static final String GENESIS_FILE = "/dev/dev_prague.json"; + private static final SECP256K1 secp256k1 = new SECP256K1(); + + public static final Address SEND_ALL_ETH_CONTRACT_ADDRESS = + Address.fromHexStringStrict("0000000000000000000000000000000000009999"); + + private final Account authorizer = + accounts.createAccount( + Address.fromHexStringStrict("8da48afC965480220a3dB9244771bd3afcB5d895")); + public static final Bytes AUTHORIZER_PRIVATE_KEY = + Bytes.fromHexString("11f2e7b6a734ab03fa682450e0d4681d18a944f8b83c99bf7b9b4de6c0f35ea1"); + + private final Account transactionSponsor = + accounts.createAccount( + Address.fromHexStringStrict("a05b21E5186Ce93d2a226722b85D6e550Ac7D6E3")); + public static final Bytes TRANSACTION_SPONSOR_PRIVATE_KEY = + Bytes.fromHexString("3a4ff6d22d7502ef2452368165422861c01a0f72f851793b372b87888dc3c453"); + + private BesuNode besuNode; + private PragueAcceptanceTestHelper testHelper; + + @BeforeEach + void setUp() throws IOException { + besuNode = besu.createExecutionEngineGenesisNode("besuNode", GENESIS_FILE); + cluster.start(besuNode); + + testHelper = new PragueAcceptanceTestHelper(besuNode, ethTransactions); + } + + /** + * At the beginning of the test both the authorizer and the transaction sponsor have a balance of + * 90000 ETH. The authorizer creates an authorization for a contract that send all its ETH to any + * given address. The transaction sponsor created a 7702 transaction with it and sends all the ETH + * from the authorizer to itself. The authorizer balance should be 0 and the transaction sponsor + * balance should be 180000 ETH minus the transaction costs. + */ + @Test + public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException { + + // 7702 transaction + final org.hyperledger.besu.datatypes.SetCodeAuthorization authorization = + SetCodeAuthorization.builder() + .chainId(BigInteger.valueOf(20211)) + .address(SEND_ALL_ETH_CONTRACT_ADDRESS) + .signAndBuild( + secp256k1.createKeyPair( + secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); + + final Transaction tx = + Transaction.builder() + .type(TransactionType.SET_CODE) + .chainId(BigInteger.valueOf(20211)) + .nonce(0) + .maxPriorityFeePerGas(Wei.of(1000000000)) + .maxFeePerGas(Wei.fromHexString("0x02540BE400")) + .gasLimit(1000000) + .to(Address.fromHexStringStrict(authorizer.getAddress())) + .value(Wei.ZERO) + .payload(Bytes32.leftPad(Bytes.fromHexString(transactionSponsor.getAddress()))) + .accessList(List.of()) + .setCodeTransactionPayloads(List.of(authorization)) + .signAndBuild( + secp256k1.createKeyPair( + secp256k1.createPrivateKey( + TRANSACTION_SPONSOR_PRIVATE_KEY.toUnsignedBigInteger()))); + + final String txHash = + besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString())); + testHelper.buildNewBlock(); + + Optional maybeTransactionReceipt = + besuNode.execute(ethTransactions.getTransactionReceipt(txHash)); + assertThat(maybeTransactionReceipt).isPresent(); + + cluster.verify(authorizer.balanceEquals(0)); + + final String gasPriceWithout0x = + maybeTransactionReceipt.get().getEffectiveGasPrice().substring(2); + final BigInteger txCost = + maybeTransactionReceipt.get().getGasUsed().multiply(new BigInteger(gasPriceWithout0x, 16)); + BigInteger expectedSponsorBalance = new BigInteger("180000000000000000000000").subtract(txCost); + cluster.verify(transactionSponsor.balanceEquals(Amount.wei(expectedSponsorBalance))); + } +} diff --git a/acceptance-tests/tests/src/test/resources/dev/dev_prague.json b/acceptance-tests/tests/src/test/resources/dev/dev_prague.json new file mode 100644 index 00000000000..26e59992d16 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/dev/dev_prague.json @@ -0,0 +1,112 @@ +{ + "config": { + "chainId":20211, + "homesteadBlock":0, + "eip150Block":0, + "eip155Block":0, + "eip158Block":0, + "byzantiumBlock":0, + "constantinopleBlock":0, + "petersburgBlock":0, + "istanbulBlock":0, + "muirGlacierBlock":0, + "berlinBlock":0, + "londonBlock":0, + "terminalTotalDifficulty":0, + "cancunTime":0, + "pragueTime":0, + "clique": { + "period": 5, + "epoch": 30000 + }, + "depositContractAddress": "0x4242424242424242424242424242424242424242" + }, + "nonce":"0x42", + "timestamp":"0x0", + "extraData":"0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit":"0x1C9C380", + "difficulty":"0x400000000", + "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase":"0x0000000000000000000000000000000000000000", + "alloc":{ + "a05b21E5186Ce93d2a226722b85D6e550Ac7D6E3": { + "privateKey": "3a4ff6d22d7502ef2452368165422861c01a0f72f851793b372b87888dc3c453", + "balance": "90000000000000000000000" + }, + "8da48afC965480220a3dB9244771bd3afcB5d895": { + "comment": "This account has signed a authorization for contract 0x0000000000000000000000000000000000009999 to send a 7702 transaction", + "privateKey": "11f2e7b6a734ab03fa682450e0d4681d18a944f8b83c99bf7b9b4de6c0f35ea1", + "balance": "90000000000000000000000" + }, + "0x0000000000000000000000000000000000009999": { + "comment": "Contract sends all its Ether to the address provided as a call data.", + "balance": "0", + "code": "5F5F5F5F475F355AF100", + "codeDecompiled": "PUSH0 PUSH0 PUSH0 PUSH0 SELFBALANCE PUSH0 CALLDATALOAD GAS CALL STOP", + "storage": {} + }, + "0xa4664C40AACeBD82A2Db79f0ea36C06Bc6A19Adb": { + "balance": "1000000000000000000000000000" + }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "comment": "This is the account used to sign the transaction that creates a validator exit", + "balance": "1000000000000000000000000000" + }, + "0x00A3ca265EBcb825B45F985A16CEFB49958cE017": { + "comment": "This is the runtime bytecode for the Withdrawal Request Smart Contract. It was created from the deployment transaction in EIP-7002 (https://eips.ethereum.org/EIPS/eip-7002#deployment)", + "balance": "0", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b36603814156101215760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012157600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f5460015460028282011161010f5750505f610115565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000004": "000000000000000000000000a4664C40AACeBD82A2Db79f0ea36C06Bc6A19Adb", + "0x0000000000000000000000000000000000000000000000000000000000000005": "b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee9922355", + "0x0000000000000000000000000000000000000000000000000000000000000006": "5d8601f0cb3bcc4ce1af9864779a416e00000000000000000000000000000000" + } + }, + "0x4242424242424242424242424242424242424242": { + "comment": "The deposit contract", + "balance": "0", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + } + }, + "number":"0x0", + "gasUsed":"0x0", + "parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas":"0x7" +} \ No newline at end of file diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java index c029983a33e..4ee34295f45 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java @@ -413,7 +413,7 @@ public void maxPrioritizedTxsPerTypeConfigFile() throws IOException { @Test public void maxPrioritizedTxsPerTypeWrongTxType() { internalTestFailure( - "Invalid value for option '--tx-pool-max-prioritized-by-type' (MAP): expected one of [FRONTIER, ACCESS_LIST, EIP1559, BLOB] (case-insensitive) but was 'WRONG_TYPE'", + "Invalid value for option '--tx-pool-max-prioritized-by-type' (MAP): expected one of [FRONTIER, ACCESS_LIST, EIP1559, BLOB, SET_CODE] (case-insensitive) but was 'WRONG_TYPE'", "--tx-pool-max-prioritized-by-type", "WRONG_TYPE=1"); } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java new file mode 100644 index 00000000000..b12e65de412 --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java @@ -0,0 +1,82 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.datatypes; + +import org.hyperledger.besu.crypto.SECPSignature; + +import java.math.BigInteger; +import java.util.Optional; + +/** + * SetCodeAuthorization is a data structure that represents the authorization to set code on a EOA + * account. + */ +public interface SetCodeAuthorization { + /** + * Return the chain id. + * + * @return chain id + */ + BigInteger chainId(); + + /** + * Return the address of the account which code will be used. + * + * @return address + */ + Address address(); + + /** + * Return the signature. + * + * @return signature + */ + SECPSignature signature(); + + /** + * Return the authorizer address. + * + * @return authorizer address of the EOA which will load the code into its account + */ + Optional
authorizer(); + + /** + * Return a valid nonce or empty otherwise. A nonce is valid if the size of the list is exactly 1 + * + * @return all the optional nonce + */ + Optional nonce(); + + /** + * Return the recovery id. + * + * @return byte + */ + byte v(); + + /** + * Return the r value of the signature. + * + * @return r value + */ + BigInteger r(); + + /** + * Return the s value of the signature. + * + * @return s value + */ + BigInteger s(); +} diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java index 4a77a898de9..d6751852bce 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java @@ -234,4 +234,18 @@ default Optional getMaxFeePerBlobGas() { * @return the size in bytes of the encoded transaction. */ int getSize(); + + /** + * Returns the set code transaction payload if this transaction is a 7702 transaction. + * + * @return the set code transaction payloads + */ + Optional> getAuthorizationList(); + + /** + * Returns the size of the authorization list. + * + * @return the size of the authorization list + */ + int authorizationListSize(); } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java index 984a4cc7467..df4a07193b8 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java @@ -27,10 +27,12 @@ public enum TransactionType { /** Eip1559 transaction type. */ EIP1559(0x02), /** Blob transaction type. */ - BLOB(0x03); + BLOB(0x03), + /** Eip7702 transaction type. */ + SET_CODE(0x04); private static final Set ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES = - Set.of(ACCESS_LIST, EIP1559, BLOB); + Set.of(ACCESS_LIST, EIP1559, BLOB, SET_CODE); private static final EnumSet LEGACY_FEE_MARKET_TRANSACTION_TYPES = EnumSet.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST); @@ -83,7 +85,8 @@ public static TransactionType of(final int serializedTypeValue) { TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559, - TransactionType.BLOB + TransactionType.BLOB, + TransactionType.SET_CODE }) .filter(transactionType -> transactionType.typeValue == serializedTypeValue) .findFirst() @@ -128,4 +131,13 @@ public boolean requiresChainId() { public boolean supportsBlob() { return this.equals(BLOB); } + + /** + * Does transaction type require code. + * + * @return the boolean + */ + public boolean requiresSetCode() { + return this.equals(SET_CODE); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java index 60ba8990a13..b5aebf4f5d7 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java @@ -33,7 +33,7 @@ public class EngineForkchoiceUpdatedV3 extends AbstractEngineForkchoiceUpdated { - private final Optional cancun; + private final Optional supportedHardFork; private static final Logger LOG = LoggerFactory.getLogger(EngineForkchoiceUpdatedV3.class); public EngineForkchoiceUpdatedV3( @@ -43,7 +43,11 @@ public EngineForkchoiceUpdatedV3( final MergeMiningCoordinator mergeCoordinator, final EngineCallListener engineCallListener) { super(vertx, protocolSchedule, protocolContext, mergeCoordinator, engineCallListener); - this.cancun = protocolSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun")); + this.supportedHardFork = + protocolSchedule.hardforkFor( + s -> + s.fork().name().equalsIgnoreCase("Cancun") + || s.fork().name().equalsIgnoreCase("Prague")); } @Override @@ -77,12 +81,12 @@ protected ValidationResult validateParameter( @Override protected ValidationResult validateForkSupported(final long blockTimestamp) { if (protocolSchedule.isPresent()) { - if (cancun.isPresent() && blockTimestamp >= cancun.get().milestone()) { + if (supportedHardFork.isPresent() && blockTimestamp >= supportedHardFork.get().milestone()) { return ValidationResult.valid(); } else { return ValidationResult.invalid( RpcErrorType.UNSUPPORTED_FORK, - "Cancun configured to start at timestamp: " + cancun.get().milestone()); + "Cancun configured to start at timestamp: " + supportedHardFork.get().milestone()); } } else { return ValidationResult.invalid( @@ -99,7 +103,7 @@ protected Optional isPayloadAttributesValid( return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError())); } else if (payloadAttributes.getTimestamp().longValue() == 0) { return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError())); - } else if (payloadAttributes.getTimestamp() < cancun.get().milestone()) { + } else if (payloadAttributes.getTimestamp() < supportedHardFork.get().milestone()) { return Optional.of(new JsonRpcErrorResponse(requestId, RpcErrorType.UNSUPPORTED_FORK)); } else { return Optional.empty(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java index 4912f81e498..a9b9a17018a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java @@ -19,7 +19,9 @@ import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonPropertyOrder({"status", "latestValidHash", "validationError"}) @@ -28,10 +30,11 @@ public class EnginePayloadStatusResult { Optional latestValidHash; Optional validationError; + @JsonCreator public EnginePayloadStatusResult( - final EngineStatus status, - final Hash latestValidHash, - final Optional validationError) { + @JsonProperty("status") final EngineStatus status, + @JsonProperty("latestValidHash") final Hash latestValidHash, + @JsonProperty("errorMessage") final Optional validationError) { this.status = status; this.latestValidHash = Optional.ofNullable(latestValidHash); this.validationError = validationError; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java index c8260d090c0..05b323cacb3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; import org.hyperledger.besu.datatypes.AccessListEntry; +import org.hyperledger.besu.datatypes.SetCodeAuthorization; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; @@ -30,6 +31,7 @@ @JsonPropertyOrder({ "accessList", + "authorizationList", "blockHash", "blockNumber", "chainId", @@ -91,6 +93,9 @@ public class TransactionCompleteResult implements TransactionResult { @JsonInclude(JsonInclude.Include.NON_NULL) private final List versionedHashes; + @JsonInclude(JsonInclude.Include.NON_NULL) + private final List authorizationList; + public TransactionCompleteResult(final TransactionWithMetadata tx) { final Transaction transaction = tx.getTransaction(); final TransactionType transactionType = transaction.getType(); @@ -125,7 +130,8 @@ public TransactionCompleteResult(final TransactionWithMetadata tx) { this.yParity = Quantity.create(transaction.getYParity()); this.v = (transactionType == TransactionType.ACCESS_LIST - || transactionType == TransactionType.EIP1559) + || transactionType == TransactionType.EIP1559) + || transactionType == TransactionType.SET_CODE ? Quantity.create(transaction.getYParity()) : null; } @@ -133,6 +139,7 @@ public TransactionCompleteResult(final TransactionWithMetadata tx) { this.r = Quantity.create(transaction.getR()); this.s = Quantity.create(transaction.getS()); this.versionedHashes = transaction.getVersionedHashes().orElse(null); + this.authorizationList = transaction.getAuthorizationList().orElse(null); } @JsonGetter(value = "accessList") @@ -246,4 +253,9 @@ public String getS() { public List getVersionedHashes() { return versionedHashes; } + + @JsonGetter(value = "authorizationList") + public List getAuthorizationList() { + return authorizationList; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java new file mode 100644 index 00000000000..ea9cae2fcb2 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java @@ -0,0 +1,260 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.encoding.SetCodeTransactionEncoder; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; + +public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetCodeAuthorization { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + public static final Bytes MAGIC = Bytes.fromHexString("05"); + + private final BigInteger chainId; + private final Address address; + private final Optional nonce; + private final SECPSignature signature; + private Optional
authorizer = Optional.empty(); + private boolean isAuthorityComputed = false; + + /** + * An access list entry as defined in EIP-7702 + * + * @param chainId can be either the current chain id or zero + * @param address the address from which the code will be set into the EOA account + * @param nonce an optional nonce after which this auth expires + * @param signature the signature of the EOA account which will be used to set the code + */ + public SetCodeAuthorization( + final BigInteger chainId, + final Address address, + final Optional nonce, + final SECPSignature signature) { + this.chainId = chainId; + this.address = address; + this.nonce = nonce; + this.signature = signature; + } + + /** + * Create access list entry. + * + * @param chainId can be either the current chain id or zero + * @param address the address from which the code will be set into the EOA account + * @param nonces the list of nonces + * @param v the recovery id + * @param r the r value of the signature + * @param s the s value of the signature + * @return SetCodeTransactionEntry + */ + @JsonCreator + public static org.hyperledger.besu.datatypes.SetCodeAuthorization createSetCodeAuthorizationEntry( + @JsonProperty("chainId") final BigInteger chainId, + @JsonProperty("address") final Address address, + @JsonProperty("nonce") final List nonces, + @JsonProperty("v") final byte v, + @JsonProperty("r") final BigInteger r, + @JsonProperty("s") final BigInteger s) { + return new SetCodeAuthorization( + chainId, + address, + Optional.ofNullable(nonces.get(0)), + SIGNATURE_ALGORITHM.get().createSignature(r, s, v)); + } + + @JsonProperty("chainId") + @Override + public BigInteger chainId() { + return chainId; + } + + @JsonProperty("address") + @Override + public Address address() { + return address; + } + + @JsonProperty("signature") + @Override + public SECPSignature signature() { + return signature; + } + + @Override + public Optional
authorizer() { + if (!isAuthorityComputed) { + authorizer = computeAuthority(); + isAuthorityComputed = true; + } + + return authorizer; + } + + @Override + public Optional nonce() { + return nonce; + } + + @JsonProperty("v") + @Override + public byte v() { + return signature.getRecId(); + } + + @JsonProperty("r") + @Override + public BigInteger r() { + return signature.getR(); + } + + @JsonProperty("s") + @Override + public BigInteger s() { + return signature.getS(); + } + + private Optional
computeAuthority() { + BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + SetCodeTransactionEncoder.encodeSingleSetCodeWithoutSignature(this, rlpOutput); + + final Hash hash = Hash.hash(Bytes.concatenate(MAGIC, rlpOutput.encoded())); + + return SIGNATURE_ALGORITHM + .get() + .recoverPublicKeyFromSignature(hash, signature) + .map(Address::extract); + } + + /** + * Create set code authorization with a builder. + * + * @return SetCodeAuthorization.Builder + */ + public static Builder builder() { + return new Builder(); + } + + /** Builder for SetCodeAuthorization. */ + public static class Builder { + private BigInteger chainId = BigInteger.ZERO; + private Address address; + private Optional nonce = Optional.empty(); + private SECPSignature signature; + + /** Create a new builder. */ + protected Builder() {} + + /** + * Set the optional chain id. + * + * @param chainId the chain id + * @return this builder + */ + public Builder chainId(final BigInteger chainId) { + this.chainId = chainId; + return this; + } + + /** + * Set the address of the authorized smart contract. + * + * @param address the address + * @return this builder + */ + public Builder address(final Address address) { + this.address = address; + return this; + } + + /** + * Set the optional nonce. + * + * @param nonce the optional nonce. + * @return this builder + */ + public Builder nonces(final Optional nonce) { + this.nonce = nonce; + return this; + } + + /** + * Set the signature of the authorizer account. + * + * @param signature the signature + * @return this builder + */ + public Builder signature(final SECPSignature signature) { + this.signature = signature; + return this; + } + + /** + * Sign the authorization with the given key pair and return the authorization. + * + * @param keyPair the key pair + * @return SetCodeAuthorization + */ + public org.hyperledger.besu.datatypes.SetCodeAuthorization signAndBuild(final KeyPair keyPair) { + final BytesValueRLPOutput output = new BytesValueRLPOutput(); + output.startList(); + output.writeBigIntegerScalar(chainId); + output.writeBytes(address); + output.startList(); + nonce.ifPresent(output::writeLongScalar); + output.endList(); + output.endList(); + + signature( + SIGNATURE_ALGORITHM + .get() + .sign(Hash.hash(Bytes.concatenate(MAGIC, output.encoded())), keyPair)); + return build(); + } + + /** + * Build the authorization. + * + * @return SetCodeAuthorization + */ + public org.hyperledger.besu.datatypes.SetCodeAuthorization build() { + if (address == null) { + throw new IllegalStateException("Address must be set"); + } + + if (signature == null) { + throw new IllegalStateException("Signature must be set"); + } + + return new SetCodeAuthorization(chainId, address, nonce, signature); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java index 7ca349721f9..513cfb8319d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.KZGCommitment; import org.hyperledger.besu.datatypes.KZGProof; +import org.hyperledger.besu.datatypes.SetCodeAuthorization; import org.hyperledger.besu.datatypes.Sha256Hash; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; @@ -38,6 +39,7 @@ import org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder; import org.hyperledger.besu.ethereum.core.encoding.BlobTransactionEncoder; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; +import org.hyperledger.besu.ethereum.core.encoding.SetCodeTransactionEncoder; import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; @@ -122,6 +124,7 @@ public class Transaction private final Optional> versionedHashes; private final Optional blobsWithCommitments; + private final Optional> maybeAuthorizationList; public static Builder builder() { return new Builder(); @@ -177,7 +180,8 @@ private Transaction( final Address sender, final Optional chainId, final Optional> versionedHashes, - final Optional blobsWithCommitments) { + final Optional blobsWithCommitments, + final Optional> maybeAuthorizationList) { if (!forCopy) { if (transactionType.requiresChainId()) { @@ -213,6 +217,12 @@ private Transaction( checkArgument( maxFeePerBlobGas.isPresent(), "Must specify max fee per blob gas for blob transaction"); } + + if (transactionType.requiresSetCode()) { + checkArgument( + maybeAuthorizationList.isPresent(), + "Must specify set code transaction payload for set code transaction"); + } } this.transactionType = transactionType; @@ -231,6 +241,7 @@ private Transaction( this.chainId = chainId; this.versionedHashes = versionedHashes; this.blobsWithCommitments = blobsWithCommitments; + this.maybeAuthorizationList = maybeAuthorizationList; } /** @@ -462,6 +473,7 @@ private Bytes32 getOrComputeSenderRecoveryHash() { payload, maybeAccessList, versionedHashes.orElse(null), + maybeAuthorizationList, chainId); } return hashNoSignature; @@ -668,6 +680,16 @@ public Optional getBlobsWithCommitments() { return blobsWithCommitments; } + @Override + public Optional> getAuthorizationList() { + return maybeAuthorizationList; + } + + @Override + public int authorizationListSize() { + return maybeAuthorizationList.map(List::size).orElse(0); + } + /** * Return the list of transaction hashes extracted from the collection of Transaction passed as * argument @@ -692,6 +714,7 @@ private static Bytes32 computeSenderRecoveryHash( final Bytes payload, final Optional> accessList, final List versionedHashes, + final Optional> authorizationList, final Optional chainId) { if (transactionType.requiresChainId()) { checkArgument(chainId.isPresent(), "Transaction type %s requires chainId", transactionType); @@ -736,6 +759,21 @@ private static Bytes32 computeSenderRecoveryHash( new IllegalStateException( "Developer error: the transaction should be guaranteed to have an access list here")), chainId); + case SET_CODE -> + setCodePreimage( + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + to, + value, + payload, + chainId, + accessList, + authorizationList.orElseThrow( + () -> + new IllegalStateException( + "Developer error: the transaction should be guaranteed to have a set code payload here"))); }; return keccak256(preimage); } @@ -873,6 +911,38 @@ private static Bytes accessListPreimage( return Bytes.concatenate(Bytes.of(TransactionType.ACCESS_LIST.getSerializedType()), encode); } + private static Bytes setCodePreimage( + final long nonce, + final Wei maxPriorityFeePerGas, + final Wei maxFeePerGas, + final long gasLimit, + final Optional
to, + final Wei value, + final Bytes payload, + final Optional chainId, + final Optional> accessList, + final List authorizationList) { + final Bytes encoded = + RLP.encode( + rlpOutput -> { + rlpOutput.startList(); + eip1559PreimageFields( + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + to, + value, + payload, + chainId, + accessList, + rlpOutput); + SetCodeTransactionEncoder.encodeSetCodeInner(authorizationList, rlpOutput); + rlpOutput.endList(); + }); + return Bytes.concatenate(Bytes.of(TransactionType.SET_CODE.getSerializedType()), encoded); + } + @Override public boolean equals(final Object other) { if (!(other instanceof Transaction that)) { @@ -1040,7 +1110,8 @@ public Transaction detachedCopy() { sender, chainId, detachedVersionedHashes, - detachedBlobsWithCommitments); + detachedBlobsWithCommitments, + maybeAuthorizationList); // copy also the computed fields, to avoid to recompute them copiedTx.sender = this.sender; @@ -1108,6 +1179,7 @@ public static class Builder { protected Optional v = Optional.empty(); protected List versionedHashes = null; private BlobsWithCommitments blobsWithCommitments; + protected Optional> setCodeTransactionPayloads = Optional.empty(); public Builder copiedFrom(final Transaction toCopy) { this.transactionType = toCopy.transactionType; @@ -1126,6 +1198,7 @@ public Builder copiedFrom(final Transaction toCopy) { this.chainId = toCopy.chainId; this.versionedHashes = toCopy.versionedHashes.orElse(null); this.blobsWithCommitments = toCopy.blobsWithCommitments.orElse(null); + this.setCodeTransactionPayloads = toCopy.maybeAuthorizationList; return this; } @@ -1219,6 +1292,8 @@ public Builder guessType() { transactionType = TransactionType.EIP1559; } else if (accessList.isPresent()) { transactionType = TransactionType.ACCESS_LIST; + } else if (setCodeTransactionPayloads.isPresent()) { + transactionType = TransactionType.SET_CODE; } else { transactionType = TransactionType.FRONTIER; } @@ -1248,7 +1323,8 @@ public Transaction build() { sender, chainId, Optional.ofNullable(versionedHashes), - Optional.ofNullable(blobsWithCommitments)); + Optional.ofNullable(blobsWithCommitments), + setCodeTransactionPayloads); } public Transaction signAndBuild(final KeyPair keys) { @@ -1275,6 +1351,7 @@ SECPSignature computeSignature(final KeyPair keys) { payload, accessList, versionedHashes, + setCodeTransactionPayloads, chainId), keys); } @@ -1298,5 +1375,11 @@ public Builder blobsWithCommitments(final BlobsWithCommitments blobsWithCommitme this.blobsWithCommitments = blobsWithCommitments; return this; } + + public Builder setCodeTransactionPayloads( + final List setCodeTransactionEntries) { + this.setCodeTransactionPayloads = Optional.ofNullable(setCodeTransactionEntries); + return this; + } } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java new file mode 100644 index 00000000000..80d59de83be --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java @@ -0,0 +1,116 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.AccessListEntry; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.SetCodeAuthorization; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; + +public class SetCodeTransactionDecoder { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + private SetCodeTransactionDecoder() { + // private constructor + } + + public static Transaction decode(final RLPInput input) { + input.enterList(); + final BigInteger chainId = input.readBigIntegerScalar(); + final Transaction.Builder builder = + Transaction.builder() + .type(TransactionType.SET_CODE) + .chainId(chainId) + .nonce(input.readLongScalar()) + .maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar())) + .maxFeePerGas(Wei.of(input.readUInt256Scalar())) + .gasLimit(input.readLongScalar()) + .to(input.readBytes(v -> v.isEmpty() ? null : Address.wrap(v))) + .value(Wei.of(input.readUInt256Scalar())) + .payload(input.readBytes()) + .accessList( + input.readList( + accessListEntryRLPInput -> { + accessListEntryRLPInput.enterList(); + final AccessListEntry accessListEntry = + new AccessListEntry( + Address.wrap(accessListEntryRLPInput.readBytes()), + accessListEntryRLPInput.readList(RLPInput::readBytes32)); + accessListEntryRLPInput.leaveList(); + return accessListEntry; + })) + .setCodeTransactionPayloads( + input.readList( + setCodeTransactionPayloadsRLPInput -> + decodeInnerPayload(setCodeTransactionPayloadsRLPInput))); + + final byte recId = (byte) input.readUnsignedByteScalar(); + final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); + final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger(); + + input.leaveList(); + + final Transaction transaction = + builder.signature(SIGNATURE_ALGORITHM.get().createSignature(r, s, recId)).build(); + + return transaction; + } + + public static org.hyperledger.besu.datatypes.SetCodeAuthorization decodeInnerPayload( + final RLPInput input) { + input.enterList(); + final BigInteger chainId = input.readBigIntegerScalar(); + final Address address = Address.wrap(input.readBytes()); + + Optional nonce = Optional.empty(); + + if (!input.nextIsList()) { + throw new IllegalArgumentException("Optional nonce must be an list, but isn't"); + } + + final long noncesSize = input.nextSize(); + + input.enterList(); + if (noncesSize == 1) { + nonce = Optional.ofNullable(input.readLongScalar()); + } else if (noncesSize > 1) { + throw new IllegalArgumentException("Nonce list may only have 1 member, if any"); + } + input.leaveList(); + + final byte yParity = (byte) input.readUnsignedByteScalar(); + final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); + final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger(); + + input.leaveList(); + + final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, yParity); + + return new SetCodeAuthorization(chainId, address, nonce, signature); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java new file mode 100644 index 00000000000..05808f3129f --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java @@ -0,0 +1,89 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder.writeAccessList; +import static org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder.writeSignatureAndRecoveryId; + +import org.hyperledger.besu.datatypes.SetCodeAuthorization; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; + +public class SetCodeTransactionEncoder { + + private SetCodeTransactionEncoder() { + // private constructor + } + + public static void encodeSetCodeInner( + final List payloads, final RLPOutput rlpOutput) { + rlpOutput.startList(); + payloads.forEach(payload -> encodeSingleSetCode(payload, rlpOutput)); + rlpOutput.endList(); + } + + public static void encodeSingleSetCodeWithoutSignature( + final SetCodeAuthorization payload, final RLPOutput rlpOutput) { + rlpOutput.startList(); + encodeAuthorizationDetails(payload, rlpOutput); + rlpOutput.endList(); + } + + public static void encodeSingleSetCode( + final SetCodeAuthorization payload, final RLPOutput rlpOutput) { + rlpOutput.startList(); + encodeAuthorizationDetails(payload, rlpOutput); + rlpOutput.writeIntScalar(payload.signature().getRecId()); + rlpOutput.writeBigIntegerScalar(payload.signature().getR()); + rlpOutput.writeBigIntegerScalar(payload.signature().getS()); + rlpOutput.endList(); + } + + private static void encodeAuthorizationDetails( + final SetCodeAuthorization payload, final RLPOutput rlpOutput) { + rlpOutput.writeBigIntegerScalar(payload.chainId()); + rlpOutput.writeBytes(payload.address().copy()); + rlpOutput.startList(); + payload.nonce().ifPresent(rlpOutput::writeLongScalar); + rlpOutput.endList(); + } + + public static void encode(final Transaction transaction, final RLPOutput out) { + out.startList(); + out.writeBigIntegerScalar(transaction.getChainId().orElseThrow()); + out.writeLongScalar(transaction.getNonce()); + out.writeUInt256Scalar(transaction.getMaxPriorityFeePerGas().orElseThrow()); + out.writeUInt256Scalar(transaction.getMaxFeePerGas().orElseThrow()); + out.writeLongScalar(transaction.getGasLimit()); + out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY)); + out.writeUInt256Scalar(transaction.getValue()); + out.writeBytes(transaction.getPayload()); + writeAccessList(out, transaction.getAccessList()); + encodeSetCodeInner( + transaction + .getAuthorizationList() + .orElseThrow( + () -> + new IllegalStateException( + "Developer error: the transaction should be guaranteed to have a set code payload here")), + out); + writeSignatureAndRecoveryId(transaction, out); + out.endList(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java index fe6687cdb5c..2a63d8f24ba 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java @@ -40,7 +40,9 @@ interface Decoder { TransactionType.EIP1559, EIP1559TransactionDecoder::decode, TransactionType.BLOB, - BlobTransactionDecoder::decode); + BlobTransactionDecoder::decode, + TransactionType.SET_CODE, + SetCodeTransactionDecoder::decode); private static final ImmutableMap POOLED_TRANSACTION_DECODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionDecoder::decode); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java index 20fe75885e4..49959756219 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java @@ -39,7 +39,9 @@ interface Encoder { TransactionType.EIP1559, EIP1559TransactionEncoder::encode, TransactionType.BLOB, - BlobTransactionEncoder::encode); + BlobTransactionEncoder::encode, + TransactionType.SET_CODE, + SetCodeTransactionEncoder::encode); private static final ImmutableMap POOLED_TRANSACTION_ENCODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionEncoder::encode); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java new file mode 100644 index 00000000000..f79e2c98e3a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java @@ -0,0 +1,88 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.AccountState; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.AuthorizedCodeService; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.math.BigInteger; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthorityProcessor { + private static final Logger LOG = LoggerFactory.getLogger(AuthorityProcessor.class); + + private final Optional maybeChainId; + + public AuthorityProcessor(final Optional maybeChainId) { + this.maybeChainId = maybeChainId; + } + + public void addContractToAuthority( + final WorldUpdater worldUpdater, + final AuthorizedCodeService authorizedCodeService, + final Transaction transaction) { + + transaction + .getAuthorizationList() + .get() + .forEach( + payload -> + payload + .authorizer() + .ifPresent( + authorityAddress -> { + LOG.trace("Set code authority: {}", authorityAddress); + + if (maybeChainId.isPresent() + && !payload.chainId().equals(BigInteger.ZERO) + && !maybeChainId.get().equals(payload.chainId())) { + return; + } + + final Optional maybeAccount = + Optional.ofNullable(worldUpdater.getAccount(authorityAddress)); + final long accountNonce = + maybeAccount.map(AccountState::getNonce).orElse(0L); + + if (payload.nonce().isPresent() + && !payload.nonce().get().equals(accountNonce)) { + return; + } + + if (authorizedCodeService.hasAuthorizedCode(authorityAddress)) { + return; + } + + Optional codeAccount = + Optional.ofNullable(worldUpdater.get(payload.address())); + final Bytes code; + if (codeAccount.isPresent()) { + code = codeAccount.get().getCode(); + } else { + code = Bytes.EMPTY; + } + + authorizedCodeService.addAuthorizedCode(authorityAddress, code); + })); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index 4ae30f8de9a..6f09df923eb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -595,7 +595,8 @@ static ProtocolSpecBuilder cancunDefinition( true, evmConfiguration.evmStackSize(), feeMarket, - CoinbaseFeePriceCalculator.eip1559())) + CoinbaseFeePriceCalculator.eip1559(), + new AuthorityProcessor(chainId))) // change to check for max blob gas per block for EIP-4844 .transactionValidatorFactoryBuilder( (evm, gasLimitCalculator, feeMarket) -> @@ -658,6 +659,23 @@ static ProtocolSpecBuilder pragueDefinition( // EIP-7002 Withdrawals / EIP-6610 Deposits / EIP-7685 Requests .requestProcessorCoordinator(pragueRequestsProcessors(depositContractAddress)) + // change to accept EIP-7702 transactions + .transactionValidatorFactoryBuilder( + (evm, gasLimitCalculator, feeMarket) -> + new TransactionValidatorFactory( + evm.getGasCalculator(), + gasLimitCalculator, + feeMarket, + true, + chainId, + Set.of( + TransactionType.FRONTIER, + TransactionType.ACCESS_LIST, + TransactionType.EIP1559, + TransactionType.BLOB, + TransactionType.SET_CODE), + evm.getEvmVersion().getMaxInitcodeSize())) + // EIP-2935 Blockhash processor .blockHashProcessor(new PragueBlockHashProcessor()) .name("Prague"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index 53801d14cd3..9ad1db7d896 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -42,6 +42,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.AuthorizedCodeService; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Deque; @@ -80,6 +81,8 @@ public class MainnetTransactionProcessor { protected final FeeMarket feeMarket; private final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator; + private final Optional maybeAuthorityProcessor; + public MainnetTransactionProcessor( final GasCalculator gasCalculator, final TransactionValidatorFactory transactionValidatorFactory, @@ -90,6 +93,30 @@ public MainnetTransactionProcessor( final int maxStackSize, final FeeMarket feeMarket, final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator) { + this( + gasCalculator, + transactionValidatorFactory, + contractCreationProcessor, + messageCallProcessor, + clearEmptyAccounts, + warmCoinbase, + maxStackSize, + feeMarket, + coinbaseFeePriceCalculator, + null); + } + + public MainnetTransactionProcessor( + final GasCalculator gasCalculator, + final TransactionValidatorFactory transactionValidatorFactory, + final AbstractMessageProcessor contractCreationProcessor, + final AbstractMessageProcessor messageCallProcessor, + final boolean clearEmptyAccounts, + final boolean warmCoinbase, + final int maxStackSize, + final FeeMarket feeMarket, + final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator, + final AuthorityProcessor maybeAuthorityProcessor) { this.gasCalculator = gasCalculator; this.transactionValidatorFactory = transactionValidatorFactory; this.contractCreationProcessor = contractCreationProcessor; @@ -99,6 +126,7 @@ public MainnetTransactionProcessor( this.maxStackSize = maxStackSize; this.feeMarket = feeMarket; this.coinbaseFeePriceCalculator = coinbaseFeePriceCalculator; + this.maybeAuthorityProcessor = Optional.ofNullable(maybeAuthorityProcessor); } /** @@ -259,6 +287,8 @@ public TransactionProcessingResult processTransaction( final PrivateMetadataUpdater privateMetadataUpdater, final Wei blobGasPrice) { try { + final AuthorizedCodeService authorizedCodeService = new AuthorizedCodeService(); + worldState.setAuthorizedCodeService(authorizedCodeService); final var transactionValidator = transactionValidatorFactory.get(); LOG.trace("Starting execution of {}", transaction); ValidationResult validationResult = @@ -332,15 +362,19 @@ public TransactionProcessingResult processTransaction( transaction.getPayload(), transaction.isContractCreation()); final long accessListGas = gasCalculator.accessListGasCost(accessListEntries.size(), accessListStorageCount); - final long gasAvailable = transaction.getGasLimit() - intrinsicGas - accessListGas; + final long setCodeGas = gasCalculator.setCodeListGasCost(transaction.authorizationListSize()); + final long gasAvailable = + transaction.getGasLimit() - intrinsicGas - accessListGas - setCodeGas; LOG.trace( - "Gas available for execution {} = {} - {} - {} (limit - intrinsic - accessList)", + "Gas available for execution {} = {} - {} - {} - {} (limit - intrinsic - accessList - setCode)", gasAvailable, transaction.getGasLimit(), intrinsicGas, - accessListGas); + accessListGas, + setCodeGas); final WorldUpdater worldUpdater = worldState.updater(); + worldUpdater.setAuthorizedCodeService(authorizedCodeService); final ImmutableMap.Builder contextVariablesBuilder = ImmutableMap.builder() .put(KEY_IS_PERSISTING_PRIVATE_STATE, isPersistingPrivateState) @@ -374,6 +408,15 @@ public TransactionProcessingResult processTransaction( if (transaction.getVersionedHashes().isPresent()) { commonMessageFrameBuilder.versionedHashes( Optional.of(transaction.getVersionedHashes().get().stream().toList())); + } else if (transaction.getAuthorizationList().isPresent()) { + if (maybeAuthorityProcessor.isEmpty()) { + throw new RuntimeException("Authority processor is required for 7702 transactions"); + } + + maybeAuthorityProcessor + .get() + .addContractToAuthority(worldUpdater, authorizedCodeService, transaction); + addressList.addAll(authorizedCodeService.getAuthorities()); } else { commonMessageFrameBuilder.versionedHashes(Optional.empty()); } @@ -392,6 +435,7 @@ public TransactionProcessingResult processTransaction( .contract(contractAddress) .inputData(initCodeBytes.slice(code.getSize())) .code(code) + .authorizedCodeService(authorizedCodeService) .build(); } else { @SuppressWarnings("OptionalGetWithoutIsPresent") // isContractCall tests isPresent @@ -407,6 +451,7 @@ public TransactionProcessingResult processTransaction( maybeContract .map(c -> messageCallProcessor.getCodeFromEVM(c.getCodeHash(), c.getCode())) .orElse(CodeV0.EMPTY_CODE)) + .authorizedCodeService(authorizedCodeService) .build(); } Deque messageFrameStack = initialFrame.getMessageFrameStack(); @@ -485,6 +530,7 @@ public TransactionProcessingResult processTransaction( coinbaseCalculator.price(usedGas, transactionGasPrice, blockHeader.getBaseFee()); coinbase.incrementBalance(coinbaseWeiDelta); + authorizedCodeService.resetAuthorities(); operationTracer.traceEndTransaction( worldUpdater, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 9d90a409a5e..481ce70e052 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -189,7 +189,8 @@ private ValidationResult validateCostAndFee( final long intrinsicGasCost = gasCalculator.transactionIntrinsicGasCost( transaction.getPayload(), transaction.isContractCreation()) - + (transaction.getAccessList().map(gasCalculator::accessListGasCost).orElse(0L)); + + (transaction.getAccessList().map(gasCalculator::accessListGasCost).orElse(0L)) + + gasCalculator.setCodeListGasCost(transaction.authorizationListSize()); if (Long.compareUnsigned(intrinsicGasCost, transaction.getGasLimit()) > 0) { return ValidationResult.invalid( TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateMutableWorldStateUpdater.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateMutableWorldStateUpdater.java index fc20db7f813..b4e8e285617 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateMutableWorldStateUpdater.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateMutableWorldStateUpdater.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.AuthorizedCodeService; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Collection; @@ -30,35 +31,39 @@ public class PrivateMutableWorldStateUpdater implements WorldUpdater { protected final WorldUpdater publicWorldUpdater; protected final WorldUpdater privateWorldUpdater; + private AuthorizedCodeService authorizedCodeService; public PrivateMutableWorldStateUpdater( final WorldUpdater publicWorldUpdater, final WorldUpdater privateWorldUpdater) { this.publicWorldUpdater = publicWorldUpdater; this.privateWorldUpdater = privateWorldUpdater; + this.authorizedCodeService = new AuthorizedCodeService(); } @Override public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { - return privateWorldUpdater.createAccount(address, nonce, balance); + return authorizedCodeService.processMutableAccount( + this, privateWorldUpdater.createAccount(address, nonce, balance), address); } @Override public MutableAccount createAccount(final Address address) { - return privateWorldUpdater.createAccount(address); + return authorizedCodeService.processMutableAccount( + this, privateWorldUpdater.createAccount(address), address); } @Override public MutableAccount getAccount(final Address address) { final MutableAccount privateAccount = privateWorldUpdater.getAccount(address); if (privateAccount != null && !privateAccount.isEmpty()) { - return privateAccount; + return authorizedCodeService.processMutableAccount(this, privateAccount, address); } final MutableAccount publicAccount = publicWorldUpdater.getAccount(address); if (publicAccount != null && !publicAccount.isEmpty()) { publicAccount.becomeImmutable(); - return publicAccount; + return authorizedCodeService.processMutableAccount(this, publicAccount, address); } - return privateAccount; + return authorizedCodeService.processMutableAccount(this, privateAccount, address); } @Override @@ -104,4 +109,9 @@ public WorldUpdater updater() { public Optional parentUpdater() { return privateWorldUpdater.parentUpdater(); } + + @Override + public void setAuthorizedCodeService(final AuthorizedCodeService authorizedCodeService) { + this.authorizedCodeService = authorizedCodeService; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java index 149cefbe540..b5bae0aed9b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java @@ -136,7 +136,8 @@ public MutableAccount createAccount(final Address address, final long nonce, fin accountsToUpdate.put(address, diffBasedValue); } else if (diffBasedValue.getUpdated() != null) { if (diffBasedValue.getUpdated().isEmpty()) { - return track(new UpdateTrackingAccount<>(diffBasedValue.getUpdated())); + return authorizedCodeService.processMutableAccount( + this, track(new UpdateTrackingAccount<>(diffBasedValue.getUpdated())), address); } else { throw new IllegalStateException("Cannot create an account when one already exists"); } @@ -153,7 +154,8 @@ public MutableAccount createAccount(final Address address, final long nonce, fin Hash.EMPTY, true); diffBasedValue.setUpdated(newAccount); - return track(new UpdateTrackingAccount<>(newAccount)); + return authorizedCodeService.processMutableAccount( + this, track(new UpdateTrackingAccount<>(newAccount)), address); } @Override diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java index c5b797403a8..2709b94e294 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java @@ -376,6 +376,7 @@ public Transaction transaction( case EIP1559 -> eip1559Transaction(payload, to); case ACCESS_LIST -> accessListTransaction(payload, to); case BLOB -> blobTransaction(payload, to); + case SET_CODE -> null; // no default, all types accounted for. }; } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java index d016b7f4e5e..3f7049be399 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java @@ -92,6 +92,8 @@ public Transaction createTransaction(final KeyPair keys) { builder.versionedHashes(versionedHashes.get()); } break; + case SET_CODE: + break; } to.ifPresent(builder::to); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java new file mode 100644 index 00000000000..610b5906f54 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java @@ -0,0 +1,120 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.SetCodeAuthorization; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; + +import java.math.BigInteger; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class SetCodeTransactionDecoderTest { + + @Test + void shouldDecodeInnerPayloadWithNonce() { + // "0xd80194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c105" + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromHexString( + "0xf85b0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c18080a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"), + true); + final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input); + + assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE); + assertThat(authorization.address()) + .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); + assertThat(authorization.nonce().get()).isEqualTo(0L); + + final SECPSignature signature = authorization.signature(); + assertThat(signature.getRecId()).isEqualTo((byte) 0); + assertThat(signature.getR().toString(16)) + .isEqualTo("840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5"); + assertThat(signature.getS().toString(16)) + .isEqualTo("3b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"); + } + + @Test + void shouldDecodeInnerPayloadWithoutNonce() { + // "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromHexString( + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"), + true); + final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input); + + assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE); + assertThat(authorization.address()) + .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); + assertThat(authorization.nonce()).isEmpty(); + + final SECPSignature signature = authorization.signature(); + assertThat(signature.getRecId()).isEqualTo((byte) 1); + assertThat(signature.getR().toString(16)) + .isEqualTo("dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148"); + assertThat(signature.getS().toString(16)) + .isEqualTo("25b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"); + } + + @Test + void shouldThrowInnerPayloadWithMultipleNonces() { + // "d90194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c20107" + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromHexString( + "0xf85c0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c2010201a0401b5d4ebe88306448115d1a46a30e5ad1136f2818b4ebb0733d9c4efffd135aa0753ff1dbce6db504ecb9635a64d8c4506ff887e2d2a0d2b7175baf94c849eccc"), + true); + + assertThrows( + IllegalArgumentException.class, + () -> { + SetCodeTransactionDecoder.decodeInnerPayload(input); + }); + } + + @Test + void shouldDecodeInnerPayloadWithoutNonceAndChainIdZero() { + // "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromHexString( + "0xf85a0094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"), + true); + final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input); + + assertThat(authorization.chainId()).isEqualTo(BigInteger.ZERO); + assertThat(authorization.address()) + .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); + assertThat(authorization.nonce().isEmpty()).isTrue(); + + final SECPSignature signature = authorization.signature(); + assertThat(signature.getRecId()).isEqualTo((byte) 1); + assertThat(signature.getR().toString(16)) + .isEqualTo("25c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2"); + assertThat(signature.getS().toString(16)) + .isEqualTo("3c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java new file mode 100644 index 00000000000..89211f4ba31 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java @@ -0,0 +1,122 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.SetCodeAuthorization; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SetCodeTransactionEncoderTest { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + BytesValueRLPOutput output; + + @BeforeEach + void setUp() { + output = new BytesValueRLPOutput(); + } + + @Test + void shouldEncodeSingleSetCodeWithNonce() { + // "0xd80194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c105" + + final SetCodeAuthorization authorization = + new SetCodeAuthorization( + BigInteger.ONE, + Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), + Optional.of(0L), + SIGNATURE_ALGORITHM + .get() + .createSignature( + new BigInteger( + "840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5", 16), + new BigInteger( + "3b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99", 16), + (byte) 0)); + + SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output); + + assertThat(output.encoded()) + .isEqualTo( + Bytes.fromHexString( + "0xf85b0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c18080a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99")); + } + + @Test + void shouldEncodeSingleSetCodeWithoutNonce() { + // "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final SetCodeAuthorization authorization = + new SetCodeAuthorization( + BigInteger.ONE, + Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), + Optional.empty(), + SIGNATURE_ALGORITHM + .get() + .createSignature( + new BigInteger( + "dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148", 16), + new BigInteger( + "25b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031", 16), + (byte) 1)); + + SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output); + + assertThat(output.encoded()) + .isEqualTo( + Bytes.fromHexString( + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031")); + } + + @Test + void shouldEncodeSingleSetCodeWithoutNonceAndChainIdZero() { + // "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final SetCodeAuthorization authorization = + new SetCodeAuthorization( + BigInteger.ZERO, + Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), + Optional.empty(), + SIGNATURE_ALGORITHM + .get() + .createSignature( + new BigInteger( + "25c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2", 16), + new BigInteger( + "3c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df", 16), + (byte) 1)); + + SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output); + + assertThat(output.encoded()) + .isEqualTo( + Bytes.fromHexString( + "0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df")); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java index d541cbd99aa..b2f5718dad3 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java @@ -39,6 +39,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.evm.worldstate.WorldView; +import java.math.BigInteger; import java.util.List; import java.util.Optional; import java.util.Set; @@ -87,7 +88,8 @@ MainnetTransactionProcessor createTransactionProcessor(final boolean warmCoinbas warmCoinbase, MAX_STACK_SIZE, FeeMarket.legacy(), - CoinbaseFeePriceCalculator.frontier()); + CoinbaseFeePriceCalculator.frontier(), + new AuthorityProcessor(Optional.of(BigInteger.ONE))); } @Test diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java index 79e8c368e06..6ce8f47a7aa 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java @@ -131,6 +131,7 @@ private int computeMemorySize() { case ACCESS_LIST -> computeAccessListMemorySize(); case EIP1559 -> computeEIP1559MemorySize(); case BLOB -> computeBlobMemorySize(); + case SET_CODE -> computeSetCodeMemorySize(); } + PENDING_TRANSACTION_MEMORY_SIZE; } @@ -164,6 +165,10 @@ private int computeBlobMemorySize() { + computeBlobWithCommitmentsMemorySize(); } + private int computeSetCodeMemorySize() { + return 0; + } + private int computeBlobWithCommitmentsMemorySize() { final int blobCount = transaction.getBlobCount(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java index e717adbed12..bbd4e7322ff 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java @@ -128,7 +128,7 @@ protected Transaction createTransaction( final TransactionType txType = TransactionType.values()[randomizeTxType.nextInt(4)]; return switch (txType) { - case FRONTIER, ACCESS_LIST, EIP1559 -> + case FRONTIER, ACCESS_LIST, EIP1559, SET_CODE -> createTransaction(txType, nonce, maxGasPrice, payloadSize, keys); case BLOB -> createTransaction( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java index a56222345ce..c5c2bdcf976 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java @@ -1377,6 +1377,7 @@ private PendingTransaction getOrCreate( case ACCESS_LIST -> createAccessListPendingTransaction(sender, n); case EIP1559 -> createEIP1559PendingTransaction(sender, n); case BLOB -> createBlobPendingTransaction(sender, n); + case SET_CODE -> throw new UnsupportedOperationException(); }); } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java index ee6c5d5f709..6f222166b34 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.core.ConsolidationRequest; import org.hyperledger.besu.ethereum.core.DepositRequest; import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.SetCodeAuthorization; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.WithdrawalRequest; @@ -212,6 +213,69 @@ protected static List extractTransactions( builder.accessList(entries); } + if (txNode.has("authorizationList")) { + JsonNode authorizationList = txNode.get("authorizationList"); + + if (!authorizationList.isArray()) { + out.printf( + "TX json node unparseable: expected authorizationList to be an array - %s%n", + txNode); + continue; + } + + List authorizations = + new ArrayList<>(authorizationList.size()); + for (JsonNode entryAsJson : authorizationList) { + final BigInteger authorizationChainId = + Bytes.fromHexStringLenient(entryAsJson.get("chainId").textValue()) + .toUnsignedBigInteger(); + final Address authorizationAddress = + Address.fromHexString(entryAsJson.get("address").textValue()); + + JsonNode nonces = entryAsJson.get("nonce"); + + if (nonces == null) { + out.printf( + "TX json node unparseable: expected nonce field to be provided - %s%n", + txNode); + continue; + } + + List authorizationNonces; + if (nonces.isArray()) { + authorizationNonces = new ArrayList<>(nonces.size()); + for (JsonNode nonceAsJson : nonces) { + authorizationNonces.add( + Bytes.fromHexStringLenient(nonceAsJson.textValue()).toLong()); + } + } else { + authorizationNonces = + List.of(Bytes.fromHexStringLenient(nonces.textValue()).toLong()); + } + + final byte authorizationV = + Bytes.fromHexStringLenient(entryAsJson.get("v").textValue()) + .toUnsignedBigInteger() + .byteValueExact(); + final BigInteger authorizationR = + Bytes.fromHexStringLenient(entryAsJson.get("r").textValue()) + .toUnsignedBigInteger(); + final BigInteger authorizationS = + Bytes.fromHexStringLenient(entryAsJson.get("s").textValue()) + .toUnsignedBigInteger(); + + authorizations.add( + SetCodeAuthorization.createSetCodeAuthorizationEntry( + authorizationChainId, + authorizationAddress, + authorizationNonces, + authorizationV, + authorizationR, + authorizationS)); + } + builder.setCodeTransactionPayloads(authorizations); + } + if (txNode.has("blobVersionedHashes")) { JsonNode blobVersionedHashes = txNode.get("blobVersionedHashes"); if (!blobVersionedHashes.isArray()) { @@ -255,7 +319,10 @@ protected static List extractTransactions( Bytes.fromHexStringLenient(txNode.get("s").textValue()) .toUnsignedBigInteger(), v.byteValueExact())); - transactions.add(builder.build()); + + final Transaction tx = builder.build(); + + transactions.add(tx); } } } else { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java new file mode 100644 index 00000000000..46acac74f93 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java @@ -0,0 +1,100 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.account; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; + +import java.util.NavigableMap; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** Wraps an EOA account and includes authorized code to be run on behalf of it. */ +public class AuthorizedCodeAccount implements Account { + private final Account wrappedAccount; + private final Bytes authorizedCode; + + /** The hash of the authorized code. */ + protected Hash codeHash = null; + + /** + * Creates a new AuthorizedCodeAccount. + * + * @param wrappedAccount the account that has authorized code to be loaded into it. + * @param authorizedCode the authorized code. + */ + public AuthorizedCodeAccount(final Account wrappedAccount, final Bytes authorizedCode) { + this.wrappedAccount = wrappedAccount; + this.authorizedCode = authorizedCode; + } + + @Override + public Address getAddress() { + return wrappedAccount.getAddress(); + } + + @Override + public boolean isStorageEmpty() { + return wrappedAccount.isStorageEmpty(); + } + + @Override + public Hash getAddressHash() { + return wrappedAccount.getAddressHash(); + } + + @Override + public long getNonce() { + return wrappedAccount.getNonce(); + } + + @Override + public Wei getBalance() { + return wrappedAccount.getBalance(); + } + + @Override + public Bytes getCode() { + return authorizedCode; + } + + @Override + public Hash getCodeHash() { + if (codeHash == null) { + codeHash = authorizedCode.equals(Bytes.EMPTY) ? Hash.EMPTY : Hash.hash(authorizedCode); + } + + return codeHash; + } + + @Override + public UInt256 getStorageValue(final UInt256 key) { + return wrappedAccount.getStorageValue(key); + } + + @Override + public UInt256 getOriginalStorageValue(final UInt256 key) { + return wrappedAccount.getOriginalStorageValue(key); + } + + @Override + public NavigableMap storageEntriesFrom( + final Bytes32 startKeyHash, final int limit) { + return wrappedAccount.storageEntriesFrom(startKeyHash, limit); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java new file mode 100644 index 00000000000..6d4e30a9c2e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java @@ -0,0 +1,138 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.account; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; + +import java.util.Map; +import java.util.NavigableMap; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** Wraps a mutable EOA account and includes authorized code to be run on behalf of it. */ +public class MutableAuthorizedCodeAccount implements MutableAccount { + + private final MutableAccount wrappedAccount; + private final Bytes authorizedCode; + + /** The hash of the authorized code. */ + protected Hash codeHash = null; + + /** + * Creates a new MutableAuthorizedCodeAccount. + * + * @param wrappedAccount the account that has authorized code to be loaded into it. + * @param authorizedCode the authorized code. + */ + public MutableAuthorizedCodeAccount( + final MutableAccount wrappedAccount, final Bytes authorizedCode) { + this.wrappedAccount = wrappedAccount; + this.authorizedCode = authorizedCode; + } + + @Override + public Address getAddress() { + return wrappedAccount.getAddress(); + } + + @Override + public boolean isStorageEmpty() { + return wrappedAccount.isStorageEmpty(); + } + + @Override + public Hash getAddressHash() { + return wrappedAccount.getAddressHash(); + } + + @Override + public long getNonce() { + return wrappedAccount.getNonce(); + } + + @Override + public Wei getBalance() { + return wrappedAccount.getBalance(); + } + + @Override + public Bytes getCode() { + return authorizedCode; + } + + @Override + public Hash getCodeHash() { + if (codeHash == null) { + codeHash = authorizedCode.equals(Bytes.EMPTY) ? Hash.EMPTY : Hash.hash(authorizedCode); + } + + return codeHash; + } + + @Override + public UInt256 getStorageValue(final UInt256 key) { + return wrappedAccount.getStorageValue(key); + } + + @Override + public UInt256 getOriginalStorageValue(final UInt256 key) { + return wrappedAccount.getOriginalStorageValue(key); + } + + @Override + public NavigableMap storageEntriesFrom( + final Bytes32 startKeyHash, final int limit) { + return wrappedAccount.storageEntriesFrom(startKeyHash, limit); + } + + @Override + public void setNonce(final long value) { + wrappedAccount.setNonce(value); + } + + @Override + public void setBalance(final Wei value) { + wrappedAccount.setBalance(value); + } + + @Override + public void setCode(final Bytes code) { + throw new RuntimeException("Cannot set code on an AuthorizedCodeAccount"); + } + + @Override + public void setStorageValue(final UInt256 key, final UInt256 value) { + wrappedAccount.setStorageValue(key, value); + } + + @Override + public void clearStorage() { + wrappedAccount.clearStorage(); + } + + @Override + public Map getUpdatedStorage() { + return wrappedAccount.getUpdatedStorage(); + } + + @Override + public void becomeImmutable() { + wrappedAccount.becomeImmutable(); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java index f52e788babf..c64ca3744fd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.AuthorizedCodeService; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Collection; @@ -34,6 +35,8 @@ public class SimpleWorld implements WorldUpdater { /** The Accounts. */ Map accounts = new HashMap<>(); + private AuthorizedCodeService authorizedCodeService; + /** Instantiates a new Simple world. */ public SimpleWorld() { this(null); @@ -46,6 +49,7 @@ public SimpleWorld() { */ public SimpleWorld(final SimpleWorld parent) { this.parent = parent; + this.authorizedCodeService = new AuthorizedCodeService(); } @Override @@ -56,11 +60,11 @@ public WorldUpdater updater() { @Override public Account get(final Address address) { if (accounts.containsKey(address)) { - return accounts.get(address); + return authorizedCodeService.processAccount(this, accounts.get(address), address); } else if (parent != null) { - return parent.get(address); + return authorizedCodeService.processAccount(this, parent.get(address), address); } else { - return null; + return authorizedCodeService.processAccount(this, null, address); } } @@ -71,14 +75,14 @@ public MutableAccount createAccount(final Address address, final long nonce, fin } SimpleAccount account = new SimpleAccount(address, nonce, balance); accounts.put(address, account); - return account; + return authorizedCodeService.processMutableAccount(this, account, address); } @Override public MutableAccount getAccount(final Address address) { SimpleAccount account = accounts.get(address); if (account != null) { - return account; + return authorizedCodeService.processMutableAccount(this, account, address); } Account parentAccount = parent == null ? null : parent.getAccount(address); if (parentAccount != null) { @@ -90,9 +94,9 @@ public MutableAccount getAccount(final Address address) { parentAccount.getBalance(), parentAccount.getCode()); accounts.put(address, account); - return account; + return authorizedCodeService.processMutableAccount(this, account, address); } - return null; + return authorizedCodeService.processMutableAccount(this, null, address); } @Override @@ -132,4 +136,9 @@ public void commit() { public Optional parentUpdater() { return Optional.ofNullable(parent); } + + @Override + public void setAuthorizedCodeService(final AuthorizedCodeService authorizedCodeService) { + this.authorizedCodeService = authorizedCodeService; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index e01c9155106..cf776192299 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -33,6 +33,7 @@ import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.worldstate.AuthorizedCodeService; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayDeque; @@ -201,6 +202,7 @@ public enum Type { // Global data fields. private final WorldUpdater worldUpdater; + private final AuthorizedCodeService authorizedCodeService; // Metadata fields. private final Type type; @@ -270,7 +272,8 @@ private MessageFrame( final Consumer completer, final Map contextVariables, final Optional revertReason, - final TxValues txValues) { + final TxValues txValues, + final AuthorizedCodeService authorizedCodeService) { this.txValues = txValues; this.type = type; @@ -290,6 +293,7 @@ private MessageFrame( this.completer = completer; this.contextVariables = contextVariables; this.revertReason = revertReason; + this.authorizedCodeService = authorizedCodeService; this.undoMark = txValues.transientStorage().mark(); } @@ -423,6 +427,15 @@ public Bytes getReturnData() { return returnData; } + /** + * Return the authorized account service. + * + * @return the authorized account service + */ + public AuthorizedCodeService getAuthorizedCodeService() { + return authorizedCodeService; + } + /** * Set the return data. * @@ -1347,6 +1360,7 @@ public static class Builder { private Optional reason = Optional.empty(); private Set
accessListWarmAddresses = emptySet(); private Multimap accessListWarmStorage = HashMultimap.create(); + private AuthorizedCodeService authorizedCodeService; private Optional> versionedHashes = Optional.empty(); @@ -1631,6 +1645,17 @@ public Builder versionedHashes(final Optional> versionedHash return this; } + /** + * Sets authorized account service. + * + * @param authorizedCodeService the authorized account service + * @return the builder + */ + public Builder authorizedCodeService(final AuthorizedCodeService authorizedCodeService) { + this.authorizedCodeService = authorizedCodeService; + return this; + } + private void validate() { if (parentMessageFrame == null) { checkState(worldUpdater != null, "Missing message frame world updater"); @@ -1664,6 +1689,11 @@ public MessageFrame build() { WorldUpdater updater; boolean newStatic; TxValues newTxValues; + + if (authorizedCodeService == null) { + authorizedCodeService = new AuthorizedCodeService(); + } + if (parentMessageFrame == null) { newTxValues = new TxValues( @@ -1686,10 +1716,12 @@ public MessageFrame build() { newStatic = isStatic; } else { newTxValues = parentMessageFrame.txValues; - updater = parentMessageFrame.worldUpdater.updater(); + updater = parentMessageFrame.getWorldUpdater().updater(); newStatic = isStatic || parentMessageFrame.isStatic; } + updater.setAuthorizedCodeService(authorizedCodeService); + MessageFrame messageFrame = new MessageFrame( type, @@ -1706,7 +1738,8 @@ public MessageFrame build() { completer, contextVariables == null ? Map.of() : contextVariables, reason, - newTxValues); + newTxValues, + authorizedCodeService); newTxValues.messageFrameStack().addFirst(messageFrame); messageFrame.warmUpAddress(sender); messageFrame.warmUpAddress(contract); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index 2e1728b2346..0af023851ca 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -645,4 +645,14 @@ default long computeExcessBlobGas(final long parentExcessBlobGas, final int newB default long computeExcessBlobGas(final long parentExcessBlobGas, final long blobGasUsed) { return 0L; } + + /** + * Returns the upfront gas cost for EIP 7702 operation. + * + * @param authorizationListLength The length of the authorization list + * @return the gas cost + */ + default long setCodeListGasCost(final int authorizationListLength) { + return 0L; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java index e2789bba336..0c223d780b0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java @@ -28,6 +28,8 @@ */ public class PragueGasCalculator extends CancunGasCalculator { + static final long PER_CONTRACT_CODE_BASE_COST = 2500L; + /** Instantiates a new Prague Gas Calculator. */ public PragueGasCalculator() { this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]); @@ -41,4 +43,9 @@ public PragueGasCalculator() { protected PragueGasCalculator(final int maxPrecompile) { super(maxPrecompile); } + + @Override + public long setCodeListGasCost(final int authorizationListLength) { + return PER_CONTRACT_CODE_BASE_COST * authorizationListLength; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 8a82262204a..762030bf3b5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -230,6 +230,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { .code(code) .isStatic(isStatic(frame)) .completer(child -> complete(frame, child)) + .authorizedCodeService(frame.getAuthorizedCodeService()) .build(); // see note in stack depth check about incrementing cost frame.incrementRemainingGas(cost); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index f20c2914a09..16ea218f9c6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -195,6 +195,7 @@ private void spawnChildMessage(final MessageFrame parent, final Code code, final .apparentValue(value) .code(code) .completer(child -> complete(parent, child, evm)) + .authorizedCodeService(parent.getAuthorizedCodeService()) .build(); parent.setState(MessageFrame.State.CODE_SUSPENDED); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java index 43d7e343ff1..e19b6cd8a29 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java @@ -180,6 +180,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { .code(code) .isStatic(isStatic(frame)) .completer(child -> complete(frame, child)) + .authorizedCodeService(frame.getAuthorizedCodeService()) .build(); frame.setState(MessageFrame.State.CODE_SUSPENDED); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java index 9623eaf89ed..00cd67ddb81 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AbstractWorldUpdater.java @@ -43,6 +43,9 @@ public abstract class AbstractWorldUpdater> updatedAccounts = new ConcurrentHashMap<>(); @@ -59,6 +62,7 @@ public abstract class AbstractWorldUpdater account = new UpdateTrackingAccount<>(address); account.setNonce(nonce); account.setBalance(balance); - return track(account); + return authorizedCodeService.processMutableAccount(this, track(account), address); } @Override @@ -95,12 +99,12 @@ public Account get(final Address address) { // We may have updated it already, so check that first. final MutableAccount existing = updatedAccounts.get(address); if (existing != null) { - return existing; + return authorizedCodeService.processAccount(this, existing, address); } if (deletedAccounts.contains(address)) { return null; } - return getForMutation(address); + return authorizedCodeService.processAccount(this, getForMutation(address), address); } @Override @@ -108,7 +112,7 @@ public MutableAccount getAccount(final Address address) { // We may have updated it already, so check that first. final MutableAccount existing = updatedAccounts.get(address); if (existing != null) { - return existing; + return authorizedCodeService.processMutableAccount(this, existing, address); } if (deletedAccounts.contains(address)) { return null; @@ -117,9 +121,10 @@ public MutableAccount getAccount(final Address address) { // Otherwise, get it from our wrapped view and create a new update tracker. final A origin = getForMutation(address); if (origin == null) { - return null; + return authorizedCodeService.processMutableAccount(this, null, address); } else { - return track(new UpdateTrackingAccount<>(origin)); + return authorizedCodeService.processMutableAccount( + this, track(new UpdateTrackingAccount<>(origin)), address); } } @@ -165,6 +170,11 @@ public Optional parentUpdater() { } } + @Override + public void setAuthorizedCodeService(final AuthorizedCodeService authorizedCodeService) { + this.authorizedCodeService = authorizedCodeService; + } + /** * The accounts modified in this updater. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java new file mode 100644 index 00000000000..ff5d94343b4 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java @@ -0,0 +1,116 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.worldstate; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.AuthorizedCodeAccount; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.account.MutableAuthorizedCodeAccount; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.tuweni.bytes.Bytes; + +/** A service that manages the code injection of authorized code. */ +public class AuthorizedCodeService { + private final Map authorizedCode = new HashMap<>(); + + /** Creates a new AuthorizedCodeService. */ + public AuthorizedCodeService() {} + + /** + * Authorizes to load the code of authorizedCode into the authorizer account. + * + * @param authorizer the address that gives the authorization. + * @param authorizedCode the code which will be loaded. + */ + public void addAuthorizedCode(final Address authorizer, final Bytes authorizedCode) { + this.authorizedCode.put(authorizer, authorizedCode); + } + + /** + * Return all the authorities that have given their authorization to load the code of another + * account. + * + * @return the set of authorities. + */ + public Set
getAuthorities() { + return authorizedCode.keySet(); + } + + /** Resets all the authorized accounts. */ + public void resetAuthorities() { + authorizedCode.clear(); + } + + /** + * Checks if the provided address has set an authorized to load code into an EOA account. + * + * @param authority the address to check. + * @return {@code true} if the address has been authorized, {@code false} otherwise. + */ + public boolean hasAuthorizedCode(final Address authority) { + return authorizedCode.containsKey(authority); + } + + /** + * Processes the provided account, injecting the authorized code if authorized. + * + * @param worldUpdater the world updater to retrieve the code account. + * @param originalAccount the account to process. + * @param address the address of the account in case the provided account is null + * @return the processed account, containing the authorized code if authorized. + */ + public Account processAccount( + final WorldUpdater worldUpdater, final Account originalAccount, final Address address) { + if (!authorizedCode.containsKey(address)) { + return originalAccount; + } + + Account account = originalAccount; + if (account == null) { + account = worldUpdater.createAccount(address); + } + + return new AuthorizedCodeAccount(account, authorizedCode.get(address)); + } + + /** + * Processes the provided mutable account, injecting the authorized code if authorized. + * + * @param worldUpdater the world updater to retrieve the code account. + * @param originalAccount the mutable account to process. + * @param address the address of the account in case the provided account is null + * @return the processed mutable account, containing the authorized code if authorized. + */ + public MutableAccount processMutableAccount( + final WorldUpdater worldUpdater, + final MutableAccount originalAccount, + final Address address) { + if (!authorizedCode.containsKey(address)) { + return originalAccount; + } + + MutableAccount account = originalAccount; + if (account == null) { + account = worldUpdater.createAccount(address); + } + + return new MutableAuthorizedCodeAccount(account, authorizedCode.get(address)); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java index 9987a5be752..2497ed348d7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java @@ -41,6 +41,7 @@ public class JournaledUpdater implements WorldUpdater { final UndoMap accounts; final UndoSet
deleted; final long undoMark; + private AuthorizedCodeService authorizedCodeService; /** * Instantiates a new Stacked updater. @@ -66,6 +67,7 @@ public JournaledUpdater(final WorldUpdater world, final EvmConfiguration evmConf "WorldUpdater must be a JournaledWorldUpdater or an AbstractWorldUpdater"); } undoMark = accounts.mark(); + this.authorizedCodeService = new AuthorizedCodeService(); } /** @@ -126,12 +128,18 @@ public void markTransactionBoundary() { accounts.values().forEach(JournaledAccount::markTransactionBoundary); } + @Override + public void setAuthorizedCodeService(final AuthorizedCodeService authorizedCodeService) { + this.authorizedCodeService = authorizedCodeService; + } + @Override public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { JournaledAccount journaledAccount = new JournaledAccount(rootWorld.createAccount(address, nonce, balance)); accounts.put(address, journaledAccount); - return new JournaledAccount(journaledAccount); + return authorizedCodeService.processMutableAccount( + this, new JournaledAccount(journaledAccount), address); } @Override @@ -139,7 +147,7 @@ public MutableAccount getAccount(final Address address) { // We may have updated it already, so check that first. final JournaledAccount existing = accounts.get(address); if (existing != null) { - return existing; + return authorizedCodeService.processMutableAccount(this, existing, address); } if (deleted.contains(address)) { return null; @@ -148,11 +156,11 @@ public MutableAccount getAccount(final Address address) { // Otherwise, get it from our wrapped view and create a new update tracker. final MutableAccount origin = rootWorld.getAccount(address); if (origin == null) { - return null; + return authorizedCodeService.processMutableAccount(this, null, address); } else { var newAccount = new JournaledAccount(origin); accounts.put(address, newAccount); - return newAccount; + return authorizedCodeService.processMutableAccount(this, newAccount, address); } } @@ -169,12 +177,12 @@ public void deleteAccount(final Address address) { public Account get(final Address address) { final MutableAccount existing = accounts.get(address); if (existing != null) { - return existing; + return authorizedCodeService.processAccount(this, existing, address); } if (deleted.contains(address)) { - return null; + return authorizedCodeService.processAccount(this, null, address); } - return rootWorld.get(address); + return authorizedCodeService.processAccount(this, rootWorld.get(address), address); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java index 4cbda732798..5144176e799 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java @@ -179,4 +179,12 @@ default void clearAccountsThatAreEmpty() { default void markTransactionBoundary() { // default is to ignore } + + /** + * Sets the {@link AuthorizedCodeService} associated with this {@link WorldUpdater}. + * + * @param authorizedCodeService the {@link AuthorizedCodeService} to associate with this {@link + * WorldUpdater} + */ + void setAuthorizedCodeService(AuthorizedCodeService authorizedCodeService); } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java index e1e9c4c3ad3..479376a8fe3 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.AuthorizedCodeService; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Collection; @@ -32,6 +33,7 @@ public class ToyWorld implements WorldUpdater { ToyWorld parent; Map accounts = new HashMap<>(); + private AuthorizedCodeService authorizedCodeService; public ToyWorld() { this(null); @@ -39,6 +41,7 @@ public ToyWorld() { public ToyWorld(final ToyWorld parent) { this.parent = parent; + this.authorizedCodeService = new AuthorizedCodeService(); } @Override @@ -49,11 +52,11 @@ public WorldUpdater updater() { @Override public Account get(final Address address) { if (accounts.containsKey(address)) { - return accounts.get(address); + return authorizedCodeService.processAccount(this, accounts.get(address), address); } else if (parent != null) { - return parent.get(address); + return authorizedCodeService.processAccount(this, parent.get(address), address); } else { - return null; + return authorizedCodeService.processAccount(this, null, address); } } @@ -70,17 +73,17 @@ public MutableAccount createAccount( final Bytes code) { ToyAccount account = new ToyAccount(parentAccount, address, nonce, balance, code); accounts.put(address, account); - return account; + return authorizedCodeService.processMutableAccount(this, account, address); } @Override public MutableAccount getAccount(final Address address) { if (accounts.containsKey(address)) { - return accounts.get(address); + return authorizedCodeService.processMutableAccount(this, accounts.get(address), address); } else if (parent != null) { Account parentAccount = parent.getAccount(address); if (parentAccount == null) { - return null; + return authorizedCodeService.processMutableAccount(this, null, address); } else { return createAccount( parentAccount, @@ -90,7 +93,7 @@ public MutableAccount getAccount(final Address address) { parentAccount.getCode()); } } else { - return null; + return authorizedCodeService.processMutableAccount(this, null, address); } } @@ -128,4 +131,9 @@ public void commit() { public Optional parentUpdater() { return Optional.empty(); } + + @Override + public void setAuthorizedCodeService(final AuthorizedCodeService authorizedCodeService) { + this.authorizedCodeService = authorizedCodeService; + } }