diff --git a/CHANGELOG.md b/CHANGELOG.md index 798e53457..4e1511386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,14 @@ ### Breaking Changes ### Additions and Improvements -- Include current chain head block when computing `eth_maxPriorityFeePerGas` [#7485](https://github.com/hyperledger/besu/pull/7485) +- Update Java and Gradle dependecies [#7571](https://github.com/hyperledger/besu/pull/7571) +- Layered txpool: new options `--tx-pool-min-score` to remove a tx from pool when its score is lower than the specified value [#7576](https://github.com/hyperledger/besu/pull/7576) +- Add `engine_getBlobsV1` method to the Engine API [#7553](https://github.com/hyperledger/besu/pull/7553) ### Bug fixes +- Layered txpool: do not send notifications when moving tx between layers [#7539](https://github.com/hyperledger/besu/pull/7539) +- Layered txpool: fix for unsent drop notifications on remove [#7538](https://github.com/hyperledger/besu/pull/7538) +- Honor block number or tag parameter in eth_estimateGas and eth_createAccessList [#7502](https://github.com/hyperledger/besu/pull/7502) ## 24.9.0 @@ -25,6 +30,8 @@ - Implement engine_getClientVersionV1 [#7512](https://github.com/hyperledger/besu/pull/7512) - Performance optimzation for ECMUL (1 of 2) [#7509](https://github.com/hyperledger/besu/pull/7509) - Performance optimzation for ECMUL (2 of 2) [#7543](https://github.com/hyperledger/besu/pull/7543) +- Include current chain head block when computing `eth_maxPriorityFeePerGas` [#7485](https://github.com/hyperledger/besu/pull/7485) +- Remove (old) documentation updates from the changelog [#7562](https://github.com/hyperledger/besu/pull/7562) ### Bug fixes - Fix tracing in precompiled contracts when halting for out of gas [#7318](https://github.com/hyperledger/besu/issues/7318) @@ -33,6 +40,8 @@ - Correctly drops messages that exceeds local message size limit [#5455](https://github.com/hyperledger/besu/pull/7507) - **DebugMetrics**: Fixed a `ClassCastException` occurring in `DebugMetrics` when handling nested metric structures. Previously, `Double` values within these structures were incorrectly cast to `Map` objects, leading to errors. This update allows for proper handling of both direct values and nested structures at the same level. Issue# [#7383](https://github.com/hyperledger/besu/pull/7383) - `evmtool` was not respecting the `--genesis` setting, resulting in unexpected trace results. [#7433](https://github.com/hyperledger/besu/pull/7433) +- The genesis config override `contractSizeLimit`q was not wired into code size limits [#7557](https://github.com/hyperledger/besu/pull/7557) +- Fix incorrect key filtering in LayeredKeyValueStorage stream [#7535](https://github.com/hyperledger/besu/pull/7557) ## 24.8.0 @@ -3005,10 +3014,7 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Performance improvements: * Multithread Websockets to increase throughput [\#231](https://github.com/hyperledger/besu/pull/231) * NewBlockHeaders performance improvement [\#230](https://github.com/hyperledger/besu/pull/230) -- EIP2384 - Ice Age Adustment around Istanbul [\#211](https://github.com/hyperledger/besu/pull/211) -- Documentation updates include: - * [Configuring mining using the Stratum protocol](https://besu.hyperledger.org/en/latest/HowTo/Configure/Configure-Mining/) - * [ETC network command line options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#network) +- EIP2384 - Ice Age Adjustment around Istanbul [\#211](https://github.com/hyperledger/besu/pull/211) - Hard Fork Support: * MuirGlacier for Ethereum Mainnet and Ropsten Testnet * Agharta for Kotti and Mordor Testnets @@ -3134,16 +3140,6 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Store db metadata file in the root data directory. [\#46](https://github.com/hyperledger/besu/pull/46) - Add `--target-gas-limit` command line option. [\#24](https://github.com/hyperledger/besu/pull/24)(thanks to new contributor [cfelde](https://github.com/cfelde)) - Allow private contracts to access public state. [\#9](https://github.com/hyperledger/besu/pull/9) -- Documentation updates include: - - Added [sample load balancer configurations](https://besu.hyperledger.org/en/latest/HowTo/Configure/Configure-HA/Sample-Configuration/) - - Added [`retesteth`](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Subcommands/#retesteth) subcommand - - Added [`debug_accountRange`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#debug_accountrange) JSON-RPC API method - - Clarified purpose of [static nodes](https://besu.hyperledger.org/en/latest/HowTo/Find-and-Connect/Managing-Peers/#static-nodes) - - Added links [Kubernetes reference implementations](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Kubernetes/) - - Added content about [access between private and public states](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Groups/#access-between-states) - - Added restriction that [account permissioning cannot be used with random key signing](https://besu.hyperledger.org/en/latest/HowTo/Use-Privacy/Sign-Privacy-Marker-Transactions/). - - Added high availability requirement for [private transaction manager](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Overview/#availability) (ie, Orion) - - Added [genesis file reference](https://besu.hyperledger.org/en/latest/Reference/Config-Items/) ### Technical Improvements @@ -3213,11 +3209,6 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Added eea\_getTransactionCount Json Rpc [\#1861](https://github.com/PegaSysEng/pantheon/pull/1861) - PrivacyMarkerTransaction to be signed with a randomly generated key [\#1844](https://github.com/PegaSysEng/pantheon/pull/1844) - Implement eth\_getproof JSON RPC API [\#1824](https://github.com/PegaSysEng/pantheon/pull/1824) (thanks to [matkt](https://github.com/matkt)) -- Documentation updates include: - - [Improved navigation](https://docs.pantheon.pegasys.tech/en/latest/) - - [Added permissioning diagram](https://docs.pantheon.pegasys.tech/en/latest/Concepts/Permissioning/Permissioning-Overview/#onchain) - - [Added Responsible Disclosure policy](https://docs.pantheon.pegasys.tech/en/latest/Reference/Responsible-Disclosure/) - - [Added `blocks export` subcommand](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Subcommands/#export) ### Technical Improvements - Update the `pantheon blocks export` command usage [\#1887](https://github.com/PegaSysEng/pantheon/pull/1887) (thanks to [matkt](https://github.com/matkt)) @@ -3325,18 +3316,10 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Added JSON-RPC API to report validator block production information [#1687](https://github.com/PegaSysEng/pantheon/pull/1687) (thanks to [matkt](https://github.com/matkt)) - Added Mark Sweep Pruner [#1638](https://github.com/PegaSysEng/pantheon/pull/1638) - Added the Blake2b F compression function as a precompile in Besu [#1614](https://github.com/PegaSysEng/pantheon/pull/1614) (thanks to [iikirilov](https://github.com/iikirilov)) -- Documentation updates include: - - Added CPU requirements [#1734](https://github.com/PegaSysEng/pantheon/pull/1734) - - Added reference to Ansible role [#1733](https://github.com/PegaSysEng/pantheon/pull/1733) - - Updated revert reason example [#1754](https://github.com/PegaSysEng/pantheon/pull/1754) - - Added content on deploying for production [#1774](https://github.com/PegaSysEng/pantheon/pull/1774) - - Updated docker docs for location of data path [#1790](https://github.com/PegaSysEng/pantheon/pull/1790) - - Updated permissiong documentation [#1792](https://github.com/PegaSysEng/pantheon/pull/1792) [#1652](https://github.com/PegaSysEng/pantheon/pull/1652) - Added permissioning webinar in the resources [#1717](https://github.com/PegaSysEng/pantheon/pull/1717) - Add web3.js-eea reference doc [#1617](https://github.com/PegaSysEng/pantheon/pull/1617) - - Updated privacy documentation [#1650](https://github.com/PegaSysEng/pantheon/pull/1650) [#1721](https://github.com/PegaSysEng/pantheon/pull/1721) [#1722](https://github.com/PegaSysEng/pantheon/pull/1722) @@ -3359,9 +3342,7 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. [#1803](https://github.com/PegaSysEng/pantheon/pull/1803) [#1810](https://github.com/PegaSysEng/pantheon/pull/1810) [#1817](https://github.com/PegaSysEng/pantheon/pull/1817) - - Added documentation for getSignerMetrics [#1723](https://github.com/PegaSysEng/pantheon/pull/1723) (thanks to [matkt](https://github.com/matkt)) - Added Java 11+ as a prerequisite for installing Besu using Homebrew. [#1755](https://github.com/PegaSysEng/pantheon/pull/1755) - - Fixed documentation formatting and typos [#1718](https://github.com/PegaSysEng/pantheon/pull/1718) [#1742](https://github.com/PegaSysEng/pantheon/pull/1742) [#1763](https://github.com/PegaSysEng/pantheon/pull/1763) [#1779](https://github.com/PegaSysEng/pantheon/pull/1779) @@ -3391,25 +3372,6 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Add eea\_findPrivacyGroup endpoint to Besu [\#1635](https://github.com/PegaSysEng/pantheon/pull/1635) (thanks to [Puneetha17](https://github.com/Puneetha17)) - Updated eea send raw transaction with privacy group ID [\#1611](https://github.com/PegaSysEng/pantheon/pull/1611) (thanks to [iikirilov](https://github.com/iikirilov)) - Added Revert Reason [\#1603](https://github.com/PegaSysEng/pantheon/pull/1603) -- Documentation updates include: - - Added [UPnP content](https://besu.hyperledger.org/en/latest/HowTo/Find-and-Connect/Using-UPnP/) - - Added [load balancer image](https://besu.hyperledger.org/en/stable/) - - Added [revert reason](https://besu.hyperledger.org/en/latest/HowTo/Send-Transactions/Revert-Reason/) - - Added [admin\_changeLogLevel](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#admin_changeloglevel) JSON RPC API (thanks to [matkt](https://github.com/matkt)) - - Updated for [new Docker image](https://besu.hyperledger.org/en/stable/) - - Added [Docker image migration content](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/Migration-Docker/) - - Added [transaction validation content](https://besu.hyperledger.org/en/latest/Concepts/Transactions/Transaction-Validation/) - - Updated [permissioning overview](https://besu.hyperledger.org/en/stable/) for onchain account permissioning - - Updated [quickstart](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/#monitor-node-performance-using-prometheus) to include Prometheus and Grafana - - Added [remote connections limits options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#remote-connections-limit-enabled) - - Updated [web3.js-eea reference](https://docs.pantheon.pegasys.tech/en/latest/Reference/web3js-eea-Methods/) to include privacy group methods - - Updated [onchain permissioning to include account permissioning](hhttps://besu.hyperledger.org/en/latest/Concepts/Permissioning/Onchain-Permissioning/) and [Permissioning Management Dapp](https://besu.hyperledger.org/en/latest/Tutorials/Permissioning/Getting-Started-Onchain-Permissioning/#start-the-development-server-for-the-permissioning-management-dapp) - - Added [deployment procedure for Permissioning Management Dapp](https://besu.hyperledger.org/en/stable/) - - Added privacy content for [EEA-compliant and Besu-extended privacy](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Groups/) - - Added content on [creating and managing privacy groups](https://besu.hyperledger.org/en/latest/Reference/web3js-eea-Methods/#createprivacygroup) - - Added content on [accessing private and privacy marker transactions](https://besu.hyperledger.org/en/latest/HowTo/Use-Privacy/Access-Private-Transactions/) - - Added content on [system requirements](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/System-Requirements/) - - Added reference to [Besu role on Galaxy to deploy using Ansible](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Ansible/). ### Technical Improvements @@ -3478,12 +3440,6 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Print Besu version when starting [\#1593](https://github.com/PegaSysEng/pantheon/pull/1593) - \[PAN-2746\] Add eea\_createPrivacyGroup & eea\_deletePrivacyGroup endpoint [\#1560](https://github.com/PegaSysEng/pantheon/pull/1560) (thanks to [Puneetha17](https://github.com/Puneetha17)) -Documentation updates include: -- Added [readiness and liveness endpoints](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Using-JSON-RPC-API/#readiness-and-liveness-endpoints) -- Added [high availability content](https://besu.hyperledger.org/en/latest/HowTo/Configure/Configure-HA/High-Availability/) -- Added [web3js-eea client library](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Privacy-Quickstart/#clone-eeajs-libraries) -- Added content on [setting CLI options using environment variables](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#specifying-options) - ### Technical Improvements - Read config from env vars when no config file specified [\#1639](https://github.com/PegaSysEng/pantheon/pull/1639) @@ -3532,16 +3488,6 @@ Documentation updates include: - Add subscribe and unsubscribe count metrics [\#1541](https://github.com/PegaSysEng/pantheon/pull/1541) - Add pivot block metrics [\#1537](https://github.com/PegaSysEng/pantheon/pull/1537) -Documentation updates include: - -- Updated [IBFT 2.0 tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-IBFT-Network/) to use network configuration tool -- Added [debug\_traceBlock\* methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#debug_traceblock) -- Reorganised [monitoring documentation](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/) -- Added [link to sample Grafana dashboard](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/#monitor-node-performance-using-prometheus) -- Added [note about replacing transactions in transaction pool](https://besu.hyperledger.org/en/latest/Concepts/Transactions/Transaction-Pool/#replacing-transactions-with-same-nonce) -- Updated [example transaction scripts](https://besu.hyperledger.org/en/latest/HowTo/Send-Transactions/Transactions/#example-javascript-scripts) -- Updated [Alethio Ethstats and Explorer documentation](https://besu.hyperledger.org/en/latest/Concepts/AlethioOverview/) - ### Technical Improvements - PAN-2816: Hiding experimental account permissioning cli options [\#1584](https://github.com/PegaSysEng/pantheon/pull/1584) @@ -3577,14 +3523,6 @@ Documentation updates include: ### Additions and Improvements -Documentation updates include: - -- Added [GraphQL options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#graphql-http-cors-origins) -- Added [troubleshooting point about illegal reflective access error](https://besu.hyperledger.org/en/latest/HowTo/Troubleshoot/Troubleshooting/#illegal-reflective-access-error-on-startup) -- Added [trusted bootnode behaviour for permissioning](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Onchain-Permissioning/#bootnodes) -- Added [how to obtain a WS authentication token](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Authentication/#obtaining-an-authentication-token) -- Updated [example scripts and added package.json file for creating signed transactions](https://besu.hyperledger.org/en/latest/HowTo/Send-Transactions/Transactions/) - ### Technical Improvements - Replaced Void datatype with void [\#1530](https://github.com/PegaSysEng/pantheon/pull/1530) @@ -3633,12 +3571,6 @@ Documentation updates include: - Added [`--tx-pool-retention-hours`](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#tx-pool-retention-hours) [\#1333](https://github.com/PegaSysEng/pantheon/pull/1333) - Added Genesis file support for specifying the maximum stack size. [\#1431](https://github.com/PegaSysEng/pantheon/pull/1431) - Included transaction details when subscribed to Pending transactions [\#1410](https://github.com/PegaSysEng/pantheon/pull/1410) -- Documentation updates include: - - [Added configuration items specified in the genesis file](https://besu.hyperledger.org/en/latest/Reference/Config-Items/#configuration-items) - - [Added pending transaction details subscription](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/RPC-PubSub/#pending-transactionss) - - [Added Troubleshooting content](https://besu.hyperledger.org/en/latest/HowTo/Troubleshoot/Troubleshooting/) - - [Added Privacy Quickstart](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Privacy-Quickstart/) - - [Added privacy roadmap](https://github.com/hyperledger/besu/blob/master/ROADMAP.md) ### Technical Improvements @@ -3746,12 +3678,6 @@ Documentation updates include: - [Privacy](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Overview/) - [Onchain Permissioning](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/#onchain) - [Fastsync](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#fast-sync-min-peers) -- Documentation updates include: - - Added JSON-RPC methods: - - [`txpool_pantheonStatistics`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#txpool_besustatistics) - - [`net_services`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#net_services) - - [Updated to indicate Docker image doesn't run on Windows](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/Run-Docker-Image/) - - [Added how to configure a free gas network](https://besu.hyperledger.org/en/latest/HowTo/Configure/FreeGas/) ### Technical Improvements @@ -3805,17 +3731,6 @@ Documentation updates include: ### Additions and Improvements - Notify of dropped messages [\#1156](https://github.com/PegaSysEng/pantheon/pull/1156) -- Documentation updates include: - - Added [Permissioning Overview](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/) - - Added content on [Network vs Node Configuration](https://besu.hyperledger.org/en/latest/HowTo/Configure/Using-Configuration-File/) - - Updated [RAM requirements](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/System-Requirements/#ram) - - Added [Privacy Overview](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Overview/) and [Processing Private Transactions](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Private-Transaction-Processing/) - - Renaming of Ethstats Lite Explorer to [Ethereum Lite Explorer](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Lite-Block-Explorer/#lite-block-explorer-documentation) (thanks to [tzapu](https://github.com/tzapu)) - - Added content on using [Truffle with Besu](https://besu.hyperledger.org/en/latest/HowTo/Develop-Dapps/Truffle/) - - Added [`droppedPendingTransactions` RPC Pub/Sub subscription](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/RPC-PubSub/#dropped-transactions) - - Added [`eea_*` JSON-RPC API methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#eea-methods) - - Added [architecture diagram](https://besu.hyperledger.org/en/latest/Concepts/ArchitectureOverview/) - - Updated [permissioning CLI options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#permissions-accounts-config-file-enabled) and [permissioned network tutorial](https://besu.hyperledger.org/en/stable/) ### Technical Improvements @@ -3870,31 +3785,6 @@ Documentation updates include: - Added PendingTransactions JSON-RPC [\#1043](https://github.com/PegaSysEng/pantheon/pull/1043) (thanks to [EdwinLeeGreene](https://github.com/EdwinLeeGreene)) - Added `admin_nodeInfo` JSON-RPC [\#1012](https://github.com/PegaSysEng/pantheon/pull/1012) - Added `--metrics-category` CLI to only enable select metrics [\#969](https://github.com/PegaSysEng/pantheon/pull/969) -- Documentation updates include: - - Updated endpoints in [Private Network Quickstart](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Private-Network-Quickstart/) (thanks to [laubai](https://github.com/laubai)) - - Updated [documentation contribution guidelines](https://besu.hyperledger.org/en/stable/) - - Added [`admin_removePeer`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#admin_removepeer) - - Updated [tutorials](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Clique-Network/) for printing of enode on startup - - Added [`txpool_pantheonTransactions`](https://besu.hyperledger.org/en/stable/Reference/API-Methods/#txpool_besutransactions) - - Added [Transaction Pool content](https://besu.hyperledger.org/en/latest/Concepts/Transactions/Transaction-Pool/) - - Added [`tx-pool-max-size` CLI option](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#tx-pool-max-size) - - Updated [developer build instructions to use installDist](https://besu.hyperledger.org/en/stable/) - - Added [Azure quickstart tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Azure-Private-Network-Quickstart/) - - Enabled copy button in code blocks - - Added [IBFT 1.0](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/QuorumIBFT/) - - Added section on using [Geth attach with Besu](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Using-JSON-RPC-API/#geth-console) - - Enabled the edit link doc site to ease external doc contributions - - Added [EthStats docs](https://besu.hyperledger.org/HowTo/Deploy/Lite-Network-Monitor/) (thanks to [baxy](https://github.com/baxy)) - - Updated [Postman collection](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Authentication/#postman) - - Added [`metrics-category` CLI option](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#metrics-category) - - Added information on [block time and timeout settings](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/#block-time) for IBFT 2.0 - - Added [`admin_nodeInfo`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#admin_nodeinfo) - - Added [permissions images](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/) - - Added permissioning blog to [Resources](https://besu.hyperledger.org/en/latest/Reference/Resources/) - - Updated [Create Permissioned Network](https://besu.hyperledger.org/en/latest/Tutorials/Permissioning/Create-Permissioned-Network/) tutorial to use `export-address` - - Updated [Clique](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/Clique/) and [IBFT 2.0](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/) docs to include complete genesis file - - Updated [Clique tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Clique-Network/) to use `export-address` subcommand - - Added IBFT 2.0 [future message configuration options](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/#optional-configuration-options) ### Technical Improvements - Fixed so self persists to the whitelist [\#1176](https://github.com/PegaSysEng/pantheon/pull/1176) @@ -4039,8 +3929,6 @@ Public key address export subcommand was missing in 1.0 release. ### Additions and Improvements - Added `public-key export-address` subcommand [\#888](https://github.com/PegaSysEng/pantheon/pull/888) -- Documentation update for the [`public-key export-address`](https://besu.hyperledger.org/en/stable/) subcommand. -- Updated [IBFT 2.0 overview](https://besu.hyperledger.org/en/stable/) to include use of `rlp encode` command and information on setting IBFT 2.0 properties to achieve your desired block time. ## 1.0 @@ -4051,16 +3939,7 @@ Public key address export subcommand was missing in 1.0 release. - Added `rlp encode` subcommand [\#965](https://github.com/PegaSysEng/pantheon/pull/965) - Method to reload permissions file [\#834](https://github.com/PegaSysEng/pantheon/pull/834) - Added rebind mitigation for Websockets. [\#905](https://github.com/PegaSysEng/pantheon/pull/905) -- Support genesis contract code [\#749](https://github.com/PegaSysEng/pantheon/pull/749) (thanks to [kziemianek](https://github.com/kziemianek)). -- Documentation updates include: - - Added details on [port configuration](https://besu.hyperledger.org/en/latest/HowTo/Find-and-Connect/Configuring-Ports/) - - Added [Resources page](https://besu.hyperledger.org/en/latest/Reference/Resources/) linking to Besu blog posts and webinars - - Added [JSON-RPC Authentication](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Authentication/) - - Added [tutorial to create permissioned network](https://besu.hyperledger.org/en/latest/Tutorials/Permissioning/Create-Permissioned-Network/) - - Added [Permissioning](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/) content - - Added [Permissioning API methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#permissioning-methods) - - Added [tutorial to create Clique private network](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Clique-Network/) - - Added [tutorial to create IBFT 2.0 private network](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-IBFT-Network/) +- Support genesis contract code [\#749](https://github.com/PegaSysEng/pantheon/pull/749) (thanks to [kziemianek](https://github.com/kziemianek)) ### Technical Improvements - RoundChangeCertificateValidator requires unique authors [\#997](https://github.com/PegaSysEng/pantheon/pull/997) @@ -4204,7 +4083,7 @@ Public key address export subcommand was missing in 1.0 release. - Updated IbftRound and RoundState APIs to use wrapped messages [\#740](https://github.com/PegaSysEng/pantheon/pull/740) - Exception handling [\#739](https://github.com/PegaSysEng/pantheon/pull/739) - Upgrade dependency versions and build cleanup [\#738](https://github.com/PegaSysEng/pantheon/pull/738) -- Update IbftBlockHeigntManager to accept new message types. [\#737](https://github.com/PegaSysEng/pantheon/pull/737) +- Update IbftBlockHeightManager to accept new message types. [\#737](https://github.com/PegaSysEng/pantheon/pull/737) - Error response handling for permissions APIs [\#736](https://github.com/PegaSysEng/pantheon/pull/736) - IPV6 bootnodes don't work [\#735](https://github.com/PegaSysEng/pantheon/pull/735) - Updated to use tags of pantheon build rather than another repo [\#734](https://github.com/PegaSysEng/pantheon/pull/734) @@ -4218,7 +4097,7 @@ Public key address export subcommand was missing in 1.0 release. ## 0.9.1 -Built and compatible with with JDK8. +Built and compatible with JDK8. ## 0.9 @@ -4281,13 +4160,6 @@ has been updated to use the moved quickstart. - Implement Petersburg hardfork [\#601](https://github.com/PegaSysEng/pantheon/pull/601) - Added private transaction abstraction [\#592](https://github.com/PegaSysEng/pantheon/pull/592) (thanks to [iikirilov](https://github.com/iikirilov)) - Added privacy command line commands [\#584](https://github.com/PegaSysEng/pantheon/pull/584) (thanks to [Puneetha17](https://github.com/Puneetha17)) -- Documentation updates include: - - Updated [Private Network Quickstart tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Private-Network-Quickstart/) - to use quickstart in `pantheon-quickstart` repository and indicate that the quickstart is not supported on Windows. - - Added IBFT 2.0 [content](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/) and [JSON RPC API methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#ibft-20-methods). - - Added [consensus protocols content](https://besu.hyperledger.org/en/latest/Concepts/Consensus-Protocols/Comparing-PoA/). - - Added content on [events and logs](https://besu.hyperledger.org/en/latest/Concepts/Events-and-Logs/), and [using filters](https://besu.hyperledger.org/en/latest/HowTo/Interact/Filters/Accessing-Logs-Using-JSON-RPC/). - - Added content on integrating with [Prometheus Push Gateway](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/#running-prometheus-with-besu-in-push-mode) ### Technical Improvements @@ -4409,11 +4281,6 @@ When restarting your node with the v0.8.4 Docker image: - Added account whitelisting [\#460](https://github.com/PegaSysEng/pantheon/pull/460) - Added configurable refresh delay for SyncingSubscriptionService on start up [\#383](https://github.com/PegaSysEng/pantheon/pull/383) - Added the Command Line Style Guide [\#530](https://github.com/PegaSysEng/pantheon/pull/530) -- Documentation updates include: - * Migrated to new [documentation site](https://docs.pantheon.pegasys.tech/en/latest/) - * Added [configuration file content](https://besu.hyperledger.org/en/stable/) - * Added [tutorial to create private network](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Network/) - * Added content on [enabling non-default APIs](https://besu.hyperledger.org/en/latest/Reference/API-Methods/) ## Technical Improvements @@ -4497,11 +4364,6 @@ Specify `*` or `all` for `--host-whitelist` to effectively disable host protecti - IBFT block mining [\#169](https://github.com/PegaSysEng/pantheon/pull/169) - Added `--goerli` CLI option [\#370](https://github.com/PegaSysEng/pantheon/pull/370) (Thanks to [@Nashatyrev](https://github.com/Nashatyrev)) - Begin capturing metrics to better understand Besu's behaviour [\#326](https://github.com/PegaSysEng/pantheon/pull/326) -- Documentation updates include: - * Added Coding Conventions [\#342](https://github.com/PegaSysEng/pantheon/pull/342) - * Reorganised [Installation documentation](https://github.com/PegaSysEng/pantheon/wiki/Installation) and added [Chocolatey installation](https://github.com/PegaSysEng/pantheon/wiki/Install-Binaries#windows-with-chocolatey) for Windows - * Reorganised [JSON-RPC API documentation](https://github.com/PegaSysEng/pantheon/wiki/JSON-RPC-API) - * Updated [RPC Pub/Sub API documentation](https://github.com/PegaSysEng/pantheon/wiki/RPC-PubSub) ### Technical Improvements @@ -4578,15 +4440,6 @@ Specify `*` or `all` for `--host-whitelist` to effectively disable host protecti - Added `--banned-nodeids` option to prevent connection to specific nodes (PR [#254](https://github.com/PegaSysEng/pantheon/pull/254)) - Send client quitting disconnect message to peers on shutdown (PR [#253](https://github.com/PegaSysEng/pantheon/pull/253)) - Improved error message for port conflict error (PR [#232](https://github.com/PegaSysEng/pantheon/pull/232)) - - Improved documentation by adding the following pages: - * [Getting Started](https://github.com/PegaSysEng/pantheon/wiki/Getting-Started) - * [Network ID and Chain ID](https://github.com/PegaSysEng/pantheon/wiki/NetworkID-And-ChainID) - * [Node Keys](https://github.com/PegaSysEng/pantheon/wiki/Node-Keys) - * [Networking](https://github.com/PegaSysEng/pantheon/wiki/Networking) - * [Accounts for Testing](https://github.com/PegaSysEng/pantheon/wiki/Accounts-for-Testing) - * [Logging](https://github.com/PegaSysEng/pantheon/wiki/Logging) - * [Proof of Authority](https://github.com/PegaSysEng/pantheon/wiki/Proof-of-Authority) - * [Passing JVM Options](https://github.com/PegaSysEng/pantheon/wiki/Passing-JVM-Options) ### Technical Improvements diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java index a2ffc9b36..0913fd227 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java @@ -24,11 +24,16 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.Node; import java.nio.file.Path; import java.util.Collection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class AllowListContainsKeyAndValue implements Condition { private final ALLOWLIST_TYPE allowlistType; private final Collection allowlistValues; private final Path configFilePath; + private static final Logger LOG = LoggerFactory.getLogger(AllowListContainsKeyAndValue.class); + public AllowListContainsKeyAndValue( final ALLOWLIST_TYPE allowlistType, final Collection allowlistValues, @@ -47,6 +52,7 @@ public class AllowListContainsKeyAndValue implements Condition { allowlistType, allowlistValues, configFilePath); } catch (final Exception e) { result = false; + LOG.error("Error verifying allowlist contains key and value", e); } assertThat(result).isTrue(); } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index f28a1d887..d4ec54045 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 @@ -18,8 +18,13 @@ import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; import org.hyperledger.besu.Runner; import org.hyperledger.besu.RunnerBuilder; +import org.hyperledger.besu.chainexport.RlpBlockExporter; +import org.hyperledger.besu.chainimport.JsonBlockImporter; +import org.hyperledger.besu.chainimport.RlpBlockImporter; +import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.controller.BesuControllerBuilder; @@ -30,23 +35,32 @@ import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; +import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.metrics.MetricsSystemFactory; +import org.hyperledger.besu.metrics.MetricsSystemModule; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.plugin.data.EnodeURL; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.PermissioningService; import org.hyperledger.besu.plugin.services.PicoCLIOptions; import org.hyperledger.besu.plugin.services.PrivacyPluginService; @@ -70,6 +84,7 @@ import org.hyperledger.besu.services.StorageServiceImpl; import org.hyperledger.besu.services.TransactionPoolValidatorServiceImpl; import org.hyperledger.besu.services.TransactionSelectionServiceImpl; import org.hyperledger.besu.services.TransactionSimulationServiceImpl; +import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin; import java.io.File; import java.nio.file.Path; @@ -79,8 +94,13 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import dagger.Component; +import dagger.Module; +import dagger.Provides; import io.opentelemetry.api.GlobalOpenTelemetry; import io.vertx.core.Vertx; import org.slf4j.Logger; @@ -96,60 +116,6 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { private final Map besuPluginContextMap = new ConcurrentHashMap<>(); - private BesuPluginContextImpl buildPluginContext( - final BesuNode node, - final StorageServiceImpl storageService, - final SecurityModuleServiceImpl securityModuleService, - final TransactionSimulationServiceImpl transactionSimulationServiceImpl, - final TransactionSelectionServiceImpl transactionSelectionServiceImpl, - final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl, - final BlockchainServiceImpl blockchainServiceImpl, - final RpcEndpointServiceImpl rpcEndpointServiceImpl, - final BesuConfiguration commonPluginConfiguration, - final PermissioningServiceImpl permissioningService) { - final CommandLine commandLine = new CommandLine(CommandSpec.create()); - final BesuPluginContextImpl besuPluginContext = new BesuPluginContextImpl(); - besuPluginContext.addService(StorageService.class, storageService); - besuPluginContext.addService(SecurityModuleService.class, securityModuleService); - besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine)); - besuPluginContext.addService(RpcEndpointService.class, rpcEndpointServiceImpl); - besuPluginContext.addService( - TransactionSelectionService.class, transactionSelectionServiceImpl); - besuPluginContext.addService( - TransactionPoolValidatorService.class, transactionPoolValidatorServiceImpl); - besuPluginContext.addService( - TransactionSimulationService.class, transactionSimulationServiceImpl); - besuPluginContext.addService(BlockchainService.class, blockchainServiceImpl); - besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); - - final Path pluginsPath; - final String pluginDir = System.getProperty("besu.plugins.dir"); - if (pluginDir == null || pluginDir.isEmpty()) { - pluginsPath = node.homeDirectory().resolve("plugins"); - final File pluginsDirFile = pluginsPath.toFile(); - if (!pluginsDirFile.isDirectory()) { - pluginsDirFile.mkdirs(); - pluginsDirFile.deleteOnExit(); - } - System.setProperty("besu.plugins.dir", pluginsPath.toString()); - } else { - pluginsPath = Path.of(pluginDir); - } - - besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); - besuPluginContext.addService(PermissioningService.class, permissioningService); - besuPluginContext.addService(PrivacyPluginService.class, new PrivacyPluginServiceImpl()); - - besuPluginContext.registerPlugins( - new PluginConfiguration.Builder().pluginsDir(pluginsPath).build()); - - commandLine.parseArgs(node.getConfiguration().getExtraCLIOptions().toArray(new String[0])); - - // register built-in plugins - new RocksDBPlugin().register(besuPluginContext); - return besuPluginContext; - } - @Override public void startNode(final BesuNode node) { @@ -162,114 +128,45 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { throw new UnsupportedOperationException("commands are not supported with thread runner"); } - final StorageServiceImpl storageService = new StorageServiceImpl(); - final SecurityModuleServiceImpl securityModuleService = new SecurityModuleServiceImpl(); - final TransactionSimulationServiceImpl transactionSimulationServiceImpl = - new TransactionSimulationServiceImpl(); - final TransactionSelectionServiceImpl transactionSelectionServiceImpl = - new TransactionSelectionServiceImpl(); - final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl = - new TransactionPoolValidatorServiceImpl(); - final BlockchainServiceImpl blockchainServiceImpl = new BlockchainServiceImpl(); - final RpcEndpointServiceImpl rpcEndpointServiceImpl = new RpcEndpointServiceImpl(); + BesuNodeProviderModule module = new BesuNodeProviderModule(node); + AcceptanceTestBesuComponent component = + DaggerThreadBesuNodeRunner_AcceptanceTestBesuComponent.builder() + .besuNodeProviderModule(module) + .build(); + final Path dataDir = node.homeDirectory(); - final BesuConfigurationImpl commonPluginConfiguration = new BesuConfigurationImpl(); final PermissioningServiceImpl permissioningService = new PermissioningServiceImpl(); - final var miningParameters = - ImmutableMiningParameters.builder() - .from(node.getMiningParameters()) - .transactionSelectionService(transactionSelectionServiceImpl) - .build(); - commonPluginConfiguration - .init(dataDir, dataDir.resolve(DATABASE_PATH), node.getDataStorageConfiguration()) - .withMiningParameters(miningParameters); - - final BesuPluginContextImpl besuPluginContext = - besuPluginContextMap.computeIfAbsent( - node, - n -> - buildPluginContext( - node, - storageService, - securityModuleService, - transactionSimulationServiceImpl, - transactionSelectionServiceImpl, - transactionPoolValidatorServiceImpl, - blockchainServiceImpl, - rpcEndpointServiceImpl, - commonPluginConfiguration, - permissioningService)); - GlobalOpenTelemetry.resetForTest(); - final ObservableMetricsSystem metricsSystem = - MetricsSystemFactory.create(node.getMetricsConfiguration()); + final ObservableMetricsSystem metricsSystem = component.getObservableMetricsSystem(); final List bootnodes = - node.getConfiguration().getBootnodes().stream() - .map(EnodeURLImpl::fromURI) - .collect(Collectors.toList()); - final NetworkName network = node.getNetwork() == null ? NetworkName.DEV : node.getNetwork(); - final EthNetworkConfig.Builder networkConfigBuilder = - new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(network)) - .setBootNodes(bootnodes); + node.getConfiguration().getBootnodes().stream().map(EnodeURLImpl::fromURI).toList(); + + final EthNetworkConfig.Builder networkConfigBuilder = component.ethNetworkConfigBuilder(); + networkConfigBuilder.setBootNodes(bootnodes); node.getConfiguration() .getGenesisConfig() .map(GenesisConfigFile::fromConfig) .ifPresent(networkConfigBuilder::setGenesisConfigFile); final EthNetworkConfig ethNetworkConfig = networkConfigBuilder.build(); - final SynchronizerConfiguration synchronizerConfiguration = - new SynchronizerConfiguration.Builder().build(); - final BesuControllerBuilder builder = - new BesuController.Builder() - .fromEthNetworkConfig(ethNetworkConfig, synchronizerConfiguration.getSyncMode()); + final BesuControllerBuilder builder = component.besuControllerBuilder(); + builder.isRevertReasonEnabled(node.isRevertReasonEnabled()); + builder.networkConfiguration(node.getNetworkingConfiguration()); - final KeyValueStorageProvider storageProvider = - new KeyValueStorageProviderBuilder() - .withStorageFactory(storageService.getByName("rocksdb").get()) - .withCommonConfiguration(commonPluginConfiguration) - .withMetricsSystem(metricsSystem) - .build(); - - final TransactionPoolConfiguration txPoolConfig = - ImmutableTransactionPoolConfiguration.builder() - .from(node.getTransactionPoolConfiguration()) - .strictTransactionReplayProtectionEnabled(node.isStrictTxReplayProtectionEnabled()) - .transactionPoolValidatorService(transactionPoolValidatorServiceImpl) - .build(); - - final InProcessRpcConfiguration inProcessRpcConfiguration = node.inProcessRpcConfiguration(); - - final int maxPeers = 25; - - builder - .synchronizerConfiguration(new SynchronizerConfiguration.Builder().build()) - .dataDirectory(node.homeDirectory()) - .miningParameters(miningParameters) - .privacyParameters(node.getPrivacyParameters()) - .nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir)))) - .metricsSystem(metricsSystem) - .transactionPoolConfiguration(txPoolConfig) - .dataStorageConfiguration(node.getDataStorageConfiguration()) - .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) - .clock(Clock.systemUTC()) - .isRevertReasonEnabled(node.isRevertReasonEnabled()) - .storageProvider(storageProvider) - .gasLimitCalculator(GasLimitCalculator.constant()) - .evmConfiguration(EvmConfiguration.DEFAULT) - .maxPeers(maxPeers) - .maxRemotelyInitiatedPeers(15) - .networkConfiguration(node.getNetworkingConfiguration()) - .randomPeerPriority(false); + builder.dataDirectory(dataDir); + builder.nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir)))); + builder.privacyParameters(node.getPrivacyParameters()); node.getGenesisConfig() .map(GenesisConfigFile::fromConfig) .ifPresent(builder::genesisConfigFile); - final BesuController besuController = builder.build(); + final BesuController besuController = component.besuController(); - initTransactionSimulationService( - transactionSimulationServiceImpl, besuController, node.getApiConfiguration()); - initBlockchainService(blockchainServiceImpl, besuController); + InProcessRpcConfiguration inProcessRpcConfiguration = node.inProcessRpcConfiguration(); + + final BesuPluginContextImpl besuPluginContext = + besuPluginContextMap.computeIfAbsent(node, n -> component.getBesuPluginContext()); final RunnerBuilder runnerBuilder = new RunnerBuilder(); runnerBuilder.permissioningConfiguration(node.getPermissioningConfiguration()); @@ -293,17 +190,13 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { .p2pEnabled(node.isP2pEnabled()) .p2pTLSConfiguration(node.getTLSConfiguration()) .graphQLConfiguration(GraphQLConfiguration.createDefault()) - .staticNodes( - node.getStaticNodes().stream() - .map(EnodeURLImpl::fromString) - .collect(Collectors.toList())) + .staticNodes(node.getStaticNodes().stream().map(EnodeURLImpl::fromString).toList()) .besuPluginContext(besuPluginContext) .autoLogBloomCaching(false) - .storageProvider(storageProvider) - .rpcEndpointService(rpcEndpointServiceImpl) + .storageProvider(besuController.getStorageProvider()) + .rpcEndpointService(component.rpcEndpointService()) .inProcessRpcConfiguration(inProcessRpcConfiguration); node.engineRpcConfiguration().ifPresent(runnerBuilder::engineJsonRpcConfiguration); - besuPluginContext.beforeExternalServices(); final Runner runner = runnerBuilder.build(); @@ -318,7 +211,7 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { besuController.getSyncState(), besuController.getProtocolContext().getBadBlockManager())); - rpcEndpointServiceImpl.init(runner.getInProcessRpcMethods()); + component.rpcEndpointService().init(runner.getInProcessRpcMethods()); besuPluginContext.startPlugins(); @@ -328,25 +221,6 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { MDC.remove("node"); } - private void initBlockchainService( - final BlockchainServiceImpl blockchainServiceImpl, final BesuController besuController) { - blockchainServiceImpl.init( - besuController.getProtocolContext(), besuController.getProtocolSchedule()); - } - - private void initTransactionSimulationService( - final TransactionSimulationServiceImpl transactionSimulationService, - final BesuController besuController, - final ApiConfiguration apiConfiguration) { - transactionSimulationService.init( - besuController.getProtocolContext().getBlockchain(), - new TransactionSimulator( - besuController.getProtocolContext().getBlockchain(), - besuController.getProtocolContext().getWorldStateArchive(), - besuController.getProtocolSchedule(), - apiConfiguration.getGasCap())); - } - @Override public void stopNode(final BesuNode node) { final BesuPluginContextImpl pluginContext = besuPluginContextMap.remove(node); @@ -396,4 +270,331 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { public String getConsoleContents() { throw new RuntimeException("Console contents can only be captured in process execution"); } + + @Module + static class BesuNodeProviderModule { + + private final BesuNode toProvide; + + public BesuNodeProviderModule(final BesuNode toProvide) { + this.toProvide = toProvide; + } + + @Provides + public BesuNode provideBesuNodeRunner() { + return toProvide; + } + + @Provides + @Named("ExtraCLIOptions") + public List provideExtraCLIOptions() { + return toProvide.getExtraCLIOptions(); + } + + @Provides + Path provideDataDir() { + return toProvide.homeDirectory(); + } + + @Provides + @Singleton + RpcEndpointServiceImpl provideRpcEndpointService() { + return new RpcEndpointServiceImpl(); + } + + @Provides + @Singleton + BlockchainServiceImpl provideBlockchainService(final BesuController besuController) { + BlockchainServiceImpl retval = new BlockchainServiceImpl(); + retval.init(besuController.getProtocolContext(), besuController.getProtocolSchedule()); + return retval; + } + + @Provides + @Singleton + Blockchain provideBlockchain(final BesuController besuController) { + return besuController.getProtocolContext().getBlockchain(); + } + + @Provides + @SuppressWarnings("CloseableProvides") + WorldStateArchive provideWorldStateArchive(final BesuController besuController) { + return besuController.getProtocolContext().getWorldStateArchive(); + } + + @Provides + ProtocolSchedule provideProtocolSchedule(final BesuController besuController) { + return besuController.getProtocolSchedule(); + } + + @Provides + ApiConfiguration provideApiConfiguration(final BesuNode node) { + return node.getApiConfiguration(); + } + + @Provides + @Singleton + TransactionPoolValidatorServiceImpl provideTransactionPoolValidatorService() { + return new TransactionPoolValidatorServiceImpl(); + } + + @Provides + @Singleton + TransactionSelectionServiceImpl provideTransactionSelectionService() { + return new TransactionSelectionServiceImpl(); + } + + @Provides + @Singleton + TransactionPoolConfiguration provideTransactionPoolConfiguration( + final BesuNode node, + final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl) { + + TransactionPoolConfiguration txPoolConfig = + ImmutableTransactionPoolConfiguration.builder() + .from(node.getTransactionPoolConfiguration()) + .strictTransactionReplayProtectionEnabled(node.isStrictTxReplayProtectionEnabled()) + .transactionPoolValidatorService(transactionPoolValidatorServiceImpl) + .build(); + return txPoolConfig; + } + + @Provides + @Singleton + TransactionSimulator provideTransactionSimulator( + final Blockchain blockchain, + final WorldStateArchive worldStateArchive, + final ProtocolSchedule protocolSchedule, + final ApiConfiguration apiConfiguration) { + return new TransactionSimulator( + blockchain, worldStateArchive, protocolSchedule, apiConfiguration.getGasCap()); + } + + @Provides + @Singleton + TransactionSimulationServiceImpl provideTransactionSimulationService( + final Blockchain blockchain, final TransactionSimulator transactionSimulator) { + TransactionSimulationServiceImpl retval = new TransactionSimulationServiceImpl(); + retval.init(blockchain, transactionSimulator); + return retval; + } + } + + @Module + @SuppressWarnings("CloseableProvides") + static class BesuControllerModule { + @Provides + @Singleton + public SynchronizerConfiguration provideSynchronizationConfiguration() { + final SynchronizerConfiguration synchronizerConfiguration = + SynchronizerConfiguration.builder().build(); + return synchronizerConfiguration; + } + + @Singleton + @Provides + public BesuControllerBuilder provideBesuControllerBuilder( + final EthNetworkConfig ethNetworkConfig, + final SynchronizerConfiguration synchronizerConfiguration, + final TransactionPoolConfiguration transactionPoolConfiguration) { + + final BesuControllerBuilder builder = + new BesuController.Builder() + .fromEthNetworkConfig(ethNetworkConfig, synchronizerConfiguration.getSyncMode()); + builder.transactionPoolConfiguration(transactionPoolConfiguration); + return builder; + } + + @Provides + @Singleton + public BesuController provideBesuController( + final SynchronizerConfiguration synchronizerConfiguration, + final BesuControllerBuilder builder, + final ObservableMetricsSystem metricsSystem, + final KeyValueStorageProvider storageProvider, + final MiningParameters miningParameters) { + + builder + .synchronizerConfiguration(synchronizerConfiguration) + .metricsSystem(metricsSystem) + .dataStorageConfiguration(DataStorageConfiguration.DEFAULT_FOREST_CONFIG) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .clock(Clock.systemUTC()) + .storageProvider(storageProvider) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .maxPeers(25) + .maxRemotelyInitiatedPeers(15) + .miningParameters(miningParameters) + .randomPeerPriority(false) + .besuComponent(null); + return builder.build(); + } + + @Provides + @Singleton + public EthNetworkConfig.Builder provideEthNetworkConfigBuilder() { + final EthNetworkConfig.Builder networkConfigBuilder = + new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(NetworkName.DEV)); + return networkConfigBuilder; + } + + @Provides + public EthNetworkConfig provideEthNetworkConfig( + final EthNetworkConfig.Builder networkConfigBuilder) { + + final EthNetworkConfig ethNetworkConfig = networkConfigBuilder.build(); + return ethNetworkConfig; + } + + @Provides + public BesuPluginContextImpl providePluginContext( + final StorageServiceImpl storageService, + final SecurityModuleServiceImpl securityModuleService, + final TransactionSimulationServiceImpl transactionSimulationServiceImpl, + final TransactionSelectionServiceImpl transactionSelectionServiceImpl, + final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl, + final BlockchainServiceImpl blockchainServiceImpl, + final RpcEndpointServiceImpl rpcEndpointServiceImpl, + final BesuConfiguration commonPluginConfiguration, + final PermissioningServiceImpl permissioningService, + final @Named("ExtraCLIOptions") List extraCLIOptions) { + final CommandLine commandLine = new CommandLine(CommandSpec.create()); + final BesuPluginContextImpl besuPluginContext = new BesuPluginContextImpl(); + besuPluginContext.addService(StorageService.class, storageService); + besuPluginContext.addService(SecurityModuleService.class, securityModuleService); + besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine)); + besuPluginContext.addService(RpcEndpointService.class, rpcEndpointServiceImpl); + besuPluginContext.addService( + TransactionSelectionService.class, transactionSelectionServiceImpl); + besuPluginContext.addService( + TransactionPoolValidatorService.class, transactionPoolValidatorServiceImpl); + besuPluginContext.addService( + TransactionSimulationService.class, transactionSimulationServiceImpl); + besuPluginContext.addService(BlockchainService.class, blockchainServiceImpl); + besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); + + final Path pluginsPath; + final String pluginDir = System.getProperty("besu.plugins.dir"); + if (pluginDir == null || pluginDir.isEmpty()) { + pluginsPath = commonPluginConfiguration.getDataPath().resolve("plugins"); + final File pluginsDirFile = pluginsPath.toFile(); + if (!pluginsDirFile.isDirectory()) { + pluginsDirFile.mkdirs(); + pluginsDirFile.deleteOnExit(); + } + System.setProperty("besu.plugins.dir", pluginsPath.toString()); + } else { + pluginsPath = Path.of(pluginDir); + } + + besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); + besuPluginContext.addService(PermissioningService.class, permissioningService); + besuPluginContext.addService(PrivacyPluginService.class, new PrivacyPluginServiceImpl()); + + besuPluginContext.registerPlugins( + new PluginConfiguration.Builder().pluginsDir(pluginsPath).build()); + commandLine.parseArgs(extraCLIOptions.toArray(new String[0])); + + // register built-in plugins + new RocksDBPlugin().register(besuPluginContext); + return besuPluginContext; + } + + @Provides + public KeyValueStorageProvider provideKeyValueStorageProvider( + final BesuConfiguration commonPluginConfiguration, final MetricsSystem metricsSystem) { + + final StorageServiceImpl storageService = new StorageServiceImpl(); + storageService.registerKeyValueStorage( + new InMemoryStoragePlugin.InMemoryKeyValueStorageFactory("memory")); + final KeyValueStorageProvider storageProvider = + new KeyValueStorageProviderBuilder() + .withStorageFactory(storageService.getByName("memory").get()) + .withCommonConfiguration(commonPluginConfiguration) + .withMetricsSystem(metricsSystem) + .build(); + + return storageProvider; + } + + @Provides + public MiningParameters provideMiningParameters( + final TransactionSelectionServiceImpl transactionSelectionServiceImpl, + final BesuNode node) { + final var miningParameters = + ImmutableMiningParameters.builder() + .from(node.getMiningParameters()) + .transactionSelectionService(transactionSelectionServiceImpl) + .build(); + + return miningParameters; + } + + @Provides + @Inject + BesuConfiguration provideBesuConfiguration( + final Path dataDir, final MiningParameters miningParameters, final BesuNode node) { + final BesuConfigurationImpl commonPluginConfiguration = new BesuConfigurationImpl(); + commonPluginConfiguration.init( + dataDir, dataDir.resolve(DATABASE_PATH), node.getDataStorageConfiguration()); + commonPluginConfiguration.withMiningParameters(miningParameters); + return commonPluginConfiguration; + } + } + + @Module + static class MockBesuCommandModule { + + @Provides + BesuCommand provideBesuCommand(final BesuPluginContextImpl pluginContext) { + final BesuCommand besuCommand = + new BesuCommand( + RlpBlockImporter::new, + JsonBlockImporter::new, + RlpBlockExporter::new, + new RunnerBuilder(), + new BesuController.Builder(), + pluginContext, + System.getenv(), + LoggerFactory.getLogger(MockBesuCommandModule.class)); + besuCommand.toCommandLine(); + return besuCommand; + } + + @Provides + @Singleton + MetricsConfiguration provideMetricsConfiguration() { + return MetricsConfiguration.builder().build(); + } + + @Provides + @Named("besuCommandLogger") + @Singleton + Logger provideBesuCommandLogger() { + return LoggerFactory.getLogger(MockBesuCommandModule.class); + } + } + + @Singleton + @Component( + modules = { + ThreadBesuNodeRunner.BesuControllerModule.class, + ThreadBesuNodeRunner.MockBesuCommandModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + MetricsSystemModule.class, + ThreadBesuNodeRunner.BesuNodeProviderModule.class, + BlobCacheModule.class + }) + public interface AcceptanceTestBesuComponent extends BesuComponent { + BesuController besuController(); + + BesuControllerBuilder besuControllerBuilder(); // TODO: needing this sucks + + EthNetworkConfig.Builder ethNetworkConfigBuilder(); + + RpcEndpointServiceImpl rpcEndpointService(); + + BlockchainServiceImpl blockchainService(); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index efbf91246..9660f6ee9 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -511,31 +511,6 @@ public class BesuNodeFactory { return create(builder.build()); } - public BesuNode createQbftNodeWithTLS(final String name, final String type) throws IOException { - return create( - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .p2pTLSEnabled(name, type) - .jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false)) - .webSocketConfiguration(node.createWebSocketEnabledConfig()) - .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig) - .build()); - } - - public BesuNode createQbftNodeWithTLSJKS(final String name) throws IOException { - return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_JKS); - } - - public BesuNode createQbftNodeWithTLSPKCS12(final String name) throws IOException { - return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_PKCS12); - } - - public BesuNode createQbftNodeWithTLSPKCS11(final String name) throws IOException { - return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_PKCS11); - } - public BesuNode createQbftNode( final String name, final boolean fixedPort, final DataStorageFormat storageFormat) throws IOException { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java index 520cd7d57..d54bf9087 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java @@ -276,7 +276,7 @@ public class PrivacyNode implements AutoCloseable { final Path dataLocation, final Path dbLocation) { final var besuConfiguration = new BesuConfigurationImpl(); besuConfiguration - .init(dataLocation, dbLocation, null) + .init(dataLocation, dbLocation, besuConfig.getDataStorageConfiguration()) .withMiningParameters(besuConfig.getMiningParameters()); return new PrivacyKeyValueStorageProviderBuilder() .withStorageFactory( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java index 8fbd0d3d6..39cc5ad26 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java @@ -38,7 +38,7 @@ public class ClusterThreadNodeRunnerAcceptanceTest extends AcceptanceTestBase { final BesuNodeRunner besuNodeRunner = new ThreadBesuNodeRunner(); noDiscoveryCluster = new Cluster(clusterConfiguration, net, besuNodeRunner); final BesuNode noDiscoveryNode = besu.createNodeWithNoDiscovery("noDiscovery"); - fullNode = besu.createArchiveNode("node2"); + fullNode = besu.createArchiveNode("archive"); noDiscoveryCluster.start(noDiscoveryNode, fullNode); } 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/CodeDelegationTransactionAcceptanceTest.java similarity index 76% rename from acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/SetCodeTransactionAcceptanceTest.java rename to acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.java index b134b1f5c..6000915b4 100644 --- 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/CodeDelegationTransactionAcceptanceTest.java @@ -18,9 +18,9 @@ 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.CodeDelegation; 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; @@ -39,7 +39,7 @@ 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 { +public class CodeDelegationTransactionAcceptanceTest extends AcceptanceTestBase { private static final String GENESIS_FILE = "/dev/dev_prague.json"; private static final SECP256K1 secp256k1 = new SECP256K1(); @@ -74,7 +74,6 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { @AfterEach void tearDown() { besuNode.close(); - cluster.close(); } /** @@ -88,17 +87,18 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException { // 7702 transaction - final org.hyperledger.besu.datatypes.SetCodeAuthorization authorization = - SetCodeAuthorization.builder() + final CodeDelegation authorization = + org.hyperledger.besu.ethereum.core.CodeDelegation.builder() .chainId(BigInteger.valueOf(20211)) .address(SEND_ALL_ETH_CONTRACT_ADDRESS) + .nonce(0) .signAndBuild( secp256k1.createKeyPair( secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); final Transaction tx = Transaction.builder() - .type(TransactionType.SET_CODE) + .type(TransactionType.DELEGATE_CODE) .chainId(BigInteger.valueOf(20211)) .nonce(0) .maxPriorityFeePerGas(Wei.of(1000000000)) @@ -108,7 +108,7 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { .value(Wei.ZERO) .payload(Bytes32.leftPad(Bytes.fromHexString(transactionSponsor.getAddress()))) .accessList(List.of()) - .setCodeTransactionPayloads(List.of(authorization)) + .codeDelegations(List.of(authorization)) .signAndBuild( secp256k1.createKeyPair( secp256k1.createPrivateKey( @@ -134,22 +134,21 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { /** * The authorizer creates an authorization for a contract that sends all its ETH to any given - * address. But the nonce is 1 and the authorization list is processed before the nonce increase - * of the sender. Therefore, the authorization should be invalid and will be ignored. No balance - * change, except for a decrease for paying the transaction cost should occur. + * address. The nonce is 1 and the authorization list is processed after the nonce increase of the + * sender. Therefore, the authorization should be valid. The authorizer balance should be 0 and + * the transaction sponsor's * balance should be 180000 ETH minus the transaction costs. */ @Test - public void shouldCheckNonceBeforeNonceIncreaseOfSender() throws IOException { - + public void shouldCheckNonceAfterNonceIncreaseOfSender() throws IOException { + final long GAS_LIMIT = 1000000L; cluster.verify(authorizer.balanceEquals(Amount.ether(90000))); - final org.hyperledger.besu.datatypes.SetCodeAuthorization authorization = - SetCodeAuthorization.builder() + final CodeDelegation authorization = + org.hyperledger.besu.ethereum.core.CodeDelegation.builder() .chainId(BigInteger.valueOf(20211)) - .nonces( - Optional.of( - 1L)) // nonce is 1, but because it is validated before the nonce increase, it - // should be 0 + .nonce( + 1L) // nonce is 1, but because it is validated before the nonce increase, it should + // be 0 .address(SEND_ALL_ETH_CONTRACT_ADDRESS) .signAndBuild( secp256k1.createKeyPair( @@ -157,17 +156,17 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { final Transaction tx = Transaction.builder() - .type(TransactionType.SET_CODE) + .type(TransactionType.DELEGATE_CODE) .chainId(BigInteger.valueOf(20211)) .nonce(0) .maxPriorityFeePerGas(Wei.of(1000000000)) .maxFeePerGas(Wei.fromHexString("0x02540BE400")) - .gasLimit(1000000) + .gasLimit(GAS_LIMIT) .to(Address.fromHexStringStrict(authorizer.getAddress())) .value(Wei.ZERO) .payload(Bytes32.leftPad(Bytes.fromHexString(otherAccount.getAddress()))) .accessList(List.of()) - .setCodeTransactionPayloads(List.of(authorization)) + .codeDelegations(List.of(authorization)) .signAndBuild( secp256k1.createKeyPair( secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); @@ -180,14 +179,25 @@ public class SetCodeTransactionAcceptanceTest extends AcceptanceTestBase { besuNode.execute(ethTransactions.getTransactionReceipt(txHash)); assertThat(maybeTransactionReceipt).isPresent(); - // verify that the balance of the other account has not changed - cluster.verify(otherAccount.balanceEquals(0)); - final String gasPriceWithout0x = maybeTransactionReceipt.get().getEffectiveGasPrice().substring(2); - final BigInteger txCost = - maybeTransactionReceipt.get().getGasUsed().multiply(new BigInteger(gasPriceWithout0x, 16)); - BigInteger expectedSenderBalance = new BigInteger("90000000000000000000000").subtract(txCost); - cluster.verify(authorizer.balanceEquals(Amount.wei(expectedSenderBalance))); + final BigInteger gasPrice = new BigInteger(gasPriceWithout0x, 16); + final BigInteger txCost = maybeTransactionReceipt.get().getGasUsed().multiply(gasPrice); + + final BigInteger authorizerBalance = besuNode.execute(ethTransactions.getBalance(authorizer)); + + // The remaining balance of the authorizer should the gas limit multiplied by the gas price + // minus the transaction cost. + // The following executes this calculation in reverse. + assertThat(GAS_LIMIT).isEqualTo(authorizerBalance.add(txCost).divide(gasPrice).longValue()); + + // The other accounts balance should be the initial 9000 ETH balance from the authorizer minus + // the remaining balance of the authorizer and minus the transaction cost + cluster.verify( + otherAccount.balanceEquals( + Amount.wei( + new BigInteger("90000000000000000000000") + .subtract(authorizerBalance) + .subtract(txCost)))); } } diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/16_prague_getPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/16_prague_getPayloadV4.json index c3fdd33ad..51843931e 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/16_prague_getPayloadV4.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/16_prague_getPayloadV4.json @@ -40,8 +40,8 @@ "consolidationRequests": [ { "sourceAddress": "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", - "sourcePubKey": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - "targetPubKey": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + "sourcePubkey": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "targetPubkey": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" } ], "blockNumber": "0x4", diff --git a/besu/build.gradle b/besu/build.gradle index a0e6c7843..fa2b0548c 100644 --- a/besu/build.gradle +++ b/besu/build.gradle @@ -105,6 +105,8 @@ dependencies { testImplementation 'org.mockito:mockito-core' testImplementation 'org.testcontainers:testcontainers' testImplementation 'tech.pegasys.discovery:discovery' + testImplementation 'com.google.dagger:dagger' annotationProcessor 'com.google.dagger:dagger-compiler' + testAnnotationProcessor 'com.google.dagger:dagger-compiler' } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 00ea6e1ef..f79ef45b9 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -84,7 +84,6 @@ import org.hyperledger.besu.cli.util.BesuCommandCustomFactory; import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.cli.util.ConfigDefaultValueProviderStrategy; import org.hyperledger.besu.cli.util.VersionProvider; -import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.CheckpointConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -865,13 +864,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private BesuController besuController; private BesuConfigurationImpl pluginCommonConfiguration; - private BesuComponent besuComponent; private final Supplier metricsSystem = - Suppliers.memoize( - () -> - besuComponent == null || besuComponent.getObservableMetricsSystem() == null - ? MetricsSystemFactory.create(metricsConfiguration()) - : besuComponent.getObservableMetricsSystem()); + Suppliers.memoize(() -> MetricsSystemFactory.create(metricsConfiguration())); + private Vertx vertx; private EnodeDnsConfiguration enodeDnsConfiguration; private KeyValueStorageProvider keyValueStorageProvider; @@ -879,7 +874,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable { /** * Besu command constructor. * - * @param besuComponent BesuComponent which acts as our application context * @param rlpBlockImporter RlpBlockImporter supplier * @param jsonBlockImporterFactory instance of {@code Function} * @param rlpBlockExporterFactory instance of {@code Function} @@ -887,18 +881,18 @@ public class BesuCommand implements DefaultCommandValues, Runnable { * @param controllerBuilder instance of BesuController.Builder * @param besuPluginContext instance of BesuPluginContextImpl * @param environment Environment variables map + * @param commandLogger instance of Logger for outputting to the CLI */ public BesuCommand( - final BesuComponent besuComponent, final Supplier rlpBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, final RunnerBuilder runnerBuilder, final BesuController.Builder controllerBuilder, final BesuPluginContextImpl besuPluginContext, - final Map environment) { + final Map environment, + final Logger commandLogger) { this( - besuComponent, rlpBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, @@ -914,13 +908,13 @@ public class BesuCommand implements DefaultCommandValues, Runnable { new TransactionSelectionServiceImpl(), new TransactionPoolValidatorServiceImpl(), new TransactionSimulationServiceImpl(), - new BlockchainServiceImpl()); + new BlockchainServiceImpl(), + commandLogger); } /** * Overloaded Besu command constructor visible for testing. * - * @param besuComponent BesuComponent which acts as our application context * @param rlpBlockImporter RlpBlockImporter supplier * @param jsonBlockImporterFactory instance of {@code Function} * @param rlpBlockExporterFactory instance of {@code Function} @@ -937,10 +931,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable { * @param transactionValidatorServiceImpl instance of TransactionValidatorServiceImpl * @param transactionSimulationServiceImpl instance of TransactionSimulationServiceImpl * @param blockchainServiceImpl instance of BlockchainServiceImpl + * @param commandLogger instance of Logger for outputting to the CLI */ @VisibleForTesting protected BesuCommand( - final BesuComponent besuComponent, final Supplier rlpBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, @@ -956,9 +950,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable { final TransactionSelectionServiceImpl transactionSelectionServiceImpl, final TransactionPoolValidatorServiceImpl transactionValidatorServiceImpl, final TransactionSimulationServiceImpl transactionSimulationServiceImpl, - final BlockchainServiceImpl blockchainServiceImpl) { - this.besuComponent = besuComponent; - this.logger = besuComponent.getBesuCommandLogger(); + final BlockchainServiceImpl blockchainServiceImpl, + final Logger commandLogger) { + + this.logger = commandLogger; this.rlpBlockImporter = rlpBlockImporter; this.rlpBlockExporterFactory = rlpBlockExporterFactory; this.jsonBlockImporterFactory = jsonBlockImporterFactory; @@ -970,8 +965,13 @@ public class BesuCommand implements DefaultCommandValues, Runnable { this.securityModuleService = securityModuleService; this.permissioningService = permissioningService; this.privacyPluginService = privacyPluginService; - this.pluginCommonConfiguration = new BesuConfigurationImpl(); - besuPluginContext.addService(BesuConfiguration.class, pluginCommonConfiguration); + if (besuPluginContext.getService(BesuConfigurationImpl.class).isPresent()) { + this.pluginCommonConfiguration = + besuPluginContext.getService(BesuConfigurationImpl.class).get(); + } else { + this.pluginCommonConfiguration = new BesuConfigurationImpl(); + besuPluginContext.addService(BesuConfiguration.class, this.pluginCommonConfiguration); + } this.rpcEndpointServiceImpl = rpcEndpointServiceImpl; this.transactionSelectionServiceImpl = transactionSelectionServiceImpl; this.transactionValidatorServiceImpl = transactionValidatorServiceImpl; @@ -1821,9 +1821,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { */ public BesuController buildController() { try { - return this.besuComponent == null - ? getControllerBuilder().build() - : getControllerBuilder().besuComponent(this.besuComponent).build(); + return setupControllerBuilder().build(); } catch (final Exception e) { throw new ExecutionException(this.commandLine, e.getMessage(), e); } @@ -1834,7 +1832,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { * * @return instance of BesuControllerBuilder */ - public BesuControllerBuilder getControllerBuilder() { + public BesuControllerBuilder setupControllerBuilder() { pluginCommonConfiguration .init(dataDir(), dataDir().resolve(DATABASE_PATH), getDataStorageConfiguration()) .withMiningParameters(miningParametersSupplier.get()) @@ -2800,7 +2798,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { builder.setTxPoolImplementation(buildTransactionPoolConfiguration().getTxPoolImplementation()); builder.setWorldStateUpdateMode(unstableEvmOptions.toDomainObject().worldUpdaterMode()); - builder.setPluginContext(besuComponent.getBesuPluginContext()); + builder.setPluginContext(this.besuPluginContext); return builder.build(); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java index 6f5381a96..6d1a6141e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java @@ -160,6 +160,7 @@ public class TransactionPoolOptions implements CLIOptions> securityModuleSuppliers = - new ConcurrentHashMap<>(); /** Default Constructor. */ + @Inject public SecurityModuleServiceImpl() {} + private final Map> securityModuleSuppliers = + new ConcurrentHashMap<>(); + @Override public void register(final String name, final Supplier securityModuleSupplier) { securityModuleSuppliers.put(name, securityModuleSupplier); diff --git a/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java index 6629da690..dd5d822cc 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import javax.inject.Inject; /** The Storage service implementation. */ public class StorageServiceImpl implements StorageService { @@ -31,6 +32,7 @@ public class StorageServiceImpl implements StorageService { private final Map factories; /** Instantiates a new Storage service. */ + @Inject public StorageServiceImpl() { this.segments = List.of(KeyValueSegmentIdentifier.values()); this.factories = new ConcurrentHashMap<>(); diff --git a/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java index 8595a3c03..1ea8db7ea 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java @@ -23,11 +23,11 @@ import java.util.Optional; /** The Transaction Selection service implementation. */ public class TransactionSelectionServiceImpl implements TransactionSelectionService { - private Optional factory = Optional.empty(); - /** Default Constructor. */ public TransactionSelectionServiceImpl() {} + private Optional factory = Optional.empty(); + @Override public PluginTransactionSelector createPluginTransactionSelector() { return factory diff --git a/besu/src/test/java/org/hyperledger/besu/FlexGroupPrivacyTest.java b/besu/src/test/java/org/hyperledger/besu/FlexGroupPrivacyTest.java new file mode 100644 index 000000000..a246d18d2 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/FlexGroupPrivacyTest.java @@ -0,0 +1,168 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY; + +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.components.BesuPluginContextModule; +import org.hyperledger.besu.components.MockBesuCommandModule; +import org.hyperledger.besu.components.NoOpMetricsSystemModule; +import org.hyperledger.besu.components.PrivacyTestModule; +import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.controller.BesuController; +import org.hyperledger.besu.cryptoservices.NodeKeyUtils; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.enclave.EnclaveFactory; +import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.precompile.PrecompiledContract; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.testutil.TestClock; + +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import io.vertx.core.Vertx; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class FlexGroupPrivacyTest { + + private final Vertx vertx = Vertx.vertx(); + + @AfterEach + public void cleanUp() { + vertx.close(); + } + + @Test + void flexibleEnabledPrivacy() { + final BesuController besuController = + DaggerFlexGroupPrivacyTest_FlexGroupPrivacyTestComponent.builder() + .build() + .getBesuController(); + + final PrecompiledContract flexiblePrecompiledContract = + getPrecompile(besuController, FLEXIBLE_PRIVACY); + + assertThat(flexiblePrecompiledContract.getName()).isEqualTo("FlexiblePrivacy"); + } + + private PrecompiledContract getPrecompile( + final BesuController besuController, final Address defaultPrivacy) { + return besuController + .getProtocolSchedule() + .getByBlockHeader(blockHeader(0)) + .getPrecompileContractRegistry() + .get(defaultPrivacy); + } + + private BlockHeader blockHeader(final long number) { + return new BlockHeaderTestFixture().number(number).buildHeader(); + } + + @Singleton + @Component( + modules = { + FlexGroupPrivacyParametersModule.class, + FlexGroupPrivacyTest.PrivacyTestBesuControllerModule.class, + PrivacyTestModule.class, + MockBesuCommandModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + NoOpMetricsSystemModule.class, + BesuPluginContextModule.class, + BlobCacheModule.class + }) + interface FlexGroupPrivacyTestComponent extends BesuComponent { + BesuController getBesuController(); + } + + @Module + static class FlexGroupPrivacyParametersModule { + + @Provides + PrivacyParameters providePrivacyParameters( + final PrivacyStorageProvider storageProvider, final Vertx vertx) { + try { + return new PrivacyParameters.Builder() + .setEnabled(true) + .setEnclaveUrl(new URI("http://127.0.0.1:8000")) + .setStorageProvider(storageProvider) + .setEnclaveFactory(new EnclaveFactory(vertx)) + .setFlexiblePrivacyGroupsEnabled(true) + .build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + } + + @Module + static class PrivacyTestBesuControllerModule { + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + BesuController provideBesuController( + final PrivacyParameters privacyParameters, + final DataStorageConfiguration dataStorageConfiguration, + final FlexGroupPrivacyTestComponent context, + @Named("dataDir") final Path dataDir) { + + return new BesuController.Builder() + .fromGenesisFile(GenesisConfigFile.mainnet(), SyncMode.FULL) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryKeyValueStorageProvider()) + .networkId(BigInteger.ONE) + .miningParameters(MiningParameters.newDefault()) + .dataStorageConfiguration(dataStorageConfiguration) + .nodeKey(NodeKeyUtils.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .privacyParameters(privacyParameters) + .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(context) + .build(); + } + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java index ceb0be40f..6298b70a9 100644 --- a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java +++ b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java @@ -21,6 +21,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.components.BesuPluginContextModule; +import org.hyperledger.besu.components.EnclaveModule; +import org.hyperledger.besu.components.MockBesuCommandModule; +import org.hyperledger.besu.components.NoOpMetricsSystemModule; +import org.hyperledger.besu.components.PrivacyTestModule; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.crypto.KeyPair; @@ -47,7 +53,9 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; @@ -56,6 +64,7 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.LogsBloomFilter; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -70,19 +79,21 @@ import java.nio.file.Path; import java.util.Collections; import java.util.Optional; import java.util.function.Supplier; +import javax.inject.Named; +import javax.inject.Singleton; import com.google.common.base.Suppliers; +import dagger.Component; +import dagger.Module; +import dagger.Provides; 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.junit.jupiter.api.io.TempDir; @SuppressWarnings("rawtypes") public class PrivacyReorgTest { - @TempDir private Path folder; - private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); @@ -130,11 +141,15 @@ public class PrivacyReorgTest { .signAndBuild(KEY_PAIR); private final BlockDataGenerator gen = new BlockDataGenerator(); - private BesuController besuController; - private PrivateStateRootResolver privateStateRootResolver; private PrivacyParameters privacyParameters; private Enclave mockEnclave; private Transaction privacyMarkerTransaction; + private final PrivacyReorgTestComponent component = + DaggerPrivacyReorgTest_PrivacyReorgTestComponent.create(); + + private final BesuController besuController = component.getBesuController(); + private final PrivateStateRootResolver privateStateRootResolver = + component.getPrivacyParameters().getPrivateStateRootResolver(); @BeforeEach public void setUp() throws IOException { @@ -174,29 +189,6 @@ public class PrivacyReorgTest { .build(); privacyParameters.setPrivacyUserId(ENCLAVE_PUBLIC_KEY.toBase64String()); - - privateStateRootResolver = - new PrivateStateRootResolver(privacyParameters.getPrivateStateStorage()); - - besuController = - new BesuController.Builder() - .fromGenesisFile( - GenesisConfigFile.fromResource("/privacy_reorg_genesis.json"), SyncMode.FULL) - .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) - .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) - .storageProvider(new InMemoryKeyValueStorageProvider()) - .networkId(BigInteger.ONE) - .miningParameters(MiningParameters.newDefault()) - .nodeKey(NodeKeyUtils.generate()) - .metricsSystem(new NoOpMetricsSystem()) - .dataDirectory(folder) - .clock(TestClock.fixed()) - .privacyParameters(privacyParameters) - .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) - .gasLimitCalculator(GasLimitCalculator.constant()) - .evmConfiguration(EvmConfiguration.DEFAULT) - .networkConfiguration(NetworkingConfiguration.create()) - .build(); } @Test @@ -204,7 +196,8 @@ public class PrivacyReorgTest { // Setup an initial blockchain with one private transaction final ProtocolContext protocolContext = besuController.getProtocolContext(); final DefaultBlockchain blockchain = (DefaultBlockchain) protocolContext.getBlockchain(); - final PrivateStateStorage privateStateStorage = privacyParameters.getPrivateStateStorage(); + final PrivateStateStorage privateStateStorage = + component.getPrivacyParameters().getPrivateStateStorage(); final Block firstBlock = gen.block( @@ -244,7 +237,7 @@ public class PrivacyReorgTest { // Setup an initial blockchain with one private transaction final ProtocolContext protocolContext = besuController.getProtocolContext(); final DefaultBlockchain blockchain = (DefaultBlockchain) protocolContext.getBlockchain(); - + assertThat(blockchain.getChainHeadBlockNumber()).isEqualTo(0); final Block firstBlock = gen.block( getBlockOptionsWithTransaction( @@ -252,8 +245,9 @@ public class PrivacyReorgTest { privacyMarkerTransaction, FIRST_BLOCK_WITH_SINGLE_TRANSACTION_STATE_ROOT)); - appendBlock(besuController, blockchain, protocolContext, firstBlock); - + var importResult = appendBlock(besuController, blockchain, protocolContext, firstBlock); + assertThat(importResult.isImported()).isTrue(); + assertThat(blockchain.getChainHeadBlockNumber()).isEqualTo(1); // Check that the private state root is not the empty state assertPrivateStateRoot( privateStateRootResolver, blockchain, STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE); @@ -394,12 +388,12 @@ public class PrivacyReorgTest { } @SuppressWarnings("unchecked") - private void appendBlock( + private BlockImportResult appendBlock( final BesuController besuController, final DefaultBlockchain blockchain, final ProtocolContext protocolContext, final Block block) { - besuController + return besuController .getProtocolSchedule() .getByBlockHeader(blockchain.getChainHeadHeader()) .getBlockImporter() @@ -487,4 +481,93 @@ public class PrivacyReorgTest { .hasOmmers(false) .setLogsBloom(LogsBloomFilter.empty()); } + + @Singleton + @Component( + modules = { + PrivacyReorgTest.PrivacyReorgParametersModule.class, + PrivacyReorgTest.PrivacyReorgTestBesuControllerModule.class, + PrivacyReorgTest.PrivacyReorgTestGenesisConfigModule.class, + EnclaveModule.class, + PrivacyTestModule.class, + MockBesuCommandModule.class, + NoOpMetricsSystemModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + BlobCacheModule.class, + BesuPluginContextModule.class + }) + interface PrivacyReorgTestComponent extends BesuComponent { + + BesuController getBesuController(); + + PrivacyParameters getPrivacyParameters(); + } + + @Module + static class PrivacyReorgParametersModule { + + // TODO: copypasta, get this from the enclave factory + private static final Bytes ENCLAVE_PUBLIC_KEY = + Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); + + @Provides + PrivacyParameters providePrivacyReorgParameters( + final PrivacyStorageProvider storageProvider, final EnclaveFactory enclaveFactory) { + + PrivacyParameters retval = + new PrivacyParameters.Builder() + .setEnabled(true) + .setStorageProvider(storageProvider) + .setEnclaveUrl(URI.create("http//1.1.1.1:1234")) + .setEnclaveFactory(enclaveFactory) + .build(); + retval.setPrivacyUserId(ENCLAVE_PUBLIC_KEY.toBase64String()); + return retval; + } + } + + @Module + static class PrivacyReorgTestBesuControllerModule { + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + BesuController provideBesuController( + final PrivacyParameters privacyParameters, + final GenesisConfigFile genesisConfigFile, + final PrivacyReorgTestComponent context, + final @Named("dataDir") Path dataDir) { + + // dataStorageConfiguration default + // named privacyReorgParams + BesuController retval = + new BesuController.Builder() + .fromGenesisFile(genesisConfigFile, SyncMode.FULL) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryKeyValueStorageProvider()) + .networkId(BigInteger.ONE) + .miningParameters(MiningParameters.newDefault()) + .nodeKey(NodeKeyUtils.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .privacyParameters(privacyParameters) + .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(context) + .build(); + return retval; + } + } + + @Module + static class PrivacyReorgTestGenesisConfigModule { + @Provides + GenesisConfigFile providePrivacyReorgGenesisConfigFile() { + return GenesisConfigFile.fromResource("/privacy_reorg_genesis.json"); + } + } } diff --git a/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java b/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java index dc5b7003c..4d488ced3 100644 --- a/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java +++ b/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 @@ -16,18 +16,17 @@ package org.hyperledger.besu; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.core.PrivacyParameters.DEFAULT_PRIVACY; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_BACKGROUND_THREAD_COUNT; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; -import org.hyperledger.besu.cli.config.EthNetworkConfig; -import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.components.BesuPluginContextModule; +import org.hyperledger.besu.components.MockBesuCommandModule; +import org.hyperledger.besu.components.NoOpMetricsSystemModule; +import org.hyperledger.besu.components.PrivacyParametersModule; +import org.hyperledger.besu.components.PrivacyTestModule; +import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.enclave.EnclaveFactory; import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; @@ -37,126 +36,47 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; -import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; -import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.precompile.PrecompiledContract; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValuePrivacyStorageFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; -import org.hyperledger.besu.services.BesuConfigurationImpl; import org.hyperledger.besu.testutil.TestClock; -import java.io.IOException; import java.math.BigInteger; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; +import javax.inject.Named; +import javax.inject.Singleton; +import dagger.Component; +import dagger.Module; +import dagger.Provides; import io.vertx.core.Vertx; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -public class PrivacyTest { +class PrivacyTest { private final Vertx vertx = Vertx.vertx(); - @TempDir private Path dataDir; - @AfterEach public void cleanUp() { vertx.close(); } @Test - public void defaultPrivacy() throws IOException, URISyntaxException { - final BesuController besuController = setUpControllerWithPrivacyEnabled(false); + void defaultPrivacy() { + final BesuController besuController = + DaggerPrivacyTest_PrivacyTestComponent.builder().build().getBesuController(); final PrecompiledContract precompiledContract = getPrecompile(besuController, DEFAULT_PRIVACY); assertThat(precompiledContract.getName()).isEqualTo("Privacy"); } - @Test - public void flexibleEnabledPrivacy() throws IOException, URISyntaxException { - final BesuController besuController = setUpControllerWithPrivacyEnabled(true); - - final PrecompiledContract flexiblePrecompiledContract = - getPrecompile(besuController, FLEXIBLE_PRIVACY); - - assertThat(flexiblePrecompiledContract.getName()).isEqualTo("FlexiblePrivacy"); - } - - private BesuController setUpControllerWithPrivacyEnabled(final boolean flexibleEnabled) - throws IOException, URISyntaxException { - final Path dbDir = Files.createTempDirectory(dataDir, "database"); - final var miningParameters = MiningParameters.newDefault(); - final var dataStorageConfiguration = DataStorageConfiguration.DEFAULT_FOREST_CONFIG; - final PrivacyParameters privacyParameters = - new PrivacyParameters.Builder() - .setEnabled(true) - .setEnclaveUrl(new URI("http://127.0.0.1:8000")) - .setStorageProvider( - createKeyValueStorageProvider( - dataDir, dbDir, dataStorageConfiguration, miningParameters)) - .setEnclaveFactory(new EnclaveFactory(vertx)) - .setFlexiblePrivacyGroupsEnabled(flexibleEnabled) - .build(); - return new BesuController.Builder() - .fromEthNetworkConfig(EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET), SyncMode.FULL) - .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) - .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) - .storageProvider(new InMemoryKeyValueStorageProvider()) - .networkId(BigInteger.ONE) - .miningParameters(miningParameters) - .dataStorageConfiguration(dataStorageConfiguration) - .nodeKey(NodeKeyUtils.generate()) - .metricsSystem(new NoOpMetricsSystem()) - .dataDirectory(dataDir) - .clock(TestClock.fixed()) - .privacyParameters(privacyParameters) - .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) - .gasLimitCalculator(GasLimitCalculator.constant()) - .evmConfiguration(EvmConfiguration.DEFAULT) - .networkConfiguration(NetworkingConfiguration.create()) - .build(); - } - - private PrivacyStorageProvider createKeyValueStorageProvider( - final Path dataDir, - final Path dbDir, - final DataStorageConfiguration dataStorageConfiguration, - final MiningParameters miningParameters) { - final var besuConfiguration = new BesuConfigurationImpl(); - besuConfiguration - .init(dataDir, dbDir, dataStorageConfiguration) - .withMiningParameters(miningParameters); - return new PrivacyKeyValueStorageProviderBuilder() - .withStorageFactory( - new RocksDBKeyValuePrivacyStorageFactory( - new RocksDBKeyValueStorageFactory( - () -> - new RocksDBFactoryConfiguration( - DEFAULT_MAX_OPEN_FILES, - DEFAULT_BACKGROUND_THREAD_COUNT, - DEFAULT_CACHE_CAPACITY, - DEFAULT_IS_HIGH_SPEC), - Arrays.asList(KeyValueSegmentIdentifier.values()), - RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS))) - .withCommonConfiguration(besuConfiguration) - .withMetricsSystem(new NoOpMetricsSystem()) - .build(); - } - private PrecompiledContract getPrecompile( final BesuController besuController, final Address defaultPrivacy) { return besuController @@ -169,4 +89,55 @@ public class PrivacyTest { private BlockHeader blockHeader(final long number) { return new BlockHeaderTestFixture().number(number).buildHeader(); } + + @Singleton + @Component( + modules = { + PrivacyParametersModule.class, + PrivacyTest.PrivacyTestBesuControllerModule.class, + PrivacyTestModule.class, + MockBesuCommandModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + NoOpMetricsSystemModule.class, + BesuPluginContextModule.class, + BlobCacheModule.class + }) + interface PrivacyTestComponent extends BesuComponent { + + BesuController getBesuController(); + } + + @Module + static class PrivacyTestBesuControllerModule { + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + BesuController provideBesuController( + final PrivacyParameters privacyParameters, + final DataStorageConfiguration dataStorageConfiguration, + final PrivacyTestComponent context, + @Named("dataDir") final Path dataDir) { + + return new BesuController.Builder() + .fromGenesisFile(GenesisConfigFile.mainnet(), SyncMode.FULL) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryKeyValueStorageProvider()) + .networkId(BigInteger.ONE) + .miningParameters(MiningParameters.newDefault()) + .dataStorageConfiguration(dataStorageConfiguration) + .nodeKey(NodeKeyUtils.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .privacyParameters(privacyParameters) + .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(context) + .build(); + } + } } diff --git a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java index 5b2a56078..5fefc7ef2 100644 --- a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java +++ b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java @@ -22,8 +22,10 @@ import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.JsonUtil; import org.hyperledger.besu.config.MergeConfigOptions; @@ -483,6 +485,7 @@ public final class RunnerTest { .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) .randomPeerPriority(Boolean.FALSE) + .besuComponent(mock(BesuComponent.class)) .maxPeers(25) .maxRemotelyInitiatedPeers(15) .build(); diff --git a/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java index 05057f15c..bbc7dea1a 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java @@ -16,10 +16,12 @@ package org.hyperledger.besu.chainexport; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.chainimport.RlpBlockImporter; import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.ethereum.GasLimitCalculator; @@ -102,6 +104,7 @@ public final class RlpBlockExporterTest { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); } diff --git a/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java index 73015afd0..7b1a4bc7d 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java @@ -17,7 +17,9 @@ package org.hyperledger.besu.chainimport; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.JsonUtil; import org.hyperledger.besu.controller.BesuController; @@ -463,6 +465,7 @@ public abstract class JsonBlockImporterTest { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); } } diff --git a/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java index e37b8a5fd..7d4fabb22 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java @@ -16,9 +16,11 @@ package org.hyperledger.besu.chainimport; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.MergeConfigOptions; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; @@ -77,6 +79,7 @@ public final class RlpBlockImporterTest { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); final RlpBlockImporter.ImportResult result = rlpBlockImporter.importBlockchain(source, targetController, false); @@ -110,6 +113,7 @@ public final class RlpBlockImporterTest { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); assertThatThrownBy( @@ -140,6 +144,7 @@ public final class RlpBlockImporterTest { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); final RlpBlockImporter.ImportResult result = diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index f6fa2e479..3dd9641b4 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -229,9 +229,6 @@ public abstract class CommandTestAbstract { @Mock protected Logger mockLogger; - @Mock(lenient = true) - protected BesuComponent mockBesuComponent; - @Captor protected ArgumentCaptor> bytesCollectionCollector; @Captor protected ArgumentCaptor pathArgumentCaptor; @Captor protected ArgumentCaptor stringArgumentCaptor; @@ -384,8 +381,6 @@ public abstract class CommandTestAbstract { lenient() .when(mockBesuPluginContext.getService(TransactionSelectionService.class)) .thenReturn(Optional.of(txSelectionService)); - - when(mockBesuComponent.getBesuCommandLogger()).thenReturn(mockLogger); } @BeforeEach @@ -470,7 +465,6 @@ public abstract class CommandTestAbstract { switch (testType) { case REQUIRED_OPTION: return new TestBesuCommandWithRequiredOption( - mockBesuComponent, () -> rlpBlockImporter, this::jsonBlockImporterFactory, (blockchain) -> rlpBlockExporter, @@ -480,10 +474,10 @@ public abstract class CommandTestAbstract { environment, storageService, securityModuleService, - privacyPluginService); + privacyPluginService, + mockLogger); case PORT_CHECK: return new TestBesuCommand( - mockBesuComponent, () -> rlpBlockImporter, this::jsonBlockImporterFactory, (blockchain) -> rlpBlockExporter, @@ -493,10 +487,10 @@ public abstract class CommandTestAbstract { environment, storageService, securityModuleService, - privacyPluginService); + privacyPluginService, + mockLogger); default: return new TestBesuCommandWithoutPortCheck( - mockBesuComponent, () -> rlpBlockImporter, this::jsonBlockImporterFactory, (blockchain) -> rlpBlockExporter, @@ -506,7 +500,8 @@ public abstract class CommandTestAbstract { environment, storageService, securityModuleService, - privacyPluginService); + privacyPluginService, + mockLogger); } } @@ -536,7 +531,6 @@ public abstract class CommandTestAbstract { private Vertx vertx; TestBesuCommand( - final BesuComponent besuComponent, final Supplier mockBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, @@ -546,9 +540,9 @@ public abstract class CommandTestAbstract { final Map environment, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, - final PrivacyPluginServiceImpl privacyPluginService) { + final PrivacyPluginServiceImpl privacyPluginService, + final Logger commandLogger) { super( - besuComponent, mockBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, @@ -564,7 +558,8 @@ public abstract class CommandTestAbstract { new TransactionSelectionServiceImpl(), new TransactionPoolValidatorServiceImpl(), new TransactionSimulationServiceImpl(), - new BlockchainServiceImpl()); + new BlockchainServiceImpl(), + commandLogger); } @Override @@ -635,7 +630,6 @@ public abstract class CommandTestAbstract { private final Boolean acceptTermsAndConditions = false; TestBesuCommandWithRequiredOption( - final BesuComponent besuComponent, final Supplier mockBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, @@ -645,9 +639,9 @@ public abstract class CommandTestAbstract { final Map environment, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, - final PrivacyPluginServiceImpl privacyPluginService) { + final PrivacyPluginServiceImpl privacyPluginService, + final Logger commandLogger) { super( - besuComponent, mockBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, @@ -657,7 +651,8 @@ public abstract class CommandTestAbstract { environment, storageService, securityModuleService, - privacyPluginService); + privacyPluginService, + commandLogger); } public Boolean getAcceptTermsAndConditions() { @@ -669,7 +664,6 @@ public abstract class CommandTestAbstract { public static class TestBesuCommandWithoutPortCheck extends TestBesuCommand { TestBesuCommandWithoutPortCheck( - final BesuComponent context, final Supplier mockBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, @@ -679,9 +673,9 @@ public abstract class CommandTestAbstract { final Map environment, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, - final PrivacyPluginServiceImpl privacyPluginService) { + final PrivacyPluginServiceImpl privacyPluginService, + final Logger commandLogger) { super( - context, mockBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, @@ -691,7 +685,8 @@ public abstract class CommandTestAbstract { environment, storageService, securityModuleService, - privacyPluginService); + privacyPluginService, + commandLogger); } @Override 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 4ee34295f..eb48a3f2a 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,11 +413,29 @@ public class TransactionPoolOptionsTest @Test public void maxPrioritizedTxsPerTypeWrongTxType() { internalTestFailure( - "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'", + "Invalid value for option '--tx-pool-max-prioritized-by-type' (MAP): expected one of [FRONTIER, ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE] (case-insensitive) but was 'WRONG_TYPE'", "--tx-pool-max-prioritized-by-type", "WRONG_TYPE=1"); } + @Test + public void minScoreWorks() { + final byte minScore = -10; + internalTestSuccess( + config -> assertThat(config.getMinScore()).isEqualTo(minScore), + "--tx-pool-min-score", + Byte.toString(minScore)); + } + + @Test + public void minScoreNonByteValueReturnError() { + final var overflowMinScore = Integer.toString(-300); + internalTestFailure( + "Invalid value for option '--tx-pool-min-score': '" + overflowMinScore + "' is not a byte", + "--tx-pool-min-score", + overflowMinScore); + } + @Override protected TransactionPoolConfiguration createDefaultDomainObject() { return TransactionPoolConfiguration.DEFAULT; diff --git a/besu/src/test/java/org/hyperledger/besu/components/EnclaveModule.java b/besu/src/test/java/org/hyperledger/besu/components/EnclaveModule.java new file mode 100644 index 000000000..20f9c1bf4 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/EnclaveModule.java @@ -0,0 +1,98 @@ +/* + * 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.components; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.enclave.Enclave; +import org.hyperledger.besu.enclave.EnclaveFactory; +import org.hyperledger.besu.enclave.types.ReceiveResponse; +import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.plugin.data.Restriction; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import dagger.Module; +import dagger.Provides; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +@Module +public class EnclaveModule { + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + private static final Bytes ENCLAVE_PUBLIC_KEY = + Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); + + private static final Bytes32 PRIVACY_GROUP_BYTES32 = + Bytes32.fromHexString("0xf250d523ae9164722b06ca25cfa2a7f3c45df96b09e215236f886c876f715bfa"); + + private static final Bytes MOCK_PAYLOAD = + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b5060008054600160a060020a03191633179055610199806100326000396000f3fe6080604052600436106100565763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633fa4f245811461005b5780636057361d1461008257806367e404ce146100ae575b600080fd5b34801561006757600080fd5b506100706100ec565b60408051918252519081900360200190f35b34801561008e57600080fd5b506100ac600480360360208110156100a557600080fd5b50356100f2565b005b3480156100ba57600080fd5b506100c3610151565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b60025490565b604080513381526020810183905281517fc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5929181900390910190a16002556001805473ffffffffffffffffffffffffffffffffffffffff191633179055565b60015473ffffffffffffffffffffffffffffffffffffffff169056fea165627a7a72305820c7f729cb24e05c221f5aa913700793994656f233fe2ce3b9fd9a505ea17e8d8a0029"); + + private static final KeyPair KEY_PAIR = + SIGNATURE_ALGORITHM + .get() + .createKeyPair( + SIGNATURE_ALGORITHM + .get() + .createPrivateKey( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); + + private static final PrivateTransaction PRIVATE_TRANSACTION = + PrivateTransaction.builder() + .chainId(BigInteger.valueOf(1337)) + .gasLimit(1000) + .gasPrice(Wei.ZERO) + .nonce(0) + .payload(MOCK_PAYLOAD) + .to(null) + .privateFrom(ENCLAVE_PUBLIC_KEY) + .privateFor(Collections.singletonList(ENCLAVE_PUBLIC_KEY)) + .restriction(Restriction.RESTRICTED) + .value(Wei.ZERO) + .signAndBuild(KEY_PAIR); + + @Provides + EnclaveFactory provideMockableEnclaveFactory() { + Enclave mockEnclave = mock(Enclave.class); + final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + PRIVATE_TRANSACTION.writeTo(rlpOutput); + when(mockEnclave.receive(any())) + .thenReturn( + new ReceiveResponse( + rlpOutput.encoded().toBase64String().getBytes(StandardCharsets.UTF_8), + PRIVACY_GROUP_BYTES32.toBase64String(), + ENCLAVE_PUBLIC_KEY.toBase64String())); + EnclaveFactory enclaveFactory = mock(EnclaveFactory.class); + when(enclaveFactory.createVertxEnclave(any())).thenReturn(mockEnclave); + return enclaveFactory; + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/GenesisConfigModule.java b/besu/src/test/java/org/hyperledger/besu/components/GenesisConfigModule.java new file mode 100644 index 000000000..ae82b8b92 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/GenesisConfigModule.java @@ -0,0 +1,38 @@ +/* + * 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.components; + +import org.hyperledger.besu.config.GenesisConfigFile; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +@Module +public class GenesisConfigModule { + + @Named("default") + @Provides + GenesisConfigFile provideDefaultGenesisConfigFile() { + return GenesisConfigFile.DEFAULT; + } + + @Named("mainnet") + @Provides + GenesisConfigFile provideMainnetGenesisConfigFile() { + return GenesisConfigFile.mainnet(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java b/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java new file mode 100644 index 000000000..743b4ee8d --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java @@ -0,0 +1,50 @@ +/* + * 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.components; + +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.cli.BesuCommand; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; + +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Module +public class MockBesuCommandModule { + + @Provides + BesuCommand provideBesuCommand() { + return mock(BesuCommand.class); + } + + @Provides + @Singleton + MetricsConfiguration provideMetricsConfiguration() { + return MetricsConfiguration.builder().build(); + } + + @Provides + @Named("besuCommandLogger") + @Singleton + Logger provideBesuCommandLogger() { + return LoggerFactory.getLogger(MockBesuCommandModule.class); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/NoOpMetricsSystemModule.java b/besu/src/test/java/org/hyperledger/besu/components/NoOpMetricsSystemModule.java new file mode 100644 index 000000000..e7807e3d7 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/NoOpMetricsSystemModule.java @@ -0,0 +1,40 @@ +/* + * 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.components; + +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class NoOpMetricsSystemModule { + + @Provides + @Singleton + MetricsSystem provideMetricsSystem() { + return new NoOpMetricsSystem(); + } + + @Provides + @Singleton + ObservableMetricsSystem provideObservableMetricsSystem() { + return new NoOpMetricsSystem(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/PrivacyParametersModule.java b/besu/src/test/java/org/hyperledger/besu/components/PrivacyParametersModule.java new file mode 100644 index 000000000..0b8fcf744 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/PrivacyParametersModule.java @@ -0,0 +1,47 @@ +/* + * 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.components; + +import org.hyperledger.besu.enclave.EnclaveFactory; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; + +import java.net.URI; +import java.net.URISyntaxException; + +import dagger.Module; +import dagger.Provides; +import io.vertx.core.Vertx; + +/** Provides a general use PrivacyParameters instance for testing. */ +@Module +public class PrivacyParametersModule { + + @Provides + PrivacyParameters providePrivacyParameters( + final PrivacyStorageProvider storageProvider, final Vertx vertx) { + try { + return new PrivacyParameters.Builder() + .setEnabled(true) + .setEnclaveUrl(new URI("http://127.0.0.1:8000")) + .setStorageProvider(storageProvider) + .setEnclaveFactory(new EnclaveFactory(vertx)) + .setFlexiblePrivacyGroupsEnabled(false) + .build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/PrivacyTestModule.java b/besu/src/test/java/org/hyperledger/besu/components/PrivacyTestModule.java new file mode 100644 index 000000000..13cafe1ab --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/PrivacyTestModule.java @@ -0,0 +1,111 @@ +/* + * 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.components; + +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_BACKGROUND_THREAD_COUNT; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; + +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; +import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValuePrivacyStorageFactory; +import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; +import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; +import org.hyperledger.besu.services.BesuConfigurationImpl; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import io.vertx.core.Vertx; + +@Module +public class PrivacyTestModule { + + @Provides + @Named("dataDir") + Path provideDataDir() { + try { + return Files.createTempDirectory("PrivacyTestDatadir"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Provides + Vertx provideVertx() { + return Vertx.vertx(); + } + + @Provides + DataStorageConfiguration provideDataStorageConfiguration() { + return DataStorageConfiguration.DEFAULT_FOREST_CONFIG; + } + + @Provides + @Singleton + @Named("dbDir") + Path provideDbDir(@Named("dataDir") final Path dataDir) { + try { + final Path dbDir = Files.createTempDirectory(dataDir, "database"); + return dbDir; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Provides + @Singleton + @Named("flexibleEnabled") + Boolean provideFlexibleEnabled() { + return true; + } + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + PrivacyStorageProvider provideKeyValueStorageProvider( + @Named("dbDir") final Path dbDir, + final DataStorageConfiguration dataStorageConfiguration, + @Named("dataDir") final Path dataDir) { + final var besuConfiguration = new BesuConfigurationImpl(); + besuConfiguration.init(dataDir, dbDir, dataStorageConfiguration); + return new PrivacyKeyValueStorageProviderBuilder() + .withStorageFactory( + new RocksDBKeyValuePrivacyStorageFactory( + new RocksDBKeyValueStorageFactory( + () -> + new RocksDBFactoryConfiguration( + DEFAULT_MAX_OPEN_FILES, + DEFAULT_BACKGROUND_THREAD_COUNT, + DEFAULT_CACHE_CAPACITY, + DEFAULT_IS_HIGH_SPEC), + Arrays.asList(KeyValueSegmentIdentifier.values()), + RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS))) + .withCommonConfiguration(besuConfiguration) + .withMetricsSystem(new NoOpMetricsSystem()) + .build(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java index f9e671f52..621ae8a98 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java @@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.CheckpointConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -156,6 +157,7 @@ public abstract class AbstractBftBesuControllerBuilderTest { .storageProvider(storageProvider) .gasLimitCalculator(gasLimitCalculator) .evmConfiguration(EvmConfiguration.DEFAULT) + .besuComponent(mock(BesuComponent.class)) .networkConfiguration(NetworkingConfiguration.create()); } diff --git a/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java index 39904df79..e9e93de9b 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.CheckpointConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -189,6 +190,7 @@ public class CliqueBesuControllerBuilderTest { .storageProvider(storageProvider) .gasLimitCalculator(gasLimitCalculator) .evmConfiguration(EvmConfiguration.DEFAULT) + .besuComponent(mock(BesuComponent.class)) .networkConfiguration(NetworkingConfiguration.create()); } diff --git a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java index 544b63301..f8c00b20e 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.CheckpointConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -189,6 +190,7 @@ public class MergeBesuControllerBuilderTest { .storageProvider(storageProvider) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .networkId(networkId); } diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index b489df9fa..e3d7a3f28 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -195,6 +195,7 @@ tx-pool-retention-hours=999 tx-pool-max-size=1234 tx-pool-limit-by-account-percentage=0.017 tx-pool-min-gas-price=1000 +tx-pool-min-score=100 # Revert Reason revert-reason-enabled=false diff --git a/build.gradle b/build.gradle index 6e2de8bf0..9a2b95c03 100644 --- a/build.gradle +++ b/build.gradle @@ -25,10 +25,11 @@ import java.util.regex.Pattern plugins { id 'com.diffplug.spotless' version '6.25.0' id 'com.github.ben-manes.versions' version '0.51.0' - id 'com.github.jk1.dependency-license-report' version '2.7' - id 'io.spring.dependency-management' version '1.1.5' + id 'com.github.jk1.dependency-license-report' version '2.9' + id 'com.jfrog.artifactory' version '5.2.5' + id 'io.spring.dependency-management' version '1.1.6' id 'me.champeau.jmh' version '0.7.2' apply false - id 'net.ltgt.errorprone' version '3.1.0' + id 'net.ltgt.errorprone' version '4.0.1' id 'maven-publish' } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java index ec10630df..9f2d848d4 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java @@ -55,6 +55,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -245,7 +246,8 @@ public class CliqueBlockCreatorTest { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - conf); + conf, + new BlobCache()); transactionPool.setEnabled(); return transactionPool; } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java index 9502d00a1..1aa2d75ef 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java @@ -45,6 +45,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -233,7 +234,8 @@ public class CliqueMinerExecutorTest { mock(TransactionBroadcaster.class), cliqueEthContext, new TransactionPoolMetrics(metricsSystem), - conf); + conf, + new BlobCache()); transactionPool.setEnabled(); return transactionPool; diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java index 2d620c563..5d2b02b1a 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java @@ -84,6 +84,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -371,7 +372,8 @@ public class TestContextBuilder { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java index 1b86896f3..15844578c 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java @@ -47,6 +47,7 @@ import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitV import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -152,7 +153,8 @@ public class BftBlockCreatorTest { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java index 6ce8bcd8d..a3dc6b6e0 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java @@ -63,6 +63,7 @@ import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; @@ -214,7 +215,8 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - poolConf); + poolConf, + new BlobCache()); this.transactionPool.setEnabled(); diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index 3467dce9f..8906f0de7 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -98,6 +98,7 @@ import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -480,7 +481,8 @@ public class TestContextBuilder { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java index 96f3ca6a9..5524faac3 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java @@ -54,7 +54,7 @@ public class SECPSignature { * @param s the s * @param recId the rec id */ - SECPSignature(final BigInteger r, final BigInteger s, final byte recId) { + public SECPSignature(final BigInteger r, final BigInteger s, final byte recId) { this.r = r; this.s = s; this.recId = recId; diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java similarity index 82% rename from datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java rename to datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java index b12e65de4..7b9e3d7d4 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/SetCodeAuthorization.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java @@ -20,10 +20,13 @@ import java.math.BigInteger; import java.util.Optional; /** - * SetCodeAuthorization is a data structure that represents the authorization to set code on a EOA - * account. + * CodeDelegation is a data structure that represents the authorization to delegate code of an EOA + * account to another account. */ -public interface SetCodeAuthorization { +public interface CodeDelegation { + /** The cost of delegating code on an existing account. */ + long PER_AUTH_BASE_COST = 2_500L; + /** * Return the chain id. * @@ -53,11 +56,11 @@ public interface SetCodeAuthorization { Optional
authorizer(); /** - * Return a valid nonce or empty otherwise. A nonce is valid if the size of the list is exactly 1 + * Return the nonce * - * @return all the optional nonce + * @return the nonce */ - Optional nonce(); + long nonce(); /** * Return the recovery id. 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 d6751852b..e61490130 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java @@ -236,16 +236,16 @@ public interface Transaction { int getSize(); /** - * Returns the set code transaction payload if this transaction is a 7702 transaction. + * Returns the code delegations if this transaction is a 7702 transaction. * - * @return the set code transaction payloads + * @return the code delegations */ - Optional> getAuthorizationList(); + Optional> getCodeDelegationList(); /** * Returns the size of the authorization list. * * @return the size of the authorization list */ - int authorizationListSize(); + int codeDelegationListSize(); } 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 df4a07193..bf1d4e779 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java @@ -29,10 +29,10 @@ public enum TransactionType { /** Blob transaction type. */ BLOB(0x03), /** Eip7702 transaction type. */ - SET_CODE(0x04); + DELEGATE_CODE(0x04); private static final Set ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES = - Set.of(ACCESS_LIST, EIP1559, BLOB, SET_CODE); + Set.of(ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE); private static final EnumSet LEGACY_FEE_MARKET_TRANSACTION_TYPES = EnumSet.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST); @@ -86,7 +86,7 @@ public enum TransactionType { TransactionType.ACCESS_LIST, TransactionType.EIP1559, TransactionType.BLOB, - TransactionType.SET_CODE + TransactionType.DELEGATE_CODE }) .filter(transactionType -> transactionType.typeValue == serializedTypeValue) .findFirst() @@ -132,12 +132,21 @@ public enum TransactionType { return this.equals(BLOB); } + /** + * Does transaction type support delegate code. + * + * @return the boolean + */ + public boolean supportsDelegateCode() { + return this.equals(DELEGATE_CODE); + } + /** * Does transaction type require code. * * @return the boolean */ - public boolean requiresSetCode() { - return this.equals(SET_CODE); + public boolean requiresCodeDelegation() { + return this.equals(DELEGATE_CODE); } } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java index 49b9659fd..6a1bbea26 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java @@ -236,7 +236,12 @@ public class EthCreateAccessListIntegrationTest { new JsonRpcSuccessResponse(null, new CreateAccessListResult(accessList, gasUsed)); final JsonRpcResponse response = method.response(request); - assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(response) + .usingRecursiveComparison() + // customize the comparison for the type that lazy compute the hashCode + .withEqualsForType(UInt256::equals, UInt256.class) + .withEqualsForType(Address::equals, Address.class) + .isEqualTo(expectedResponse); } private JsonRpcRequestContext requestWithParams(final Object... params) { diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java index 16a25d350..d4de40530 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java @@ -50,6 +50,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; @@ -121,7 +122,8 @@ public class EthGetFilterChangesIntegrationTest { batchAddedListener, ethContext, new TransactionPoolMetrics(metricsSystem), - TransactionPoolConfiguration.DEFAULT); + TransactionPoolConfiguration.DEFAULT, + new BlobCache()); transactionPool.setEnabled(); final BlockchainQueries blockchainQueries = new BlockchainQueries( diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java index e88d377f7..0c194ad22 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java @@ -50,6 +50,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; @@ -121,7 +122,8 @@ public class EthGetFilterChangesIntegrationTest { batchAddedListener, ethContext, new TransactionPoolMetrics(metricsSystem), - TransactionPoolConfiguration.DEFAULT); + TransactionPoolConfiguration.DEFAULT, + new BlobCache()); transactionPool.setEnabled(); final BlockchainQueries blockchainQueries = new BlockchainQueries( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index 79d33ffba..f8bc597ff 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -51,6 +51,7 @@ public enum RpcMethod { DEBUG_GET_RAW_BLOCK("debug_getRawBlock"), DEBUG_GET_RAW_RECEIPTS("debug_getRawReceipts"), DEBUG_GET_RAW_TRANSACTION("debug_getRawTransaction"), + ENGINE_GET_BLOBS_V1("engine_getBlobsV1"), ENGINE_GET_PAYLOAD_V1("engine_getPayloadV1"), ENGINE_GET_PAYLOAD_V2("engine_getPayloadV2"), ENGINE_GET_PAYLOAD_V3("engine_getPayloadV3"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java index ef8ca5b31..4cbfb818d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java @@ -14,15 +14,19 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; + import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -34,32 +38,68 @@ import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer; import java.util.Optional; -public abstract class AbstractEstimateGas implements JsonRpcMethod { +public abstract class AbstractEstimateGas extends AbstractBlockParameterMethod { private static final double SUB_CALL_REMAINING_GAS_RATIO = 65D / 64D; - protected final BlockchainQueries blockchainQueries; protected final TransactionSimulator transactionSimulator; public AbstractEstimateGas( final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) { - this.blockchainQueries = blockchainQueries; + super(blockchainQueries); this.transactionSimulator = transactionSimulator; } - protected BlockHeader blockHeader() { - final Blockchain theChain = blockchainQueries.getBlockchain(); - - // Optimistically get the block header for the chain head without taking a lock, - // but revert to the safe implementation if it returns an empty optional. (It's - // possible the chain head has been updated but the block is still being persisted - // to storage/cache under the lock). - return theChain - .getBlockHeader(theChain.getChainHeadHash()) - .or(() -> theChain.getBlockHeaderSafe(theChain.getChainHeadHash())) - .orElse(null); + @Override + protected BlockParameter blockParameter(final JsonRpcRequestContext request) { + try { + return request.getOptionalParameter(1, BlockParameter.class).orElse(BlockParameter.LATEST); + } catch (JsonRpcParameter.JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } + protected Optional blockHeader(final long blockNumber) { + if (getBlockchainQueries().headBlockNumber() == blockNumber) { + // chain head header if cached, and we can return it form memory + return Optional.of(getBlockchainQueries().getBlockchain().getChainHeadHeader()); + } + return getBlockchainQueries().getBlockHeaderByNumber(blockNumber); + } + + protected Optional validateBlockHeader( + final Optional maybeBlockHeader) { + if (maybeBlockHeader.isEmpty()) { + return Optional.of(RpcErrorType.BLOCK_NOT_FOUND); + } + + final var blockHeader = maybeBlockHeader.get(); + if (!getBlockchainQueries() + .getWorldStateArchive() + .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { + return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE); + } + return Optional.empty(); + } + + @Override + protected Object resultByBlockNumber( + final JsonRpcRequestContext requestContext, final long blockNumber) { + final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); + final Optional maybeBlockHeader = blockHeader(blockNumber); + final Optional jsonRpcError = validateBlockHeader(maybeBlockHeader); + if (jsonRpcError.isPresent()) { + return errorResponse(requestContext, jsonRpcError.get()); + } + return resultByBlockHeader(requestContext, jsonCallParameter, maybeBlockHeader.get()); + } + + protected abstract Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter jsonCallParameter, + final BlockHeader blockHeader); + protected CallParameter overrideGasLimitAndPrice( final JsonCallParameter callParams, final long gasLimit) { return new CallParameter( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java index 2be5047a2..2b8e79a81 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java @@ -14,15 +14,11 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; - import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CreateAccessListResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -52,44 +48,29 @@ public class EthCreateAccessList extends AbstractEstimateGas { } @Override - public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); - final BlockHeader blockHeader = blockHeader(); - final Optional jsonRpcError = validateBlockHeader(blockHeader); - if (jsonRpcError.isPresent()) { - return errorResponse(requestContext, jsonRpcError.get()); - } + protected Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter jsonCallParameter, + final BlockHeader blockHeader) { final AccessListSimulatorResult maybeResult = processTransaction(jsonCallParameter, blockHeader); // if the call accessList is different from the simulation result, calculate gas and return - if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.getTracer())) { + if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.tracer())) { final AccessListSimulatorResult result = processTransactionWithAccessListOverride( - jsonCallParameter, blockHeader, maybeResult.getTracer().getAccessList()); + jsonCallParameter, blockHeader, maybeResult.tracer().getAccessList()); return createResponse(requestContext, result); } else { return createResponse(requestContext, maybeResult); } } - private Optional validateBlockHeader(final BlockHeader blockHeader) { - if (blockHeader == null) { - return Optional.of(RpcErrorType.INTERNAL_ERROR); - } - if (!blockchainQueries - .getWorldStateArchive() - .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { - return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE); - } - return Optional.empty(); - } - - private JsonRpcResponse createResponse( + private Object createResponse( final JsonRpcRequestContext requestContext, final AccessListSimulatorResult result) { return result - .getResult() - .map(createResponse(requestContext, result.getTracer())) - .orElse(errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); + .result() + .map(createResponse(requestContext, result.tracer())) + .orElseGet(() -> errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); } private TransactionValidationParams transactionValidationParams( @@ -117,14 +98,12 @@ public class EthCreateAccessList extends AbstractEstimateGas { return !Objects.equals(tracer.getAccessList(), parameters.getAccessList().get()); } - private Function createResponse( + private Function createResponse( final JsonRpcRequestContext request, final AccessListOperationTracer operationTracer) { return result -> result.isSuccessful() - ? new JsonRpcSuccessResponse( - request.getRequest().getId(), - new CreateAccessListResult( - operationTracer.getAccessList(), processEstimateGas(result, operationTracer))) + ? new CreateAccessListResult( + operationTracer.getAccessList(), processEstimateGas(result, operationTracer)) : errorResponse(request, result); } @@ -138,8 +117,7 @@ public class EthCreateAccessList extends AbstractEstimateGas { final AccessListOperationTracer tracer = AccessListOperationTracer.create(); final Optional result = - transactionSimulator.process( - callParams, transactionValidationParams, tracer, blockHeader.getNumber()); + transactionSimulator.process(callParams, transactionValidationParams, tracer, blockHeader); return new AccessListSimulatorResult(result, tracer); } @@ -156,7 +134,7 @@ public class EthCreateAccessList extends AbstractEstimateGas { final Optional result = transactionSimulator.process( - callParameter, transactionValidationParams, tracer, blockHeader.getNumber()); + callParameter, transactionValidationParams, tracer, blockHeader); return new AccessListSimulatorResult(result, tracer); } @@ -176,22 +154,6 @@ public class EthCreateAccessList extends AbstractEstimateGas { Optional.ofNullable(accessListEntries)); } - private static class AccessListSimulatorResult { - final Optional result; - final AccessListOperationTracer tracer; - - public AccessListSimulatorResult( - final Optional result, final AccessListOperationTracer tracer) { - this.result = result; - this.tracer = tracer; - } - - public Optional getResult() { - return result; - } - - public AccessListOperationTracer getTracer() { - return tracer; - } - } + private record AccessListSimulatorResult( + Optional result, AccessListOperationTracer tracer) {} } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java index ca084ba0f..c713ea20a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 @@ -14,13 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; - import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -38,7 +35,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class EthEstimateGas extends AbstractEstimateGas { - private static final Logger LOG = LoggerFactory.getLogger(EthEstimateGas.class); public EthEstimateGas( @@ -52,19 +48,10 @@ public class EthEstimateGas extends AbstractEstimateGas { } @Override - public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final JsonCallParameter callParams = validateAndGetCallParams(requestContext); - - final BlockHeader blockHeader = blockHeader(); - if (blockHeader == null) { - LOG.error("Chain head block not found"); - return errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR); - } - if (!blockchainQueries - .getWorldStateArchive() - .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { - return errorResponse(requestContext, RpcErrorType.WORLD_STATE_UNAVAILABLE); - } + protected Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter callParams, + final BlockHeader blockHeader) { final CallParameter modifiedCallParams = overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit()); @@ -72,44 +59,48 @@ public class EthEstimateGas extends AbstractEstimateGas { final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); final EstimateGasOperationTracer operationTracer = new EstimateGasOperationTracer(); + final var transactionValidationParams = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(isAllowExceedingBalance) + .build(); - var gasUsed = - executeSimulation( - blockHeader, modifiedCallParams, operationTracer, isAllowExceedingBalance); + LOG.debug("Processing transaction with params: {}", modifiedCallParams); + final var maybeResult = + transactionSimulator.process( + modifiedCallParams, transactionValidationParams, operationTracer, blockHeader); - if (gasUsed.isEmpty()) { - LOG.error("gasUsed is empty after simulating transaction."); - return errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR); + final Optional maybeErrorResponse = + validateSimulationResult(requestContext, maybeResult); + if (maybeErrorResponse.isPresent()) { + return maybeErrorResponse.get(); } - // if the transaction is invalid or doesn't have enough gas with the max it never will! - if (gasUsed.get().isInvalid() || !gasUsed.get().isSuccessful()) { - return errorResponse(requestContext, gasUsed.get()); - } - - var low = gasUsed.get().result().getEstimateGasUsedByTransaction(); - var lowResult = - executeSimulation( - blockHeader, + final var result = maybeResult.get(); + long low = result.result().getEstimateGasUsedByTransaction(); + final var lowResult = + transactionSimulator.process( overrideGasLimitAndPrice(callParams, low), + transactionValidationParams, operationTracer, - isAllowExceedingBalance); + blockHeader); if (lowResult.isPresent() && lowResult.get().isSuccessful()) { - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(low)); + return Quantity.create(low); } - var high = processEstimateGas(gasUsed.get(), operationTracer); - var mid = high; - while (low + 1 < high) { - mid = (high + low) / 2; + long high = processEstimateGas(result, operationTracer); + long mid; + while (low + 1 < high) { + mid = (low + high) / 2; var binarySearchResult = - executeSimulation( - blockHeader, + transactionSimulator.process( overrideGasLimitAndPrice(callParams, mid), + transactionValidationParams, operationTracer, - isAllowExceedingBalance); + blockHeader); + if (binarySearchResult.isEmpty() || !binarySearchResult.get().isSuccessful()) { low = mid; } else { @@ -117,21 +108,23 @@ public class EthEstimateGas extends AbstractEstimateGas { } } - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(high)); + return Quantity.create(high); } - private Optional executeSimulation( - final BlockHeader blockHeader, - final CallParameter modifiedCallParams, - final EstimateGasOperationTracer operationTracer, - final boolean allowExceedingBalance) { - return transactionSimulator.process( - modifiedCallParams, - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(allowExceedingBalance) - .build(), - operationTracer, - blockHeader.getNumber()); + private Optional validateSimulationResult( + final JsonRpcRequestContext requestContext, + final Optional maybeResult) { + if (maybeResult.isEmpty()) { + LOG.error("No result after simulating transaction."); + return Optional.of( + new JsonRpcErrorResponse( + requestContext.getRequest().getId(), RpcErrorType.INTERNAL_ERROR)); + } + + // if the transaction is invalid or doesn't have enough gas with the max it never will! + if (maybeResult.get().isInvalid() || !maybeResult.get().isSuccessful()) { + return Optional.of(errorResponse(requestContext, maybeResult.get())); + } + return Optional.empty(); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1.java new file mode 100644 index 000000000..cc9ba2dce --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1.java @@ -0,0 +1,115 @@ +/* + * 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.api.jsonrpc.internal.methods.engine; + +import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.VersionedHash; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlobAndProofV1; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; + +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.vertx.core.Vertx; + +/** + * #### Specification + * + *

1. Given an array of blob versioned hashes client software **MUST** respond with an array of + * `BlobAndProofV1` objects with matching versioned hashes, respecting the order of versioned hashes + * in the input array. + * + *

2. Client software **MUST** place responses in the order given in the request, using `null` + * for any missing blobs. For instance, if the request is `[A_versioned_hash, B_versioned_hash, + * C_versioned_hash]` and client software has data for blobs `A` and `C`, but doesn't have data for + * `B`, the response **MUST** be `[A, null, C]`. + * + *

3. Client software **MUST** support request sizes of at least 128 blob versioned hashes. The + * client **MUST** return `-38004: Too large request` error if the number of requested blobs is too + * large. + * + *

4. Client software **MAY** return an array of all `null` entries if syncing or otherwise + * unable to serve blob pool data. + * + *

5. Callers **MUST** consider that execution layer clients may prune old blobs from their pool, + * and will respond with `null` if a blob has been pruned. + */ +public class EngineGetBlobsV1 extends ExecutionEngineJsonRpcMethod { + + private final TransactionPool transactionPool; + + public EngineGetBlobsV1( + final Vertx vertx, + final ProtocolContext protocolContext, + final EngineCallListener engineCallListener, + final TransactionPool transactionPool) { + super(vertx, protocolContext, engineCallListener); + this.transactionPool = transactionPool; + } + + @Override + public String getName() { + return "engine_getBlobsV1"; + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { + final VersionedHash[] versionedHashes; + try { + versionedHashes = requestContext.getRequiredParameter(0, VersionedHash[].class); + } catch (JsonRpcParameter.JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid versioned hashes parameter (index 0)", + RpcErrorType.INVALID_VERSIONED_HASHES_PARAMS, + e); + } + + if (versionedHashes.length > 128) { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), + RpcErrorType.INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST); + } + + final List result = getBlobV1Result(versionedHashes); + + return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result); + } + + private @Nonnull List getBlobV1Result(final VersionedHash[] versionedHashes) { + return Arrays.stream(versionedHashes) + .map(transactionPool::getBlobQuad) + .map(this::getBlobAndProofV1) + .toList(); + } + + private @Nullable BlobAndProofV1 getBlobAndProofV1(final BlobsWithCommitments.BlobQuad bq) { + if (bq == null) { + return null; + } + return new BlobAndProofV1( + bq.blob().getData().toHexString(), bq.kzgProof().getData().toHexString()); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ConsolidationRequestParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ConsolidationRequestParameter.java index d33b12c6a..c6c600329 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ConsolidationRequestParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ConsolidationRequestParameter.java @@ -61,12 +61,12 @@ public class ConsolidationRequestParameter { } @JsonGetter - public String getSourcePubKey() { + public String getSourcePubkey() { return sourcePubkey; } @JsonGetter - public String getTargetPubKey() { + public String getTargetPubkey() { return targetPubkey; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java index 009fca38f..875eab601 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java @@ -58,6 +58,7 @@ public enum RpcErrorType implements RpcMethodError { INVALID_ENGINE_NEW_PAYLOAD_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid engine payload parameter"), INVALID_ENGINE_PREPARE_PAYLOAD_PARAMS( INVALID_PARAMS_ERROR_CODE, "Invalid engine prepare payload parameter"), + INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST(-38004, "Too large request"), INVALID_ENODE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid enode params"), INVALID_EXCESS_BLOB_GAS_PARAMS( INVALID_PARAMS_ERROR_CODE, "Invalid excess blob gas params (missing or invalid)"), @@ -109,6 +110,7 @@ public enum RpcErrorType implements RpcMethodError { INVALID_TRANSACTION_LIMIT_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid transaction limit params"), INVALID_TRANSACTION_TRACE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid transaction trace params"), INVALID_VERSIONED_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid versioned hash params"), + INVALID_VERSIONED_HASHES_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid versioned hashes params"), INVALID_VOTE_TYPE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid vote type params"), INVALID_WITHDRAWALS_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid withdrawals"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobAndProofV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobAndProofV1.java new file mode 100644 index 000000000..c8978e00c --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobAndProofV1.java @@ -0,0 +1,42 @@ +/* + * 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.api.jsonrpc.internal.results; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +/** + * The result of the eth_getBlobAndProofV1 JSON-RPC method contains an array of BlobAndProofV1. + * BlobAndProofV1 contains the blob data and the kzg proof for the blob. + */ +@JsonPropertyOrder({"blob", "proof"}) +public class BlobAndProofV1 { + + private final String blob; + + private final String proof; + + public BlobAndProofV1(final String blob, final String proof) { + this.blob = blob; + this.proof = proof; + } + + public String getProof() { + return proof; + } + + public String getBlob() { + return blob; + } +} 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 05b323cac..e8262021f 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,7 +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.CodeDelegation; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; @@ -94,7 +94,7 @@ public class TransactionCompleteResult implements TransactionResult { private final List versionedHashes; @JsonInclude(JsonInclude.Include.NON_NULL) - private final List authorizationList; + private final List authorizationList; public TransactionCompleteResult(final TransactionWithMetadata tx) { final Transaction transaction = tx.getTransaction(); @@ -131,7 +131,7 @@ public class TransactionCompleteResult implements TransactionResult { this.v = (transactionType == TransactionType.ACCESS_LIST || transactionType == TransactionType.EIP1559) - || transactionType == TransactionType.SET_CODE + || transactionType == TransactionType.DELEGATE_CODE ? Quantity.create(transaction.getYParity()) : null; } @@ -139,7 +139,7 @@ public class TransactionCompleteResult implements TransactionResult { this.r = Quantity.create(transaction.getR()); this.s = Quantity.create(transaction.getS()); this.versionedHashes = transaction.getVersionedHashes().orElse(null); - this.authorizationList = transaction.getAuthorizationList().orElse(null); + this.authorizationList = transaction.getCodeDelegationList().orElse(null); } @JsonGetter(value = "accessList") @@ -255,7 +255,7 @@ public class TransactionCompleteResult implements TransactionResult { } @JsonGetter(value = "authorizationList") - public List getAuthorizationList() { + public List getAuthorizationList() { return authorizationList; } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java index 6dda09bce..d8d04027f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineE import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV3; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetBlobsV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetClientVersionV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByHashV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByRangeV1; @@ -39,6 +40,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineQ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.ArrayList; @@ -60,6 +62,7 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { private final Vertx consensusEngineServer; private final String clientVersion; private final String commit; + private final TransactionPool transactionPool; ExecutionEngineJsonRpcMethods( final MiningCoordinator miningCoordinator, @@ -68,7 +71,8 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { final EthPeers ethPeers, final Vertx consensusEngineServer, final String clientVersion, - final String commit) { + final String commit, + final TransactionPool transactionPool) { this.mergeCoordinator = Optional.ofNullable(miningCoordinator) .filter(mc -> mc.isCompatibleWithEngineApi()) @@ -79,6 +83,7 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { this.consensusEngineServer = consensusEngineServer; this.clientVersion = clientVersion; this.commit = commit; + this.transactionPool = transactionPool; } @Override @@ -156,7 +161,9 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { new EnginePreparePayloadDebug( consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get()), new EngineGetClientVersionV1( - consensusEngineServer, protocolContext, engineQosTimer, clientVersion, commit))); + consensusEngineServer, protocolContext, engineQosTimer, clientVersion, commit), + new EngineGetBlobsV1( + consensusEngineServer, protocolContext, engineQosTimer, transactionPool))); if (protocolSchedule.anyMatch(p -> p.spec().getName().equalsIgnoreCase("cancun"))) { executionEngineApisSupported.add( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index 41227c2ca..55d9ef602 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -119,7 +119,8 @@ public class JsonRpcMethodsFactory { ethPeers, consensusEngineServer, clientVersion, - commit), + commit, + transactionPool), new EthJsonRpcMethods( blockchainQueries, synchronizer, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java index 8812a1e0a..f7c0c3191 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java @@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -24,7 +24,6 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -70,7 +69,9 @@ public class EthCreateAccessListTest { private final String METHOD = "eth_createAccessList"; private EthCreateAccessList method; - @Mock private BlockHeader blockHeader; + @Mock private BlockHeader latestBlockHeader; + @Mock private BlockHeader finalizedBlockHeader; + @Mock private BlockHeader genesisBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -80,16 +81,18 @@ public class EthCreateAccessListTest { public void setUp() { when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); - when(blockchain.getChainHeadHash()) - .thenReturn( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3")); - when(blockchain.getBlockHeader( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3"))) - .thenReturn(Optional.of(blockHeader)); - when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); - when(blockHeader.getNumber()).thenReturn(1L); + when(blockchainQueries.headBlockNumber()).thenReturn(2L); + when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader)); + when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader)); + when(blockchainQueries.getBlockHeaderByNumber(1L)) + .thenReturn(Optional.of(finalizedBlockHeader)); + when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(genesisBlockHeader.getNumber()).thenReturn(0L); + when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(finalizedBlockHeader.getNumber()).thenReturn(1L); + when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); + when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(latestBlockHeader.getNumber()).thenReturn(2L); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthCreateAccessList(blockchainQueries, transactionSimulator); @@ -105,18 +108,22 @@ public class EthCreateAccessListTest { new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter})); } + private JsonRpcRequestContext ethCreateAccessListRequest( + final CallParameter callParameter, final String blockParam) { + return new JsonRpcRequestContext( + new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter, blockParam})); + } + @Test public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -124,21 +131,19 @@ public class EthCreateAccessListTest { final Wei gasPrice = Wei.of(1000); final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(gasPrice)); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { final JsonRpcRequestContext request = ethCreateAccessListRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); - mockTransactionSimulatorResult(false, false, 1L); + mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader); Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) @@ -150,29 +155,25 @@ public class EthCreateAccessListTest { when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(false, false, 1L); + mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(false, true, 1L); + mockTransactionSimulatorResult(false, true, 1L, latestBlockHeader); final String errorReason = "0x00"; final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, errorReason)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -182,12 +183,10 @@ public class EthCreateAccessListTest { new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); final JsonRpcRequestContext request = ethCreateAccessListRequest(eip1559TransactionCallParameter()); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -204,11 +203,11 @@ public class EthCreateAccessListTest { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -223,11 +222,9 @@ public class EthCreateAccessListTest { ethCreateAccessListRequest(eip1559TransactionCallParameter(accessListParam)); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -244,11 +241,11 @@ public class EthCreateAccessListTest { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -268,11 +265,51 @@ public class EthCreateAccessListTest { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); + } + + @Test + public void shouldReturnAccessListWhenBlockTagParamIsPresent() { + final JsonRpcRequestContext request = + ethCreateAccessListRequest(eip1559TransactionCallParameter(), "finalized"); + // Create a list with one access list entry + final List expectedAccessList = createAccessList(); + + // expect a list with the mocked access list + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); + final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); + + // Set TransactionSimulator.process response + mockTransactionSimulatorResult(true, false, 1L, finalizedBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) + .usingRecursiveComparison() + .isEqualTo(expectedResponse); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(finalizedBlockHeader)); + } + + @Test + public void shouldReturnAccessListWhenBlockNumberParamIsPresent() { + final JsonRpcRequestContext request = + ethCreateAccessListRequest(eip1559TransactionCallParameter(), "0x0"); + // Create a list with one access list entry + final List expectedAccessList = createAccessList(); + + // expect a list with the mocked access list + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); + final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); + + // Set TransactionSimulator.process response + mockTransactionSimulatorResult(true, false, 1L, genesisBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) + .usingRecursiveComparison() + .isEqualTo(expectedResponse); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(genesisBlockHeader)); } private JsonRpcResponse responseWithMockTracer( @@ -292,9 +329,12 @@ public class EthCreateAccessListTest { } private void mockTransactionSimulatorResult( - final boolean isSuccessful, final boolean isReverted, final long estimateGas) { + final boolean isSuccessful, + final boolean isReverted, + final long estimateGas, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); - when(transactionSimulator.process(any(), any(), any(), anyLong())) + when(transactionSimulator.process(any(), any(), any(), eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java index a6f5008b2..f85a0813e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -66,7 +65,9 @@ public class EthEstimateGasTest { private EthEstimateGas method; - @Mock private BlockHeader blockHeader; + @Mock private BlockHeader latestBlockHeader; + @Mock private BlockHeader finalizedBlockHeader; + @Mock private BlockHeader genesisBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -76,16 +77,18 @@ public class EthEstimateGasTest { public void setUp() { when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); - when(blockchain.getChainHeadHash()) - .thenReturn( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3")); - when(blockchain.getBlockHeader( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3"))) - .thenReturn(Optional.of(blockHeader)); - when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); - when(blockHeader.getNumber()).thenReturn(1L); + when(blockchainQueries.headBlockNumber()).thenReturn(2L); + when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader)); + when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader)); + when(blockchainQueries.getBlockHeaderByNumber(1L)) + .thenReturn(Optional.of(finalizedBlockHeader)); + when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(genesisBlockHeader.getNumber()).thenReturn(0L); + when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(finalizedBlockHeader.getNumber()).thenReturn(1L); + when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); + when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(latestBlockHeader.getNumber()).thenReturn(2L); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthEstimateGas(blockchainQueries, transactionSimulator); @@ -104,15 +107,13 @@ public class EthEstimateGasTest { eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(latestBlockHeader))) .thenReturn(Optional.empty()); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -122,28 +123,24 @@ public class EthEstimateGasTest { eq(modifiedEip1559TransactionCallParameter()), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(latestBlockHeader))) .thenReturn(Optional.empty()); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, true, false); + mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -151,20 +148,19 @@ public class EthEstimateGasTest { final Wei gasPrice = Wei.of(1000); final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(gasPrice)); - mockTransientProcessorResultGasEstimate(1L, true, gasPrice, Optional.empty()); + mockTransientProcessorResultGasEstimate( + 1L, true, gasPrice, Optional.empty(), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) .hasMessageContaining("gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas"); @@ -174,12 +170,10 @@ public class EthEstimateGasTest { public void shouldReturnGasEstimateWhenTransientEip1559TransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); - mockTransientProcessorResultGasEstimate(1L, true, false); + mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -187,28 +181,24 @@ public class EthEstimateGasTest { shouldReturnGasEstimateErrorWhenTransientLegacyTransactionProcessorReturnsResultFailure() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenTransientEip1559TransactionProcessorReturnsResultFailure() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -217,7 +207,8 @@ public class EthEstimateGasTest { ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); mockTransientProcessorResultTxInvalidReason( TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, - "transaction up-front cost 10 exceeds transaction sender account balance 5"); + "transaction up-front cost 10 exceeds transaction sender account balance 5", + latestBlockHeader); final ValidationResult validationResult = ValidationResult.invalid( @@ -226,9 +217,7 @@ public class EthEstimateGasTest { final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -236,7 +225,8 @@ public class EthEstimateGasTest { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); mockTransientProcessorResultTxInvalidReason( TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, - "transaction up-front cost 10 exceeds transaction sender account balance 5"); + "transaction up-front cost 10 exceeds transaction sender account balance 5", + latestBlockHeader); final ValidationResult validationResult = ValidationResult.invalid( TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, @@ -244,9 +234,7 @@ public class EthEstimateGasTest { final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -254,21 +242,21 @@ public class EthEstimateGasTest { when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); JsonRpcResponse theResponse = method.response(request); - Assertions.assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, "0x00")); @@ -278,7 +266,7 @@ public class EthEstimateGasTest { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted"); @@ -296,7 +284,8 @@ public class EthEstimateGasTest { + "00002545524332303a207472616e736665722066726f6d20746865207a65726f20" + "61646472657373000000000000000000000000000000000000000000000000000000"; - mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(executionRevertedReason)); + mockTransientProcessorTxReverted( + 1L, false, Bytes.fromHexString(executionRevertedReason), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( @@ -307,7 +296,7 @@ public class EthEstimateGasTest { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted: ERC20: transfer from the zero address"); @@ -323,7 +312,8 @@ public class EthEstimateGasTest { "0x08c379a000000000000000000000000000000000000000000000000000000000" + "123451234512345123451234512345123451234512345123451234512345123451"; - mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(invalidRevertReason)); + mockTransientProcessorTxReverted( + 1L, false, Bytes.fromHexString(invalidRevertReason), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( @@ -334,7 +324,7 @@ public class EthEstimateGasTest { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted: ABI decode error"); @@ -344,7 +334,7 @@ public class EthEstimateGasTest { public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); method.response(request); @@ -357,14 +347,14 @@ public class EthEstimateGasTest { .isAllowExceedingBalance(true) .build()), any(OperationTracer.class), - eq(1L)); + eq(latestBlockHeader)); } @Test public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeEnabled() { final JsonRpcRequestContext request = ethEstimateGasRequest(legacyTransactionCallParameter(Wei.ZERO, true)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); method.response(request); @@ -377,7 +367,7 @@ public class EthEstimateGasTest { .isAllowExceedingBalance(false) .build()), any(OperationTracer.class), - eq(1L)); + eq(latestBlockHeader)); } @Test @@ -385,22 +375,44 @@ public class EthEstimateGasTest { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); mockTransientProcessorResultTxInvalidReason( - TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); + TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION", latestBlockHeader); final ValidationResult validationResult = ValidationResult.invalid(TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void shouldUseBlockTagParamWhenPresent() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(eip1559TransactionCallParameter(), "finalized"); + mockTransientProcessorResultGasEstimate(1L, true, false, finalizedBlockHeader); + + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void shouldUseBlockNumberParamWhenPresent() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(eip1559TransactionCallParameter(), "0x0"); + mockTransientProcessorResultGasEstimate(1L, true, false, genesisBlockHeader); + + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } private void mockTransientProcessorResultTxInvalidReason( - final TransactionInvalidReason reason, final String validationFailedErrorMessage) { + final TransactionInvalidReason reason, + final String validationFailedErrorMessage, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = - getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty()); + getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty(), blockHeader); when(mockTxSimResult.getValidationResult()) .thenReturn( validationFailedErrorMessage == null @@ -409,45 +421,55 @@ public class EthEstimateGasTest { } private void mockTransientProcessorTxReverted( - final long estimateGas, final boolean isSuccessful, final Bytes revertReason) { + final long estimateGas, + final boolean isSuccessful, + final Bytes revertReason, + final BlockHeader blockHeader) { mockTransientProcessorResultGasEstimate( - estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason)); + estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason), blockHeader); } private void mockTransientProcessorResultGasEstimate( - final long estimateGas, final boolean isSuccessful, final boolean isReverted) { + final long estimateGas, + final boolean isSuccessful, + final boolean isReverted, + final BlockHeader blockHeader) { mockTransientProcessorResultGasEstimate( estimateGas, isSuccessful, Wei.ZERO, - isReverted ? Optional.of(Bytes.of(0)) : Optional.empty()); + isReverted ? Optional.of(Bytes.of(0)) : Optional.empty(), + blockHeader); } private void mockTransientProcessorResultGasEstimate( final long estimateGas, final boolean isSuccessful, final Wei gasPrice, - final Optional revertReason) { - getMockTransactionSimulatorResult(isSuccessful, estimateGas, gasPrice, revertReason); + final Optional revertReason, + final BlockHeader blockHeader) { + getMockTransactionSimulatorResult( + isSuccessful, estimateGas, gasPrice, revertReason, blockHeader); } private TransactionSimulatorResult getMockTransactionSimulatorResult( final boolean isSuccessful, final long estimateGas, final Wei gasPrice, - final Optional revertReason) { + final Optional revertReason, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); when(transactionSimulator.process( eq(modifiedLegacyTransactionCallParameter(gasPrice)), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); when(transactionSimulator.process( eq(modifiedEip1559TransactionCallParameter()), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); @@ -523,4 +545,10 @@ public class EthEstimateGasTest { return new JsonRpcRequestContext( new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter})); } + + private JsonRpcRequestContext ethEstimateGasRequest( + final CallParameter callParameter, final String blockParam) { + return new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter, blockParam})); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1Test.java new file mode 100644 index 000000000..29c7ae82e --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1Test.java @@ -0,0 +1,267 @@ +/* + * 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.api.jsonrpc.internal.methods.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPPrivateKey; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.VersionedHash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlobAndProofV1; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.BlobTestFixture; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import io.vertx.core.Vertx; +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.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class EngineGetBlobsV1Test { + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + private static final SECPPrivateKey PRIVATE_KEY1 = + SIGNATURE_ALGORITHM + .get() + .createPrivateKey( + Bytes32.fromHexString( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63")); + private static final KeyPair KEYS1 = + new KeyPair(PRIVATE_KEY1, SIGNATURE_ALGORITHM.get().createPublicKey(PRIVATE_KEY1)); + public static final VersionedHash VERSIONED_HASH_ZERO = new VersionedHash((byte) 1, Hash.ZERO); + + @Mock private ProtocolContext protocolContext; + @Mock private EngineCallListener engineCallListener; + @Mock private MutableBlockchain blockchain; + @Mock private TransactionPool transactionPool; + + private EngineGetBlobsV1 method; + + private static final Vertx vertx = Vertx.vertx(); + + @BeforeEach + public void before() { + when(protocolContext.getBlockchain()).thenReturn(blockchain); + this.method = + spy(new EngineGetBlobsV1(vertx, protocolContext, engineCallListener, transactionPool)); + } + + @Test + public void shouldReturnExpectedMethodName() { + assertThat(method.getName()).isEqualTo("engine_getBlobsV1"); + } + + @Test + public void shouldReturnBlobsAndProofsForKnownVersionedHashesFromMap() { + final Transaction blobTransaction = createBlobTransaction(); + + final BlobsWithCommitments blobsWithCommitments = + blobTransaction.getBlobsWithCommitments().get(); + + mockTransactionPoolMethod(blobsWithCommitments); + + VersionedHash[] versionedHashes = + blobsWithCommitments.getVersionedHashes().toArray(new VersionedHash[0]); + + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + final List blobAndProofV1s = fromSuccessResp(jsonRpcResponse); + + assertThat(blobAndProofV1s.size()).isEqualTo(versionedHashes.length); + // for loop to check each blob and proof + for (int i = 0; i < versionedHashes.length; i++) { + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getBlob())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).blob().getData()); + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getProof())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).kzgProof().getData()); + } + } + + @Test + public void shouldReturnNullForBlobsAndProofsForUnknownVersionedHashes() { + final Transaction blobTransaction = createBlobTransaction(); + + final BlobsWithCommitments blobsWithCommitments = + blobTransaction.getBlobsWithCommitments().get(); + + mockTransactionPoolMethod(blobsWithCommitments); + + List versionedHashesList = + blobsWithCommitments.getVersionedHashes().stream().toList(); + + final VersionedHash[] hashes = versionedHashesList.toArray(new VersionedHash[0]); + hashes[1] = VERSIONED_HASH_ZERO; + + final JsonRpcResponse jsonRpcResponse = resp(hashes); + + final List blobAndProofV1s = fromSuccessResp(jsonRpcResponse); + + assertThat(blobAndProofV1s.size()).isEqualTo(versionedHashesList.size()); + // for loop to check each blob and proof + for (int i = 0; i < versionedHashesList.size(); i++) { + if (i != 1) { + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getBlob())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).blob().getData()); + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getProof())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).kzgProof().getData()); + } else { + assertThat(blobAndProofV1s.get(i)).isNull(); + } + } + } + + @Test + public void shouldReturnOnlyNullsForBlobsAndProofsIfAllVersionedHashesUnknown() { + final Transaction blobTransaction = createBlobTransaction(); + + final BlobsWithCommitments blobsWithCommitments = + blobTransaction.getBlobsWithCommitments().get(); + + mockTransactionPoolMethod(blobsWithCommitments); + + List versionedHashesList = + blobsWithCommitments.getVersionedHashes().stream().toList(); + + final VersionedHash[] versionedHashes = new VersionedHash[6]; + Arrays.fill(versionedHashes, VERSIONED_HASH_ZERO); + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + final List blobAndProofV1s = fromSuccessResp(jsonRpcResponse); + + assertThat(blobAndProofV1s.size()).isEqualTo(versionedHashesList.size()); + // for loop to check each blob and proof + for (int i = 0; i < versionedHashes.length; i++) { + assertThat(blobAndProofV1s.get(i)).isNull(); + } + } + + @Test + public void shouldReturnEmptyResponseForEmptyRequest() { + final VersionedHash[] versionedHashes = new VersionedHash[0]; + + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + assertThat(fromSuccessResp(jsonRpcResponse).size()).isEqualTo(0); + } + + @Test + public void shouldFailWhenRequestingMoreThan128() { + final VersionedHash[] versionedHashes = new VersionedHash[129]; + for (int i = 0; i < 129; i++) { + versionedHashes[i] = new VersionedHash((byte) 1, Hash.ZERO); + } + + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + assertThat(fromErrorResp(jsonRpcResponse).getCode()) + .isEqualTo(RpcErrorType.INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST.getCode()); + assertThat(fromErrorResp(jsonRpcResponse).getMessage()) + .isEqualTo(RpcErrorType.INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST.getMessage()); + } + + Transaction createBlobTransaction() { + BlobTestFixture blobTestFixture = new BlobTestFixture(); + BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(6); + TransactionTestFixture ttf = new TransactionTestFixture(); + Transaction fullOfBlobs = + ttf.to(Optional.of(Address.ZERO)) + .type(TransactionType.BLOB) + .chainId(Optional.of(BigInteger.valueOf(42))) + .gasLimit(21000) + .maxFeePerGas(Optional.of(Wei.of(15))) + .maxFeePerBlobGas(Optional.of(Wei.of(128))) + .maxPriorityFeePerGas(Optional.of(Wei.of(1))) + .versionedHashes(Optional.of(bwc.getVersionedHashes())) + .blobsWithCommitments(Optional.of(bwc)) + .createTransaction(KEYS1); + return fullOfBlobs; + } + + private void mockTransactionPoolMethod(final BlobsWithCommitments blobsWithCommitments) { + blobsWithCommitments + .getBlobQuads() + .forEach( + blobQuad -> + when(transactionPool.getBlobQuad(blobQuad.versionedHash())).thenReturn(blobQuad)); + } + + private JsonRpcResponse resp(final VersionedHash[] versionedHashes) { + return method.response( + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + RpcMethod.ENGINE_GET_BLOBS_V1.getMethodName(), + new Object[] {versionedHashes}))); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private List fromSuccessResp(final JsonRpcResponse resp) { + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); + final List list = + Optional.of(resp) + .map(JsonRpcSuccessResponse.class::cast) + .map(JsonRpcSuccessResponse::getResult) + .map(List.class::cast) + .get(); + final ArrayList blobAndProofV1s = new ArrayList<>(); + list.forEach(obj -> blobAndProofV1s.add((BlobAndProofV1) obj)); + return blobAndProofV1s; + } + + private RpcErrorType fromErrorResp(final JsonRpcResponse resp) { + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); + return Optional.of(resp) + .map(JsonRpcErrorResponse.class::cast) + .map(JsonRpcErrorResponse::getErrorType) + .get(); + } +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json new file mode 100644 index 000000000..aa9731f74 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "data": "0x123456" + }, + "0x1" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x52d4" + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json new file mode 100644 index 000000000..992a7d772 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "data": "0x123456" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5238" + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json new file mode 100644 index 000000000..6dd337509 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "gas": "0x1" + }, + "0xa" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5208" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json new file mode 100644 index 000000000..d909af9ee --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "from": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to": "0x8888f1f195afa192cfee860698584c030f4c9db1", + "value": "0x1" + }, + "earliest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5208" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java index 946d4ab09..88a39925b 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java @@ -63,6 +63,7 @@ import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -419,7 +420,8 @@ abstract class AbstractBlockCreatorTest { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(new NoOpMetricsSystem()), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); final MiningParameters miningParameters = diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java index 416e5fd23..940d076bb 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; @@ -94,7 +95,8 @@ public class LegacyFeeMarketBlockTransactionSelectorTest mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); return transactionPool; } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java index a59841d4f..68d9a71de 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java @@ -31,6 +31,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; @@ -101,7 +102,8 @@ public class LondonFeeMarketBlockTransactionSelectorTest mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); return transactionPool; } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java index 7e3c01200..e9e727d99 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java @@ -39,6 +39,7 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyCalculators; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -371,7 +372,8 @@ class PoWBlockCreatorTest extends AbstractBlockCreatorTest { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(metricsSystem), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); return transactionPool; diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java index 6859409d4..8c1e217d7 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -117,7 +118,8 @@ public class PoWMinerExecutorTest { mock(TransactionBroadcaster.class), ethContext, new TransactionPoolMetrics(new NoOpMetricsSystem()), - poolConf); + poolConf, + new BlobCache()); transactionPool.setEnabled(); return transactionPool; 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/CodeDelegation.java similarity index 76% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java index ea9cae2fc..68fa958a8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SetCodeAuthorization.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java @@ -20,11 +20,10 @@ 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.core.encoding.CodeDelegationEncoder; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import java.math.BigInteger; -import java.util.List; import java.util.Optional; import java.util.function.Supplier; @@ -33,7 +32,7 @@ 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 { +public class CodeDelegation implements org.hyperledger.besu.datatypes.CodeDelegation { private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); @@ -41,7 +40,7 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC private final BigInteger chainId; private final Address address; - private final Optional nonce; + private final long nonce; private final SECPSignature signature; private Optional

authorizer = Optional.empty(); private boolean isAuthorityComputed = false; @@ -51,13 +50,13 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC * * @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 nonce the nonce after which this auth expires * @param signature the signature of the EOA account which will be used to set the code */ - public SetCodeAuthorization( + public CodeDelegation( final BigInteger chainId, final Address address, - final Optional nonce, + final long nonce, final SECPSignature signature) { this.chainId = chainId; this.address = address; @@ -66,29 +65,26 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC } /** - * Create access list entry. + * Create code delegation. * * @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 nonce the nonce * @param v the recovery id * @param r the r value of the signature * @param s the s value of the signature - * @return SetCodeTransactionEntry + * @return CodeDelegation */ @JsonCreator - public static org.hyperledger.besu.datatypes.SetCodeAuthorization createSetCodeAuthorizationEntry( + public static org.hyperledger.besu.datatypes.CodeDelegation createCodeDelegation( @JsonProperty("chainId") final BigInteger chainId, @JsonProperty("address") final Address address, - @JsonProperty("nonce") final List nonces, + @JsonProperty("nonce") final long nonce, @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)); + return new CodeDelegation( + chainId, address, nonce, SIGNATURE_ALGORITHM.get().createSignature(r, s, v)); } @JsonProperty("chainId") @@ -120,7 +116,7 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC } @Override - public Optional nonce() { + public long nonce() { return nonce; } @@ -144,30 +140,38 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC private Optional
computeAuthority() { BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); - SetCodeTransactionEncoder.encodeSingleSetCodeWithoutSignature(this, rlpOutput); + CodeDelegationEncoder.encodeSingleCodeDelegationWithoutSignature(this, rlpOutput); final Hash hash = Hash.hash(Bytes.concatenate(MAGIC, rlpOutput.encoded())); - return SIGNATURE_ALGORITHM - .get() - .recoverPublicKeyFromSignature(hash, signature) - .map(Address::extract); + Optional
authorityAddress; + try { + authorityAddress = + SIGNATURE_ALGORITHM + .get() + .recoverPublicKeyFromSignature(hash, signature) + .map(Address::extract); + } catch (final IllegalArgumentException e) { + authorityAddress = Optional.empty(); + } + + return authorityAddress; } /** - * Create set code authorization with a builder. + * Create a code delegation authorization with a builder. * - * @return SetCodeAuthorization.Builder + * @return CodeDelegation.Builder */ public static Builder builder() { return new Builder(); } - /** Builder for SetCodeAuthorization. */ + /** Builder for CodeDelegation authorizations. */ public static class Builder { private BigInteger chainId = BigInteger.ZERO; private Address address; - private Optional nonce = Optional.empty(); + private Long nonce; private SECPSignature signature; /** Create a new builder. */ @@ -196,12 +200,12 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC } /** - * Set the optional nonce. + * Set the nonce. * - * @param nonce the optional nonce. + * @param nonce the nonce. * @return this builder */ - public Builder nonces(final Optional nonce) { + public Builder nonce(final long nonce) { this.nonce = nonce; return this; } @@ -221,16 +225,14 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC * Sign the authorization with the given key pair and return the authorization. * * @param keyPair the key pair - * @return SetCodeAuthorization + * @return CodeDelegation */ - public org.hyperledger.besu.datatypes.SetCodeAuthorization signAndBuild(final KeyPair keyPair) { + public org.hyperledger.besu.datatypes.CodeDelegation 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.writeLongScalar(nonce); output.endList(); signature( @@ -243,18 +245,22 @@ public class SetCodeAuthorization implements org.hyperledger.besu.datatypes.SetC /** * Build the authorization. * - * @return SetCodeAuthorization + * @return CodeDelegation */ - public org.hyperledger.besu.datatypes.SetCodeAuthorization build() { + public org.hyperledger.besu.datatypes.CodeDelegation build() { if (address == null) { throw new IllegalStateException("Address must be set"); } + if (nonce == null) { + throw new IllegalStateException("Nonce must be set"); + } + if (signature == null) { throw new IllegalStateException("Signature must be set"); } - return new SetCodeAuthorization(chainId, address, nonce, signature); + return new CodeDelegation(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 513cfb831..0da8f8f28 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 @@ -28,18 +28,18 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Blob; import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.CodeDelegation; 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; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder; import org.hyperledger.besu.ethereum.core.encoding.BlobTransactionEncoder; +import org.hyperledger.besu.ethereum.core.encoding.CodeDelegationEncoder; 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; @@ -124,7 +124,7 @@ public class Transaction private final Optional> versionedHashes; private final Optional blobsWithCommitments; - private final Optional> maybeAuthorizationList; + private final Optional> maybeCodeDelegationList; public static Builder builder() { return new Builder(); @@ -181,7 +181,7 @@ public class Transaction final Optional chainId, final Optional> versionedHashes, final Optional blobsWithCommitments, - final Optional> maybeAuthorizationList) { + final Optional> maybeCodeDelegationList) { if (!forCopy) { if (transactionType.requiresChainId()) { @@ -218,10 +218,10 @@ public class Transaction maxFeePerBlobGas.isPresent(), "Must specify max fee per blob gas for blob transaction"); } - if (transactionType.requiresSetCode()) { + if (transactionType.requiresCodeDelegation()) { checkArgument( - maybeAuthorizationList.isPresent(), - "Must specify set code transaction payload for set code transaction"); + maybeCodeDelegationList.isPresent(), + "Must specify code delegation authorizations for code delegation transaction"); } } @@ -241,7 +241,7 @@ public class Transaction this.chainId = chainId; this.versionedHashes = versionedHashes; this.blobsWithCommitments = blobsWithCommitments; - this.maybeAuthorizationList = maybeAuthorizationList; + this.maybeCodeDelegationList = maybeCodeDelegationList; } /** @@ -473,7 +473,7 @@ public class Transaction payload, maybeAccessList, versionedHashes.orElse(null), - maybeAuthorizationList, + maybeCodeDelegationList, chainId); } return hashNoSignature; @@ -681,13 +681,13 @@ public class Transaction } @Override - public Optional> getAuthorizationList() { - return maybeAuthorizationList; + public Optional> getCodeDelegationList() { + return maybeCodeDelegationList; } @Override - public int authorizationListSize() { - return maybeAuthorizationList.map(List::size).orElse(0); + public int codeDelegationListSize() { + return maybeCodeDelegationList.map(List::size).orElse(0); } /** @@ -714,7 +714,7 @@ public class Transaction final Bytes payload, final Optional> accessList, final List versionedHashes, - final Optional> authorizationList, + final Optional> codeDelegationList, final Optional chainId) { if (transactionType.requiresChainId()) { checkArgument(chainId.isPresent(), "Transaction type %s requires chainId", transactionType); @@ -759,8 +759,8 @@ public class Transaction new IllegalStateException( "Developer error: the transaction should be guaranteed to have an access list here")), chainId); - case SET_CODE -> - setCodePreimage( + case DELEGATE_CODE -> + codeDelegationPreimage( nonce, maxPriorityFeePerGas, maxFeePerGas, @@ -770,10 +770,10 @@ public class Transaction payload, chainId, accessList, - authorizationList.orElseThrow( + codeDelegationList.orElseThrow( () -> new IllegalStateException( - "Developer error: the transaction should be guaranteed to have a set code payload here"))); + "Developer error: the transaction should be guaranteed to have a code delegations here"))); }; return keccak256(preimage); } @@ -911,7 +911,7 @@ public class Transaction return Bytes.concatenate(Bytes.of(TransactionType.ACCESS_LIST.getSerializedType()), encode); } - private static Bytes setCodePreimage( + private static Bytes codeDelegationPreimage( final long nonce, final Wei maxPriorityFeePerGas, final Wei maxFeePerGas, @@ -921,7 +921,7 @@ public class Transaction final Bytes payload, final Optional chainId, final Optional> accessList, - final List authorizationList) { + final List authorizationList) { final Bytes encoded = RLP.encode( rlpOutput -> { @@ -937,10 +937,10 @@ public class Transaction chainId, accessList, rlpOutput); - SetCodeTransactionEncoder.encodeSetCodeInner(authorizationList, rlpOutput); + CodeDelegationEncoder.encodeCodeDelegationInner(authorizationList, rlpOutput); rlpOutput.endList(); }); - return Bytes.concatenate(Bytes.of(TransactionType.SET_CODE.getSerializedType()), encoded); + return Bytes.concatenate(Bytes.of(TransactionType.DELEGATE_CODE.getSerializedType()), encoded); } @Override @@ -1111,7 +1111,7 @@ public class Transaction chainId, detachedVersionedHashes, detachedBlobsWithCommitments, - maybeAuthorizationList); + maybeCodeDelegationList); // copy also the computed fields, to avoid to recompute them copiedTx.sender = this.sender; @@ -1179,7 +1179,7 @@ public class Transaction protected Optional v = Optional.empty(); protected List versionedHashes = null; private BlobsWithCommitments blobsWithCommitments; - protected Optional> setCodeTransactionPayloads = Optional.empty(); + protected Optional> codeDelegationAuthorizations = Optional.empty(); public Builder copiedFrom(final Transaction toCopy) { this.transactionType = toCopy.transactionType; @@ -1198,7 +1198,7 @@ public class Transaction this.chainId = toCopy.chainId; this.versionedHashes = toCopy.versionedHashes.orElse(null); this.blobsWithCommitments = toCopy.blobsWithCommitments.orElse(null); - this.setCodeTransactionPayloads = toCopy.maybeAuthorizationList; + this.codeDelegationAuthorizations = toCopy.maybeCodeDelegationList; return this; } @@ -1292,8 +1292,8 @@ public class Transaction transactionType = TransactionType.EIP1559; } else if (accessList.isPresent()) { transactionType = TransactionType.ACCESS_LIST; - } else if (setCodeTransactionPayloads.isPresent()) { - transactionType = TransactionType.SET_CODE; + } else if (codeDelegationAuthorizations.isPresent()) { + transactionType = TransactionType.DELEGATE_CODE; } else { transactionType = TransactionType.FRONTIER; } @@ -1324,7 +1324,7 @@ public class Transaction chainId, Optional.ofNullable(versionedHashes), Optional.ofNullable(blobsWithCommitments), - setCodeTransactionPayloads); + codeDelegationAuthorizations); } public Transaction signAndBuild(final KeyPair keys) { @@ -1351,7 +1351,7 @@ public class Transaction payload, accessList, versionedHashes, - setCodeTransactionPayloads, + codeDelegationAuthorizations, chainId), keys); } @@ -1376,9 +1376,8 @@ public class Transaction return this; } - public Builder setCodeTransactionPayloads( - final List setCodeTransactionEntries) { - this.setCodeTransactionPayloads = Optional.ofNullable(setCodeTransactionEntries); + public Builder codeDelegations(final List codeDelegations) { + this.codeDelegationAuthorizations = Optional.ofNullable(codeDelegations); return this; } } 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/CodeDelegationEncoder.java similarity index 75% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoder.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java index 05808f312..4cedf93ad 100644 --- 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/CodeDelegationEncoder.java @@ -17,7 +17,7 @@ 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.datatypes.CodeDelegation; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.rlp.RLPOutput; @@ -25,28 +25,28 @@ import java.util.List; import org.apache.tuweni.bytes.Bytes; -public class SetCodeTransactionEncoder { +public class CodeDelegationEncoder { - private SetCodeTransactionEncoder() { + private CodeDelegationEncoder() { // private constructor } - public static void encodeSetCodeInner( - final List payloads, final RLPOutput rlpOutput) { + public static void encodeCodeDelegationInner( + final List payloads, final RLPOutput rlpOutput) { rlpOutput.startList(); - payloads.forEach(payload -> encodeSingleSetCode(payload, rlpOutput)); + payloads.forEach(payload -> encodeSingleCodeDelegation(payload, rlpOutput)); rlpOutput.endList(); } - public static void encodeSingleSetCodeWithoutSignature( - final SetCodeAuthorization payload, final RLPOutput rlpOutput) { + public static void encodeSingleCodeDelegationWithoutSignature( + final CodeDelegation payload, final RLPOutput rlpOutput) { rlpOutput.startList(); encodeAuthorizationDetails(payload, rlpOutput); rlpOutput.endList(); } - public static void encodeSingleSetCode( - final SetCodeAuthorization payload, final RLPOutput rlpOutput) { + public static void encodeSingleCodeDelegation( + final CodeDelegation payload, final RLPOutput rlpOutput) { rlpOutput.startList(); encodeAuthorizationDetails(payload, rlpOutput); rlpOutput.writeIntScalar(payload.signature().getRecId()); @@ -56,12 +56,10 @@ public class SetCodeTransactionEncoder { } private static void encodeAuthorizationDetails( - final SetCodeAuthorization payload, final RLPOutput rlpOutput) { + final CodeDelegation payload, final RLPOutput rlpOutput) { rlpOutput.writeBigIntegerScalar(payload.chainId()); rlpOutput.writeBytes(payload.address().copy()); - rlpOutput.startList(); - payload.nonce().ifPresent(rlpOutput::writeLongScalar); - rlpOutput.endList(); + rlpOutput.writeLongScalar(payload.nonce()); } public static void encode(final Transaction transaction, final RLPOutput out) { @@ -75,13 +73,13 @@ public class SetCodeTransactionEncoder { out.writeUInt256Scalar(transaction.getValue()); out.writeBytes(transaction.getPayload()); writeAccessList(out, transaction.getAccessList()); - encodeSetCodeInner( + encodeCodeDelegationInner( transaction - .getAuthorizationList() + .getCodeDelegationList() .orElseThrow( () -> new IllegalStateException( - "Developer error: the transaction should be guaranteed to have a set code payload here")), + "Developer error: the transaction should be guaranteed to have a code delegation authorizations here")), out); writeSignatureAndRecoveryId(transaction, out); out.endList(); 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/CodeDelegationTransactionDecoder.java similarity index 73% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoder.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java index 80d59de83..8961431c9 100644 --- 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/CodeDelegationTransactionDecoder.java @@ -19,23 +19,22 @@ 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.CodeDelegation; 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 { +public class CodeDelegationTransactionDecoder { private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); - private SetCodeTransactionDecoder() { + private CodeDelegationTransactionDecoder() { // private constructor } @@ -44,7 +43,7 @@ public class SetCodeTransactionDecoder { final BigInteger chainId = input.readBigIntegerScalar(); final Transaction.Builder builder = Transaction.builder() - .type(TransactionType.SET_CODE) + .type(TransactionType.DELEGATE_CODE) .chainId(chainId) .nonce(input.readLongScalar()) .maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar())) @@ -64,10 +63,7 @@ public class SetCodeTransactionDecoder { accessListEntryRLPInput.leaveList(); return accessListEntry; })) - .setCodeTransactionPayloads( - input.readList( - setCodeTransactionPayloadsRLPInput -> - decodeInnerPayload(setCodeTransactionPayloadsRLPInput))); + .codeDelegations(input.readList(CodeDelegationTransactionDecoder::decodeInnerPayload)); final byte recId = (byte) input.readUnsignedByteScalar(); final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); @@ -75,33 +71,15 @@ public class SetCodeTransactionDecoder { input.leaveList(); - final Transaction transaction = - builder.signature(SIGNATURE_ALGORITHM.get().createSignature(r, s, recId)).build(); - - return transaction; + return builder.signature(SIGNATURE_ALGORITHM.get().createSignature(r, s, recId)).build(); } - public static org.hyperledger.besu.datatypes.SetCodeAuthorization decodeInnerPayload( - final RLPInput input) { + public static CodeDelegation 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 long nonce = input.readLongScalar(); final byte yParity = (byte) input.readUnsignedByteScalar(); final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); @@ -111,6 +89,7 @@ public class SetCodeTransactionDecoder { final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, yParity); - return new SetCodeAuthorization(chainId, address, nonce, signature); + return new org.hyperledger.besu.ethereum.core.CodeDelegation( + chainId, address, nonce, signature); } } 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 2a63d8f24..7261aecbe 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 @@ -41,8 +41,8 @@ public class TransactionDecoder { EIP1559TransactionDecoder::decode, TransactionType.BLOB, BlobTransactionDecoder::decode, - TransactionType.SET_CODE, - SetCodeTransactionDecoder::decode); + TransactionType.DELEGATE_CODE, + CodeDelegationTransactionDecoder::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 499597562..26bad56c6 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 @@ -40,8 +40,8 @@ public class TransactionEncoder { EIP1559TransactionEncoder::encode, TransactionType.BLOB, BlobTransactionEncoder::encode, - TransactionType.SET_CODE, - SetCodeTransactionEncoder::encode); + TransactionType.DELEGATE_CODE, + CodeDelegationEncoder::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 deleted file mode 100644 index 1d25d2d34..000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AuthorityProcessor.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.EVMWorldUpdater; - -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 EVMWorldUpdater evmWorldUpdater, 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(evmWorldUpdater.getAccount(authorityAddress)); - final long accountNonce = - maybeAccount.map(AccountState::getNonce).orElse(0L); - - if (payload.nonce().isPresent() - && !payload.nonce().get().equals(accountNonce)) { - return; - } - - if (evmWorldUpdater - .authorizedCodeService() - .hasAuthorizedCode(authorityAddress)) { - return; - } - - Optional codeAccount = - Optional.ofNullable(evmWorldUpdater.get(payload.address())); - final Bytes code; - if (codeAccount.isPresent()) { - code = codeAccount.get().getCode(); - } else { - code = Bytes.EMPTY; - } - - evmWorldUpdater - .authorizedCodeService() - .addAuthorizedCode(authorityAddress, code); - })); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java new file mode 100644 index 000000000..58f66b6af --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java @@ -0,0 +1,134 @@ +/* + * 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.datatypes.Address; +import org.hyperledger.besu.ethereum.core.CodeDelegation; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.EVMWorldUpdater; + +import java.math.BigInteger; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CodeDelegationProcessor { + private static final Logger LOG = LoggerFactory.getLogger(CodeDelegationProcessor.class); + + private final Optional maybeChainId; + + public CodeDelegationProcessor(final Optional maybeChainId) { + this.maybeChainId = maybeChainId; + } + + /** + * At the start of executing the transaction, after incrementing the sender’s nonce, for each + * authorization we do the following: + * + *
    + *
  1. Verify the chain id is either 0 or the chain's current ID. + *
  2. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]` + *
  3. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).) + *
  4. Verify the code of `authority` is either empty or already delegated. + *
  5. Verify the nonce of `authority` is equal to `nonce`. + *
  6. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if + * `authority` exists in the trie. + *
  7. Set the code of `authority` to be `0xef0100 || address`. This is a delegation + * designation. + *
  8. Increase the nonce of `authority` by one. + *
+ * + * @param evmWorldUpdater The world state updater which is aware of code delegation. + * @param transaction The transaction being processed. + * @return The result of the code delegation processing. + */ + public CodeDelegationResult process( + final EVMWorldUpdater evmWorldUpdater, final Transaction transaction) { + final CodeDelegationResult result = new CodeDelegationResult(); + + transaction + .getCodeDelegationList() + .get() + .forEach( + codeDelegation -> + processAuthorization( + evmWorldUpdater, + (org.hyperledger.besu.ethereum.core.CodeDelegation) codeDelegation, + result)); + + return result; + } + + private void processAuthorization( + final EVMWorldUpdater evmWorldUpdater, + final CodeDelegation codeDelegation, + final CodeDelegationResult result) { + LOG.trace("Processing code delegation: {}", codeDelegation); + + if (maybeChainId.isPresent() + && !codeDelegation.chainId().equals(BigInteger.ZERO) + && !maybeChainId.get().equals(codeDelegation.chainId())) { + LOG.trace( + "Invalid chain id for code delegation. Expected: {}, Actual: {}", + maybeChainId.get(), + codeDelegation.chainId()); + return; + } + + final Optional
authorizer = codeDelegation.authorizer(); + if (authorizer.isEmpty()) { + LOG.trace("Invalid signature for code delegation"); + return; + } + + LOG.trace("Set code delegation for authority: {}", authorizer.get()); + + final Optional maybeAuthorityAccount = + Optional.ofNullable(evmWorldUpdater.getAccount(authorizer.get())); + + result.addAccessedDelegatorAddress(authorizer.get()); + + MutableAccount authority; + boolean authorityDoesAlreadyExist = false; + if (maybeAuthorityAccount.isEmpty()) { + authority = evmWorldUpdater.createAccount(authorizer.get()); + } else { + authority = maybeAuthorityAccount.get(); + + if (!evmWorldUpdater.authorizedCodeService().canSetDelegatedCode(authority)) { + return; + } + + authorityDoesAlreadyExist = true; + } + + if (codeDelegation.nonce() != authority.getNonce()) { + LOG.trace( + "Invalid nonce for code delegation. Expected: {}, Actual: {}", + authority.getNonce(), + codeDelegation.nonce()); + return; + } + + if (authorityDoesAlreadyExist) { + result.incremenentAlreadyExistingDelegators(); + } + + evmWorldUpdater.authorizedCodeService().addDelegatedCode(authority, codeDelegation.address()); + authority.incrementNonce(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java new file mode 100644 index 000000000..20d75a675 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java @@ -0,0 +1,41 @@ +/* + * 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.collections.trie.BytesTrieSet; +import org.hyperledger.besu.datatypes.Address; + +import java.util.Set; + +public class CodeDelegationResult { + private final Set
accessedDelegatorAddresses = new BytesTrieSet<>(Address.SIZE); + private long alreadyExistingDelegators = 0L; + + public void addAccessedDelegatorAddress(final Address address) { + accessedDelegatorAddresses.add(address); + } + + public void incremenentAlreadyExistingDelegators() { + alreadyExistingDelegators += 1; + } + + public Set
accessedDelegatorAddresses() { + return accessedDelegatorAddresses; + } + + public long alreadyExistingDelegators() { + return alreadyExistingDelegators; + } +} 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 a96ca58fd..e6d7a88cb 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 @@ -650,7 +650,7 @@ public abstract class MainnetProtocolSpecs { TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559), - evm.getEvmVersion().getMaxInitcodeSize())) + evm.getMaxInitcodeSize())) .withdrawalsProcessor(new WithdrawalsProcessor()) .withdrawalsValidator(new WithdrawalsValidator.AllowedWithdrawals()) .name("Shanghai"); @@ -715,7 +715,7 @@ public abstract class MainnetProtocolSpecs { evmConfiguration.evmStackSize(), feeMarket, CoinbaseFeePriceCalculator.eip1559(), - new AuthorityProcessor(chainId))) + new CodeDelegationProcessor(chainId))) // change to check for max blob gas per block for EIP-4844 .transactionValidatorFactoryBuilder( (evm, gasLimitCalculator, feeMarket) -> @@ -730,7 +730,7 @@ public abstract class MainnetProtocolSpecs { TransactionType.ACCESS_LIST, TransactionType.EIP1559, TransactionType.BLOB), - evm.getEvmVersion().getMaxInitcodeSize())) + evm.getMaxInitcodeSize())) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::cancun) .blockHeaderValidatorBuilder(MainnetBlockHeaderValidator::cancunBlockHeaderValidator) .blockHashProcessor(new CancunBlockHashProcessor()) @@ -813,8 +813,8 @@ public abstract class MainnetProtocolSpecs { TransactionType.ACCESS_LIST, TransactionType.EIP1559, TransactionType.BLOB, - TransactionType.SET_CODE), - evm.getEvmVersion().getMaxInitcodeSize())) + TransactionType.DELEGATE_CODE), + evm.getMaxInitcodeSize())) // EIP-2935 Blockhash processor .blockHashProcessor(new PragueBlockHashProcessor()) 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 b008a8d5f..91c964525 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 @@ -81,7 +81,7 @@ public class MainnetTransactionProcessor { protected final FeeMarket feeMarket; private final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator; - private final Optional maybeAuthorityProcessor; + private final Optional maybeCodeDelegationProcessor; public MainnetTransactionProcessor( final GasCalculator gasCalculator, @@ -116,7 +116,7 @@ public class MainnetTransactionProcessor { final int maxStackSize, final FeeMarket feeMarket, final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator, - final AuthorityProcessor maybeAuthorityProcessor) { + final CodeDelegationProcessor maybeCodeDelegationProcessor) { this.gasCalculator = gasCalculator; this.transactionValidatorFactory = transactionValidatorFactory; this.contractCreationProcessor = contractCreationProcessor; @@ -126,7 +126,7 @@ public class MainnetTransactionProcessor { this.maxStackSize = maxStackSize; this.feeMarket = feeMarket; this.coinbaseFeePriceCalculator = coinbaseFeePriceCalculator; - this.maybeAuthorityProcessor = Optional.ofNullable(maybeAuthorityProcessor); + this.maybeCodeDelegationProcessor = Optional.ofNullable(maybeCodeDelegationProcessor); } /** @@ -316,16 +316,7 @@ public class MainnetTransactionProcessor { operationTracer.tracePrepareTransaction(evmWorldUpdater, transaction); - final Set
addressList = new BytesTrieSet<>(Address.SIZE); - - if (transaction.getAuthorizationList().isPresent()) { - if (maybeAuthorityProcessor.isEmpty()) { - throw new RuntimeException("Authority processor is required for 7702 transactions"); - } - - maybeAuthorityProcessor.get().addContractToAuthority(evmWorldUpdater, transaction); - addressList.addAll(evmWorldUpdater.authorizedCodeService().getAuthorities()); - } + final Set
warmAddressList = new BytesTrieSet<>(Address.SIZE); final long previousNonce = sender.incrementNonce(); LOG.trace( @@ -349,6 +340,20 @@ public class MainnetTransactionProcessor { previousBalance, sender.getBalance()); + long codeDelegationRefund = 0L; + if (transaction.getCodeDelegationList().isPresent()) { + if (maybeCodeDelegationProcessor.isEmpty()) { + throw new RuntimeException("Code delegation processor is required for 7702 transactions"); + } + + final CodeDelegationResult codeDelegationResult = + maybeCodeDelegationProcessor.get().process(evmWorldUpdater, transaction); + warmAddressList.addAll(codeDelegationResult.accessedDelegatorAddresses()); + codeDelegationRefund = + gasCalculator.calculateDelegateCodeGasRefund( + (codeDelegationResult.alreadyExistingDelegators())); + } + final List accessListEntries = transaction.getAccessList().orElse(List.of()); // we need to keep a separate hash set of addresses in case they specify no storage. // No-storage is a common pattern, especially for Externally Owned Accounts @@ -356,13 +361,13 @@ public class MainnetTransactionProcessor { int accessListStorageCount = 0; for (final var entry : accessListEntries) { final Address address = entry.address(); - addressList.add(address); + warmAddressList.add(address); final List storageKeys = entry.storageKeys(); storageList.putAll(address, storageKeys); accessListStorageCount += storageKeys.size(); } if (warmCoinbase) { - addressList.add(miningBeneficiary); + warmAddressList.add(miningBeneficiary); } final long intrinsicGas = @@ -370,16 +375,17 @@ public class MainnetTransactionProcessor { transaction.getPayload(), transaction.isContractCreation()); final long accessListGas = gasCalculator.accessListGasCost(accessListEntries.size(), accessListStorageCount); - final long setCodeGas = gasCalculator.setCodeListGasCost(transaction.authorizationListSize()); + final long codeDelegationGas = + gasCalculator.delegateCodeGasCost(transaction.codeDelegationListSize()); final long gasAvailable = - transaction.getGasLimit() - intrinsicGas - accessListGas - setCodeGas; + transaction.getGasLimit() - intrinsicGas - accessListGas - codeDelegationGas; LOG.trace( - "Gas available for execution {} = {} - {} - {} - {} (limit - intrinsic - accessList - setCode)", + "Gas available for execution {} = {} - {} - {} - {} (limit - intrinsic - accessList - codeDelegation)", gasAvailable, transaction.getGasLimit(), intrinsicGas, accessListGas, - setCodeGas); + codeDelegationGas); final WorldUpdater worldUpdater = evmWorldUpdater.updater(); final ImmutableMap.Builder contextVariablesBuilder = @@ -409,7 +415,7 @@ public class MainnetTransactionProcessor { .miningBeneficiary(miningBeneficiary) .blockHashLookup(blockHashLookup) .contextVariables(contextVariablesBuilder.build()) - .accessListWarmAddresses(addressList) + .accessListWarmAddresses(warmAddressList) .accessListWarmStorage(storageList); if (transaction.getVersionedHashes().isPresent()) { @@ -488,7 +494,8 @@ public class MainnetTransactionProcessor { // after the other so that if it is the same account somehow, we end up with the right result) final long selfDestructRefund = gasCalculator.getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size(); - final long baseRefundGas = initialFrame.getGasRefund() + selfDestructRefund; + final long baseRefundGas = + initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund; final long refundedGas = refunded(transaction, initialFrame.getRemainingGas(), baseRefundGas); final Wei refundedWei = transactionGasPrice.multiply(refundedGas); final Wei balancePriorToRefund = sender.getBalance(); @@ -528,7 +535,6 @@ public class MainnetTransactionProcessor { final var coinbase = evmWorldUpdater.getOrCreate(miningBeneficiary); coinbase.incrementBalance(coinbaseWeiDelta); - evmWorldUpdater.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 481ce70e0..4f4c554f3 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 @@ -20,6 +20,7 @@ import org.hyperledger.besu.crypto.SECPSignature; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Blob; import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.CodeDelegation; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.KZGCommitment; import org.hyperledger.besu.datatypes.TransactionType; @@ -129,9 +130,50 @@ public class MainnetTransactionValidator implements TransactionValidator { transaction.getPayload().size(), maxInitcodeSize)); } + if (transactionType == TransactionType.DELEGATE_CODE) { + if (isDelegateCodeEmpty(transaction)) { + return ValidationResult.invalid( + TransactionInvalidReason.EMPTY_CODE_DELEGATION, + "transaction code delegation transactions must have a non-empty code delegation list"); + } + + final BigInteger halfCurveOrder = SignatureAlgorithmFactory.getInstance().getHalfCurveOrder(); + final Optional> validationResult = + transaction + .getCodeDelegationList() + .map( + codeDelegations -> { + for (CodeDelegation codeDelegation : codeDelegations) { + if (codeDelegation.signature().getS().compareTo(halfCurveOrder) > 0) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_SIGNATURE, + "Invalid signature for code delegation. S value must be less or equal than the half curve order."); + } + + if (codeDelegation.signature().getRecId() != 0 + && codeDelegation.signature().getRecId() != 1) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_SIGNATURE, + "Invalid signature for code delegation. RecId value must be 0 or 1."); + } + } + + return ValidationResult.valid(); + }); + + if (validationResult.isPresent() && !validationResult.get().isValid()) { + return validationResult.get(); + } + } + return validateCostAndFee(transaction, baseFee, blobFee, transactionValidationParams); } + private static boolean isDelegateCodeEmpty(final Transaction transaction) { + return transaction.getCodeDelegationList().isEmpty() + || transaction.getCodeDelegationList().get().isEmpty(); + } + private ValidationResult validateCostAndFee( final Transaction transaction, final Optional maybeBaseFee, @@ -190,7 +232,7 @@ public class MainnetTransactionValidator implements TransactionValidator { gasCalculator.transactionIntrinsicGasCost( transaction.getPayload(), transaction.isContractCreation()) + (transaction.getAccessList().map(gasCalculator::accessListGasCost).orElse(0L)) - + gasCalculator.setCodeListGasCost(transaction.authorizationListSize()); + + gasCalculator.delegateCodeGasCost(transaction.codeDelegationListSize()); if (Long.compareUnsigned(intrinsicGasCost, transaction.getGasLimit()) > 0) { return ValidationResult.invalid( TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT, @@ -250,7 +292,8 @@ public class MainnetTransactionValidator implements TransactionValidator { transaction.getNonce(), senderNonce)); } - if (!validationParams.isAllowContractAddressAsSender() && !codeHash.equals(Hash.EMPTY)) { + if (!validationParams.isAllowContractAddressAsSender() + && !canSendTransaction(sender, codeHash)) { return ValidationResult.invalid( TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED, String.format( @@ -261,6 +304,10 @@ public class MainnetTransactionValidator implements TransactionValidator { return ValidationResult.valid(); } + private static boolean canSendTransaction(final Account sender, final Hash codeHash) { + return codeHash.equals(Hash.EMPTY) || sender.hasDelegatedCode(); + } + private ValidationResult validateTransactionSignature( final Transaction transaction) { if (chainId.isPresent() diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index 6a5a5d213..bf5c6b3eb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -51,6 +51,7 @@ public enum TransactionInvalidReason { PLUGIN_TX_POOL_VALIDATOR, EXECUTION_HALTED, EOF_CODE_INVALID, + EMPTY_CODE_DELEGATION, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_INVALID, PRIVATE_TRANSACTION_FAILED, 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 2709b94e2..ea62d7f1f 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,7 +376,7 @@ public class BlockDataGenerator { case EIP1559 -> eip1559Transaction(payload, to); case ACCESS_LIST -> accessListTransaction(payload, to); case BLOB -> blobTransaction(payload, to); - case SET_CODE -> null; + case DELEGATE_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 3f7049be3..602116749 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,7 +92,7 @@ public class TransactionTestFixture { builder.versionedHashes(versionedHashes.get()); } break; - case SET_CODE: + case DELEGATE_CODE: break; } 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/CodeDelegationDecoderTest.java similarity index 67% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionDecoderTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java index 610b5906f..d6ff585b4 100644 --- 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/CodeDelegationDecoderTest.java @@ -15,11 +15,10 @@ 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.datatypes.CodeDelegation; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import java.math.BigInteger; @@ -27,7 +26,7 @@ import java.math.BigInteger; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; -class SetCodeTransactionDecoderTest { +class CodeDelegationDecoderTest { @Test void shouldDecodeInnerPayloadWithNonce() { @@ -36,14 +35,14 @@ class SetCodeTransactionDecoderTest { final BytesValueRLPInput input = new BytesValueRLPInput( Bytes.fromHexString( - "0xf85b0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c18080a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"), + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa562a80a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"), true); - final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input); + final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input); assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE); assertThat(authorization.address()) .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); - assertThat(authorization.nonce().get()).isEqualTo(0L); + assertThat(authorization.nonce()).isEqualTo(42); final SECPSignature signature = authorization.signature(); assertThat(signature.getRecId()).isEqualTo((byte) 0); @@ -54,20 +53,20 @@ class SetCodeTransactionDecoderTest { } @Test - void shouldDecodeInnerPayloadWithoutNonce() { + void shouldDecodeInnerPayloadWithNonceZero() { // "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" final BytesValueRLPInput input = new BytesValueRLPInput( Bytes.fromHexString( - "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"), + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa568001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"), true); - final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input); + final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input); assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE); assertThat(authorization.address()) .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); - assertThat(authorization.nonce()).isEmpty(); + assertThat(authorization.nonce()).isEqualTo(0); final SECPSignature signature = authorization.signature(); assertThat(signature.getRecId()).isEqualTo((byte) 1); @@ -78,37 +77,20 @@ class SetCodeTransactionDecoderTest { } @Test - void shouldThrowInnerPayloadWithMultipleNonces() { - // "d90194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c20107" - - final BytesValueRLPInput input = - new BytesValueRLPInput( - Bytes.fromHexString( - "0xf85c0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c2010201a0401b5d4ebe88306448115d1a46a30e5ad1136f2818b4ebb0733d9c4efffd135aa0753ff1dbce6db504ecb9635a64d8c4506ff887e2d2a0d2b7175baf94c849eccc"), - true); - - assertThrows( - IllegalArgumentException.class, - () -> { - SetCodeTransactionDecoder.decodeInnerPayload(input); - }); - } - - @Test - void shouldDecodeInnerPayloadWithoutNonceAndChainIdZero() { + void shouldDecodeInnerPayloadWithChainIdZero() { // "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" final BytesValueRLPInput input = new BytesValueRLPInput( Bytes.fromHexString( - "0xf85a0094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"), + "0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa560501a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"), true); - final SetCodeAuthorization authorization = SetCodeTransactionDecoder.decodeInnerPayload(input); + final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input); assertThat(authorization.chainId()).isEqualTo(BigInteger.ZERO); assertThat(authorization.address()) .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); - assertThat(authorization.nonce().isEmpty()).isTrue(); + assertThat(authorization.nonce()).isEqualTo(5); final SECPSignature signature = authorization.signature(); assertThat(signature.getRecId()).isEqualTo((byte) 1); 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/CodeDelegationEncoderTest.java similarity index 75% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/SetCodeTransactionEncoderTest.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java index 89211f4ba..34b7c6d44 100644 --- 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/CodeDelegationEncoderTest.java @@ -19,11 +19,10 @@ 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.core.CodeDelegation; 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; @@ -31,7 +30,7 @@ import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class SetCodeTransactionEncoderTest { +class CodeDelegationEncoderTest { private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); @@ -43,14 +42,14 @@ class SetCodeTransactionEncoderTest { } @Test - void shouldEncodeSingleSetCodeWithNonce() { + void shouldEncodeSingleCodeDelegationWithNonceAndChainId() { // "0xd80194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c105" - final SetCodeAuthorization authorization = - new SetCodeAuthorization( + final CodeDelegation authorization = + new CodeDelegation( BigInteger.ONE, Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), - Optional.of(0L), + 42, SIGNATURE_ALGORITHM .get() .createSignature( @@ -60,23 +59,23 @@ class SetCodeTransactionEncoderTest { "3b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99", 16), (byte) 0)); - SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output); + CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output); assertThat(output.encoded()) .isEqualTo( Bytes.fromHexString( - "0xf85b0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c18080a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99")); + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa562a80a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99")); } @Test - void shouldEncodeSingleSetCodeWithoutNonce() { + void shouldEncodeSingleCodeDelegationWithNonceZero() { // "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" - final SetCodeAuthorization authorization = - new SetCodeAuthorization( + final CodeDelegation authorization = + new CodeDelegation( BigInteger.ONE, Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), - Optional.empty(), + 0, SIGNATURE_ALGORITHM .get() .createSignature( @@ -86,23 +85,23 @@ class SetCodeTransactionEncoderTest { "25b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031", 16), (byte) 1)); - SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output); + CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output); assertThat(output.encoded()) .isEqualTo( Bytes.fromHexString( - "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031")); + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa568001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031")); } @Test - void shouldEncodeSingleSetCodeWithoutNonceAndChainIdZero() { + void shouldEncodeSingleCodeDelegationWithChainIdZero() { // "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" - final SetCodeAuthorization authorization = - new SetCodeAuthorization( + final CodeDelegation authorization = + new CodeDelegation( BigInteger.ZERO, Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), - Optional.empty(), + 5, SIGNATURE_ALGORITHM .get() .createSignature( @@ -112,11 +111,11 @@ class SetCodeTransactionEncoderTest { "3c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df", 16), (byte) 1)); - SetCodeTransactionEncoder.encodeSingleSetCode(authorization, output); + CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output); assertThat(output.encoded()) .isEqualTo( Bytes.fromHexString( - "0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c001a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df")); + "0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa560501a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df")); } } 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 b2f5718da..d8d9f3777 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 @@ -89,7 +89,7 @@ class MainnetTransactionProcessorTest { MAX_STACK_SIZE, FeeMarket.legacy(), CoinbaseFeePriceCalculator.frontier(), - new AuthorityProcessor(Optional.of(BigInteger.ONE))); + new CodeDelegationProcessor(Optional.of(BigInteger.ONE))); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java index 388a11269..939e3ec0c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java @@ -181,7 +181,8 @@ public abstract class AbstractIsolationTests { mock(TransactionBroadcaster.class), ethContext, txPoolMetrics, - poolConfiguration); + poolConfiguration, + new BlobCache()); transactionPool.setEnabled(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java index 50cc23977..3d3a435f1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java @@ -87,4 +87,8 @@ public class BlobCache { return Optional.empty(); } } + + public BlobsWithCommitments.BlobQuad get(final VersionedHash vh) { + return cache.getIfPresent(vh); + } } 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 bd98bee5a..91b4efd7b 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 @@ -31,8 +31,8 @@ import java.util.concurrent.atomic.AtomicLong; public abstract class PendingTransaction implements org.hyperledger.besu.datatypes.PendingTransaction { static final int NOT_INITIALIZED = -1; - static final int FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE = 888; - static final int EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE = 1000; + static final int FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE = 904; + static final int EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE = 1016; static final int OPTIONAL_TO_MEMORY_SIZE = 112; static final int OPTIONAL_CHAIN_ID_MEMORY_SIZE = 80; static final int PAYLOAD_BASE_MEMORY_SIZE = 32; @@ -147,7 +147,7 @@ public abstract class PendingTransaction case ACCESS_LIST -> computeAccessListMemorySize(); case EIP1559 -> computeEIP1559MemorySize(); case BLOB -> computeBlobMemorySize(); - case SET_CODE -> computeSetCodeMemorySize(); + case DELEGATE_CODE -> computeSetCodeMemorySize(); } + PENDING_TRANSACTION_MEMORY_SIZE; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java index 6bb202996..315f82921 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java @@ -20,8 +20,10 @@ import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.TRANSACTION_ALREADY_KNOWN; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; @@ -55,6 +57,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.IntSummaryStatistics; import java.util.List; import java.util.Map; @@ -89,6 +92,7 @@ public class TransactionPool implements BlockAddedObserver { private static final Logger LOG = LoggerFactory.getLogger(TransactionPool.class); private static final Logger LOG_FOR_REPLAY = LoggerFactory.getLogger("LOG_FOR_REPLAY"); private final Supplier pendingTransactionsSupplier; + private final BlobCache cacheForBlobsOfTransactionsAddedToABlock; private volatile PendingTransactions pendingTransactions = new DisabledPendingTransactions(); private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; @@ -103,6 +107,8 @@ public class TransactionPool implements BlockAddedObserver { private final SaveRestoreManager saveRestoreManager = new SaveRestoreManager(); private final Set
localSenders = ConcurrentHashMap.newKeySet(); private final EthScheduler.OrderedProcessor blockAddedEventOrderedProcessor; + private final Map mapOfBlobsInTransactionPool = + new HashMap<>(); public TransactionPool( final Supplier pendingTransactionsSupplier, @@ -111,7 +117,8 @@ public class TransactionPool implements BlockAddedObserver { final TransactionBroadcaster transactionBroadcaster, final EthContext ethContext, final TransactionPoolMetrics metrics, - final TransactionPoolConfiguration configuration) { + final TransactionPoolConfiguration configuration, + final BlobCache blobCache) { this.pendingTransactionsSupplier = pendingTransactionsSupplier; this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; @@ -121,7 +128,10 @@ public class TransactionPool implements BlockAddedObserver { this.configuration = configuration; this.blockAddedEventOrderedProcessor = ethContext.getScheduler().createOrderedProcessor(this::processBlockAddedEvent); + this.cacheForBlobsOfTransactionsAddedToABlock = blobCache; initLogForReplay(); + subscribePendingTransactions(this::mapBlobsOnTransactionAdded); + subscribeDroppedTransactions(this::unmapBlobsOnTransactionDropped); } private void initLogForReplay() { @@ -640,6 +650,38 @@ public class TransactionPool implements BlockAddedObserver { return CompletableFuture.completedFuture(null); } + private void mapBlobsOnTransactionAdded( + final org.hyperledger.besu.datatypes.Transaction transaction) { + final Optional maybeBlobsWithCommitments = + transaction.getBlobsWithCommitments(); + if (maybeBlobsWithCommitments.isEmpty()) { + return; + } + final List blobQuads = + maybeBlobsWithCommitments.get().getBlobQuads(); + blobQuads.forEach(bq -> mapOfBlobsInTransactionPool.put(bq.versionedHash(), bq)); + } + + private void unmapBlobsOnTransactionDropped( + final org.hyperledger.besu.datatypes.Transaction transaction) { + final Optional maybeBlobsWithCommitments = + transaction.getBlobsWithCommitments(); + if (maybeBlobsWithCommitments.isEmpty()) { + return; + } + final List blobQuads = + maybeBlobsWithCommitments.get().getBlobQuads(); + blobQuads.forEach(bq -> mapOfBlobsInTransactionPool.remove(bq.versionedHash())); + } + + public BlobsWithCommitments.BlobQuad getBlobQuad(final VersionedHash vh) { + BlobsWithCommitments.BlobQuad blobQuad = mapOfBlobsInTransactionPool.get(vh); + if (blobQuad == null) { + blobQuad = cacheForBlobsOfTransactionsAddedToABlock.get(vh); + } + return blobQuad; + } + public boolean isEnabled() { return isPoolEnabled.get(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java index 18a8598ab..2f823830f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java @@ -80,6 +80,7 @@ public interface TransactionPoolConfiguration { Implementation DEFAULT_TX_POOL_IMPLEMENTATION = Implementation.LAYERED; Set
DEFAULT_PRIORITY_SENDERS = Set.of(); Wei DEFAULT_TX_POOL_MIN_GAS_PRICE = Wei.of(1000); + byte DEFAULT_TX_POOL_MIN_SCORE = -128; TransactionPoolConfiguration DEFAULT = ImmutableTransactionPoolConfiguration.builder().build(); @@ -173,6 +174,11 @@ public interface TransactionPoolConfiguration { return DEFAULT_TX_POOL_MIN_GAS_PRICE; } + @Value.Default + default byte getMinScore() { + return DEFAULT_TX_POOL_MIN_SCORE; + } + @Value.Default default TransactionPoolValidatorService getTransactionPoolValidatorService() { return new TransactionPoolValidatorService() { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java index d32b7bafe..79b1298d2 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java @@ -118,7 +118,8 @@ public class TransactionPoolFactory { newPooledTransactionHashesMessageSender), ethContext, metrics, - transactionPoolConfiguration); + transactionPoolConfiguration, + blobCache); final TransactionsMessageHandler transactionsMessageHandler = new TransactionsMessageHandler( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java index 90e9628e5..e08805551 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.eth.transactions; import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.metrics.ReplaceableDoubleSupplier; @@ -68,6 +69,7 @@ public class TransactionPoolMetrics { "Count of transactions added to the transaction pool", "source", "priority", + "reason", "layer"); removedCounter = @@ -215,11 +217,13 @@ public class TransactionPoolMetrics { SKIPPED_MESSAGES_LOGGING_THRESHOLD)); } - public void incrementAdded(final PendingTransaction pendingTransaction, final String layer) { + public void incrementAdded( + final PendingTransaction pendingTransaction, final AddReason addReason, final String layer) { addedCounter .labels( location(pendingTransaction.isReceivedFromLocalSource()), priority(pendingTransaction.hasPriority()), + addReason.label(), layer) .inc(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java index 619611edd..f7d71ca37 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java @@ -14,8 +14,9 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.EVICTED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.FOLLOW_INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.EVICTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.FOLLOW_INVALIDATED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; @@ -23,6 +24,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import java.util.Map; import java.util.NavigableMap; @@ -44,7 +46,7 @@ public abstract class AbstractSequentialTransactionsLayer extends AbstractTransa } @Override - public void remove(final PendingTransaction invalidatedTx, final RemovalReason reason) { + public void remove(final PendingTransaction invalidatedTx, final PoolRemovalReason reason) { nextLayer.remove(invalidatedTx, reason); final var senderTxs = txsBySender.get(invalidatedTx.getSender()); @@ -77,7 +79,7 @@ public abstract class AbstractSequentialTransactionsLayer extends AbstractTransa senderTxs.remove(txToRemove.getNonce()); processRemove(senderTxs, txToRemove.getTransaction(), FOLLOW_INVALIDATED); }) - .forEach(followingTx -> nextLayer.add(followingTx, gap)); + .forEach(followingTx -> nextLayer.add(followingTx, gap, MOVE)); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java index b4f6e927c..ca980b219 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java @@ -19,11 +19,13 @@ import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedRes import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.REJECTED_UNDERPRICED_REPLACEMENT; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.TRY_NEXT_LAYER; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.CONFIRMED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.CROSS_LAYER_REPLACED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.EVICTED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.PROMOTED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.EVICTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.BELOW_MIN_SCORE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.CONFIRMED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.CROSS_LAYER_REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.RemovedFrom.POOL; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -169,7 +171,8 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { final PendingTransaction pendingTransaction, final int gap); @Override - public TransactionAddedResult add(final PendingTransaction pendingTransaction, final int gap) { + public TransactionAddedResult add( + final PendingTransaction pendingTransaction, final int gap, final AddReason addReason) { // is replacing an existing one? TransactionAddedResult addStatus = maybeReplaceTransaction(pendingTransaction); @@ -178,21 +181,26 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { } if (addStatus.equals(TRY_NEXT_LAYER)) { - return addToNextLayer(pendingTransaction, gap); + return addToNextLayer(pendingTransaction, gap, addReason); } if (addStatus.isSuccess()) { - processAdded(pendingTransaction.detachedCopy()); + final var addedPendingTransaction = + addReason.makeCopy() ? pendingTransaction.detachedCopy() : pendingTransaction; + processAdded(addedPendingTransaction, addReason); addStatus.maybeReplacedTransaction().ifPresent(this::replaced); - nextLayer.notifyAdded(pendingTransaction); + nextLayer.notifyAdded(addedPendingTransaction); if (!maybeFull()) { // if there is space try to see if the added tx filled some gaps - tryFillGap(addStatus, pendingTransaction, getRemainingPromotionsPerType()); + tryFillGap(addStatus, addedPendingTransaction, getRemainingPromotionsPerType()); + } + + if (addReason.sendNotification()) { + ethScheduler.scheduleTxWorkerTask(() -> notifyTransactionAdded(addedPendingTransaction)); } - ethScheduler.scheduleTxWorkerTask(() -> notifyTransactionAdded(pendingTransaction)); } else { final var rejectReason = addStatus.maybeInvalidReason().orElseThrow(); metrics.incrementRejected(pendingTransaction, rejectReason, name()); @@ -238,7 +246,7 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { pendingTransaction.getNonce(), remainingPromotionsPerType); if (promotedTx != null) { - processAdded(promotedTx); + processAdded(promotedTx, AddReason.PROMOTED); if (!maybeFull()) { tryFillGap(ADDED, promotedTx, remainingPromotionsPerType); } @@ -286,7 +294,8 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { if (remainingPromotionsPerType[txType.ordinal()] > 0) { senderTxs.pollFirstEntry(); - processRemove(senderTxs, candidateTx.getTransaction(), PROMOTED); + processRemove( + senderTxs, candidateTx.getTransaction(), RemovalReason.LayerMoveReason.PROMOTED); metrics.incrementRemoved(candidateTx, "promoted", name()); if (senderTxs.isEmpty()) { @@ -302,32 +311,34 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { } private TransactionAddedResult addToNextLayer( - final PendingTransaction pendingTransaction, final int distance) { + final PendingTransaction pendingTransaction, final int distance, final AddReason addReason) { return addToNextLayer( txsBySender.getOrDefault(pendingTransaction.getSender(), EMPTY_SENDER_TXS), pendingTransaction, - distance); + distance, + addReason); } protected TransactionAddedResult addToNextLayer( final NavigableMap senderTxs, final PendingTransaction pendingTransaction, - final int distance) { + final int distance, + final AddReason addReason) { final int nextLayerDistance; if (senderTxs.isEmpty()) { nextLayerDistance = distance; } else { nextLayerDistance = (int) (pendingTransaction.getNonce() - (senderTxs.lastKey() + 1)); } - return nextLayer.add(pendingTransaction, nextLayerDistance); + return nextLayer.add(pendingTransaction, nextLayerDistance, addReason); } - private void processAdded(final PendingTransaction addedTx) { + private void processAdded(final PendingTransaction addedTx, final AddReason addReason) { pendingTransactions.put(addedTx.getHash(), addedTx); final var senderTxs = txsBySender.computeIfAbsent(addedTx.getSender(), s -> new TreeMap<>()); senderTxs.put(addedTx.getNonce(), addedTx); increaseCounters(addedTx); - metrics.incrementAdded(addedTx, name()); + metrics.incrementAdded(addedTx, addReason, name()); internalAdd(senderTxs, addedTx); } @@ -353,7 +364,7 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { ++evictedCount; evictedSize += lastTx.memorySize(); // evicted can always be added to the next layer - addToNextLayer(lessReadySenderTxs, lastTx, 0); + addToNextLayer(lessReadySenderTxs, lastTx, 0, MOVE); } if (lessReadySenderTxs.isEmpty()) { @@ -411,6 +422,9 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { decreaseCounters(removedTx); metrics.incrementRemoved(removedTx, removalReason.label(), name()); internalRemove(senderTxs, removedTx, removalReason); + if (removalReason.removedFrom().equals(POOL)) { + notifyTransactionDropped(removedTx); + } } return removedTx; } @@ -459,7 +473,7 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { nextLayer .promote( this::promotionFilter, cacheFreeSpace(), freeSlots, getRemainingPromotionsPerType()) - .forEach(this::processAdded); + .forEach(addedTx -> processAdded(addedTx, AddReason.PROMOTED)); } } @@ -468,6 +482,9 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { if (pendingTransactions.containsKey(penalizedTransaction.getHash())) { internalPenalize(penalizedTransaction); metrics.incrementPenalized(penalizedTransaction, name()); + if (penalizedTransaction.getScore() < poolConfig.getMinScore()) { + remove(penalizedTransaction, BELOW_MIN_SCORE); + } } else { nextLayer.penalize(penalizedTransaction); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AddReason.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AddReason.java new file mode 100644 index 000000000..895dd61e3 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AddReason.java @@ -0,0 +1,65 @@ +/* + * 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.eth.transactions.layered; + +import java.util.Locale; + +/** Describe why we are trying to add a tx to a layer. */ +public enum AddReason { + /** When adding a tx, that is not present in the pool. */ + NEW(true, true), + /** When adding a tx as result of an internal move between layers. */ + MOVE(false, false), + /** When adding a tx as result of a promotion from a lower layer. */ + PROMOTED(false, false); + + private final boolean sendNotification; + private final boolean makeCopy; + private final String label; + + AddReason(final boolean sendNotification, final boolean makeCopy) { + this.sendNotification = sendNotification; + this.makeCopy = makeCopy; + this.label = name().toLowerCase(Locale.ROOT); + } + + /** + * Should we send add notification for this reason? + * + * @return true if notification should be sent + */ + public boolean sendNotification() { + return sendNotification; + } + + /** + * Should the layer make a copy of the pending tx before adding it, to avoid keeping reference to + * potentially large underlying byte buffers? + * + * @return true is a copy is necessary + */ + public boolean makeCopy() { + return makeCopy; + } + + /** + * Return a label that identify this reason to be used in the metric system. + * + * @return a label + */ + public String label() { + return label; + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java index b3dec34b7..c06fb53e3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java @@ -14,7 +14,8 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.BELOW_BASE_FEE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.DEMOTED; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -104,7 +105,7 @@ public class BaseFeePrioritizedTransactions extends AbstractPrioritizedTransacti while (itTxsBySender.hasNext()) { final var senderTxs = itTxsBySender.next().getValue(); - Optional maybeFirstUnderpricedNonce = Optional.empty(); + Optional maybeFirstDemotedNonce = Optional.empty(); for (final var e : senderTxs.entrySet()) { final PendingTransaction tx = e.getValue(); @@ -114,26 +115,28 @@ public class BaseFeePrioritizedTransactions extends AbstractPrioritizedTransacti } else { // otherwise sender txs starting from this nonce need to be demoted to next layer, // and we can go to next sender - maybeFirstUnderpricedNonce = Optional.of(e.getKey()); + maybeFirstDemotedNonce = Optional.of(e.getKey()); break; } } - maybeFirstUnderpricedNonce.ifPresent( + maybeFirstDemotedNonce.ifPresent( nonce -> { - // demote all txs after the first underpriced to the next layer, because none of them is + // demote all txs after the first demoted to the next layer, because none of them is // executable now, and we can avoid sorting them until they are candidate for execution // again final var demoteTxs = senderTxs.tailMap(nonce, true); while (!demoteTxs.isEmpty()) { final PendingTransaction demoteTx = demoteTxs.pollLastEntry().getValue(); LOG.atTrace() - .setMessage("Demoting tx {} with max gas price below next block base fee {}") + .setMessage( + "Demoting tx {} since it does not respect anymore the requisites to stay in this layer." + + " Next block base fee {}") .addArgument(demoteTx::toTraceLog) .addArgument(newNextBlockBaseFee::toHumanReadableString) .log(); - processEvict(senderTxs, demoteTx, BELOW_BASE_FEE); - addToNextLayer(senderTxs, demoteTx, 0); + processEvict(senderTxs, demoteTx, DEMOTED); + addToNextLayer(senderTxs, demoteTx, 0, MOVE); } }); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java index f383f178c..c76d2ab97 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.DROPPED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.DROPPED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionAddedLis import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.util.Subscribers; @@ -75,7 +76,8 @@ public class EndLayer implements TransactionsLayer { } @Override - public TransactionAddedResult add(final PendingTransaction pendingTransaction, final int gap) { + public TransactionAddedResult add( + final PendingTransaction pendingTransaction, final int gap, final AddReason reason) { notifyTransactionDropped(pendingTransaction); metrics.incrementRemoved(pendingTransaction, DROPPED.label(), name()); ++droppedCount; @@ -83,7 +85,7 @@ public class EndLayer implements TransactionsLayer { } @Override - public void remove(final PendingTransaction pendingTransaction, final RemovalReason reason) {} + public void remove(final PendingTransaction pendingTransaction, final PoolRemovalReason reason) {} @Override public void penalize(final PendingTransaction penalizedTx) {} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java index 5297f0802..21c75b364 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java @@ -20,8 +20,9 @@ import static java.util.stream.Collectors.reducing; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.INTERNAL_ERROR; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.RECONCILED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.NEW; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.RECONCILED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -41,7 +42,6 @@ import org.hyperledger.besu.evm.account.AccountState; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -100,7 +100,7 @@ public class LayeredPendingTransactions implements PendingTransactions { } try { - return prioritizedTransactions.add(pendingTransaction, (int) nonceDistance); + return prioritizedTransactions.add(pendingTransaction, (int) nonceDistance, NEW); } catch (final Throwable throwable) { return reconcileAndRetryAdd( pendingTransaction, stateSenderNonce, (int) nonceDistance, throwable); @@ -123,7 +123,7 @@ public class LayeredPendingTransactions implements PendingTransactions { .log(); reconcileSender(pendingTransaction.getSender(), stateSenderNonce); try { - return prioritizedTransactions.add(pendingTransaction, nonceDistance); + return prioritizedTransactions.add(pendingTransaction, nonceDistance, NEW); } catch (final Throwable throwable2) { // the error should have been solved by the reconcile, logging at higher level now LOG.atWarn() @@ -210,7 +210,7 @@ public class LayeredPendingTransactions implements PendingTransactions { final long lowestNonce = reAddTxs.getFirst().getNonce(); final int newNonceDistance = (int) Math.max(0, lowestNonce - stateSenderNonce); - reAddTxs.forEach(ptx -> prioritizedTransactions.add(ptx, newNonceDistance)); + reAddTxs.forEach(ptx -> prioritizedTransactions.add(ptx, newNonceDistance, NEW)); } LOG.atDebug() @@ -315,8 +315,6 @@ public class LayeredPendingTransactions implements PendingTransactions { @Override public void selectTransactions(final PendingTransactions.TransactionSelector selector) { - final List invalidTransactions = new ArrayList<>(); - final List penalizedTransactions = new ArrayList<>(); final Set
skipSenders = new HashSet<>(); final Map> candidateTxsByScore; @@ -346,12 +344,22 @@ public class LayeredPendingTransactions implements PendingTransactions { .log(); if (selectionResult.discard()) { - invalidTransactions.add(candidatePendingTx); + ethScheduler.scheduleTxWorkerTask( + () -> { + synchronized (this) { + prioritizedTransactions.remove(candidatePendingTx, INVALIDATED); + } + }); logDiscardedTransaction(candidatePendingTx, selectionResult); } if (selectionResult.penalize()) { - penalizedTransactions.add(candidatePendingTx); + ethScheduler.scheduleTxWorkerTask( + () -> { + synchronized (this) { + prioritizedTransactions.penalize(candidatePendingTx); + } + }); LOG.atTrace() .setMessage("Transaction {} penalized") .addArgument(candidatePendingTx::toTraceLog) @@ -374,22 +382,6 @@ public class LayeredPendingTransactions implements PendingTransactions { } } } - - ethScheduler.scheduleTxWorkerTask( - () -> { - invalidTransactions.forEach( - invalidTx -> { - synchronized (this) { - prioritizedTransactions.remove(invalidTx, INVALIDATED); - } - }); - penalizedTransactions.forEach( - penalizedTx -> { - synchronized (this) { - prioritizedTransactions.internalPenalize(penalizedTx); - } - }); - }); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java index 0f52e1c5c..7c7ddcdae 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.PROMOTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.PROMOTED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/RemovalReason.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/RemovalReason.java new file mode 100644 index 000000000..8acd026b1 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/RemovalReason.java @@ -0,0 +1,136 @@ +/* + * 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.eth.transactions.layered; + +import java.util.Locale; + +/** The reason why a pending tx has been removed */ +public interface RemovalReason { + /** + * From where the tx has been removed + * + * @return removed from item + */ + RemovedFrom removedFrom(); + + /** + * Return a label that identify this reason to be used in the metric system. + * + * @return a label + */ + String label(); + + /** There are 2 kinds of removals, from a layer and from the pool. */ + enum RemovedFrom { + /** + * Removing from a layer, can be also seen as a move between layers, since it is removed + * from the current layer and added to another layer, for example in the case the layer is full + * and some txs need to be moved to the next layer, or in the opposite case when some txs are + * promoted to the upper layer. + */ + LAYER, + /** + * Removing from the pool, instead means that the tx is directly removed from the pool, and it + * will not be present in any layer, for example, when it is added to an imported block, or it + * is replaced by another tx. + */ + POOL + } + + /** The reason why the tx has been removed from the pool */ + enum PoolRemovalReason implements RemovalReason { + /** Tx removed since it is confirmed on chain, as part of an imported block. */ + CONFIRMED, + /** Tx removed since it has been replaced by another one added in the same layer. */ + REPLACED, + /** Tx removed since it has been replaced by another one added in another layer. */ + CROSS_LAYER_REPLACED, + /** Tx removed when the pool is full, to make space for new incoming txs. */ + DROPPED, + /** + * Tx removed since found invalid after it was added to the pool, for example during txs + * selection for a new block proposal. + */ + INVALIDATED, + /** + * Special case, when for a sender, discrepancies are found between the world state view and the + * pool view, then all the txs for this sender are removed and added again. Discrepancies, are + * rare, and can happen during a short windows when a new block is being imported and the world + * state being updated. + */ + RECONCILED, + /** + * When a pending tx is penalized its score is decreased, if at some point its score is lower + * than the configured minimum then the pending tx is removed from the pool. + */ + BELOW_MIN_SCORE; + + private final String label; + + PoolRemovalReason() { + this.label = name().toLowerCase(Locale.ROOT); + } + + @Override + public RemovedFrom removedFrom() { + return RemovedFrom.POOL; + } + + @Override + public String label() { + return label; + } + } + + /** The reason why the tx has been moved across layers */ + enum LayerMoveReason implements RemovalReason { + /** + * When the current layer is full, and this tx needs to be moved to the lower layer, in order to + * free space. + */ + EVICTED, + /** + * Specific to sequential layers, when a tx is removed because found invalid, then if the sender + * has other txs with higher nonce, then a gap is created, and since sequential layers do not + * permit gaps, txs following the invalid one need to be moved to lower layers. + */ + FOLLOW_INVALIDATED, + /** + * When a tx is moved to the upper layer, since it satisfies all the requirement to be promoted. + */ + PROMOTED, + /** + * When a tx is moved to the lower layer, since it, or a preceding one from the same sender, + * does not respect anymore the requisites to stay in this layer. + */ + DEMOTED; + + private final String label; + + LayerMoveReason() { + this.label = name().toLowerCase(Locale.ROOT); + } + + @Override + public RemovedFrom removedFrom() { + return RemovedFrom.LAYER; + } + + @Override + public String label() { + return label; + } + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java index aef318e6d..7a20f9454 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java @@ -14,8 +14,8 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.PROMOTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.PROMOTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import java.util.ArrayList; @@ -225,7 +226,7 @@ public class SparseTransactions extends AbstractTransactionsLayer { @Override public synchronized void remove( - final PendingTransaction invalidatedTx, final RemovalReason reason) { + final PendingTransaction invalidatedTx, final PoolRemovalReason reason) { final var senderTxs = txsBySender.get(invalidatedTx.getSender()); if (senderTxs != null && senderTxs.containsKey(invalidatedTx.getNonce())) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java index 531add0af..16ce957fb 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java @@ -22,10 +22,10 @@ import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionAddedListener; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.OptionalLong; @@ -41,9 +41,27 @@ public interface TransactionsLayer { boolean contains(Transaction transaction); - TransactionAddedResult add(PendingTransaction pendingTransaction, int gap); + /** + * Try to add a pending transaction to this layer. The {@code addReason} is used to discriminate + * between a new tx that is added to the pool, or a tx that is already in the pool, but is moving + * internally between layers, for example, due to a promotion or demotion. The distinction is + * needed since we only need to send a notification for a new tx, and not when it is only an + * internal move. + * + * @param pendingTransaction the tx to try to add to this layer + * @param gap the nonce gap between the current sender nonce and the tx + * @param addReason define if it is a new tx or an internal move + * @return the result of the add operation + */ + TransactionAddedResult add(PendingTransaction pendingTransaction, int gap, AddReason addReason); - void remove(PendingTransaction pendingTransaction, RemovalReason reason); + /** + * Remove the pending tx from the pool + * + * @param pendingTransaction the pending tx + * @param reason the reason it is removed from the pool + */ + void remove(PendingTransaction pendingTransaction, PoolRemovalReason reason); /** * Penalize a pending transaction. Penalization could be applied to notify the txpool that this @@ -107,27 +125,4 @@ public interface TransactionsLayer { String logStats(); String logSender(Address sender); - - enum RemovalReason { - CONFIRMED, - CROSS_LAYER_REPLACED, - EVICTED, - DROPPED, - FOLLOW_INVALIDATED, - INVALIDATED, - PROMOTED, - REPLACED, - RECONCILED, - BELOW_BASE_FEE; - - private final String label; - - RemovalReason() { - this.label = name().toLowerCase(Locale.ROOT); - } - - public String label() { - return label; - } - } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java index cd780c2dc..e5ec06fd3 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java @@ -293,7 +293,8 @@ public abstract class AbstractTransactionPoolTest { transactionBroadcaster, ethContext, new TransactionPoolMetrics(metricsSystem), - poolConfig); + poolConfig, + new BlobCache()); txPool.setEnabled(); return txPool; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java index d247ca1e8..6a0005eec 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java @@ -17,6 +17,7 @@ package org.hyperledger.besu.ethereum.eth.transactions.layered; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.DROPPED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.NEW; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; @@ -169,7 +170,7 @@ public abstract class AbstractPrioritizedTransactionsTestBase extends BaseTransa .mapToObj( i -> { final var lowPriceTx = lowValueTxSupplier.next(); - final var prioritizeResult = transactions.add(lowPriceTx, 0); + final var prioritizeResult = transactions.add(lowPriceTx, 0, NEW); assertThat(prioritizeResult).isEqualTo(ADDED); assertThat(evictCollector.getEvictedTransactions()).isEmpty(); @@ -180,7 +181,7 @@ public abstract class AbstractPrioritizedTransactionsTestBase extends BaseTransa assertThat(transactions.count()).isEqualTo(MAX_TRANSACTIONS); // This should kick the oldest tx with the low gas price out, namely the first one we added - final var highValuePrioRes = transactions.add(highValueTx, 0); + final var highValuePrioRes = transactions.add(highValueTx, 0, NEW); assertThat(highValuePrioRes).isEqualTo(ADDED); assertEvicted(expectedDroppedTx); @@ -195,7 +196,7 @@ public abstract class AbstractPrioritizedTransactionsTestBase extends BaseTransa } protected TransactionAddedResult prioritizeTransaction(final PendingTransaction tx) { - return transactions.add(tx, 0); + return transactions.add(tx, 0, NEW); } protected void assertTransactionPrioritized(final PendingTransaction tx) { 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 bbd4e7322..690ab02a9 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 @@ public class BaseTransactionPoolTest { final TransactionType txType = TransactionType.values()[randomizeTxType.nextInt(4)]; return switch (txType) { - case FRONTIER, ACCESS_LIST, EIP1559, SET_CODE -> + case FRONTIER, ACCESS_LIST, EIP1559, DELEGATE_CODE -> createTransaction(txType, nonce, maxGasPrice, payloadSize, keys); case BLOB -> createTransaction( @@ -258,9 +258,10 @@ public class BaseTransactionPoolTest { } } - protected long getAddedCount(final String source, final String priority, final String layer) { + protected long getAddedCount( + final String source, final String priority, final AddReason addReason, final String layer) { return metricsSystem.getCounterValue( - TransactionPoolMetrics.ADDED_COUNTER_NAME, source, priority, layer); + TransactionPoolMetrics.ADDED_COUNTER_NAME, source, priority, addReason.label(), layer); } protected long getRemovedCount( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java index 4b39f26cb..31cedb1d9 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java @@ -35,8 +35,9 @@ public class EvictCollectorLayer extends EndLayer { } @Override - public TransactionAddedResult add(final PendingTransaction pendingTransaction, final int gap) { - final var res = super.add(pendingTransaction, gap); + public TransactionAddedResult add( + final PendingTransaction pendingTransaction, final int gap, final AddReason addReason) { + final var res = super.add(pendingTransaction, gap, addReason); evictedTxs.add(pendingTransaction); return res; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java index b2f03ce17..4fa3fa727 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java @@ -20,8 +20,10 @@ import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedRes import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.REJECTED_UNDERPRICED_REPLACEMENT; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.DROPPED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.NEW; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.DROPPED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.REPLACED; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOB_PRICE_BELOW_CURRENT_MIN; @@ -207,14 +209,14 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { createRemotePendingTransaction(transaction0), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); pendingTransactions.addTransaction( createRemotePendingTransaction(transaction1), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(2); } @@ -270,12 +272,48 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { getRemovedCount( REMOTE, NO_PRIORITY, DROPPED.label(), smallLayers.evictedCollector.name())) .isEqualTo(1); + // before get evicted definitively, the tx moves to the lower layers, where it does not fix, + // until is discarded + assertThat(getAddedCount(REMOTE, NO_PRIORITY, MOVE, smallLayers.readyTransactions.name())) + .isEqualTo(1); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, MOVE, smallLayers.sparseTransactions.name())) + .isEqualTo(1); assertThat(smallLayers.evictedCollector.getEvictedTransactions()) .map(PendingTransaction::getTransaction) .contains(firstTxs.get(0)); verify(droppedListener).onTransactionDropped(firstTxs.get(0)); } + @Test + public void txsMovingToNextLayerWhenFirstIsFull() { + final List txs = new ArrayList<>(MAX_TRANSACTIONS + 1); + + pendingTransactions.subscribeDroppedTransactions(droppedListener); + + for (int i = 0; i < MAX_TRANSACTIONS + 1; i++) { + final Account sender = mock(Account.class); + when(sender.getNonce()).thenReturn((long) i); + final var tx = + createTransaction( + i, DEFAULT_BASE_FEE.add(i), SIGNATURE_ALGORITHM.get().generateKeyPair()); + pendingTransactions.addTransaction(createRemotePendingTransaction(tx), Optional.of(sender)); + txs.add(tx); + assertTransactionPending(pendingTransactions, tx); + } + + assertThat(pendingTransactions.size()).isEqualTo(MAX_TRANSACTIONS + 1); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isEqualTo(MAX_TRANSACTIONS + 1); + + // one tx moved to the ready layer since the prioritized was full + assertThat(getAddedCount(REMOTE, NO_PRIORITY, MOVE, layers.readyTransactions.name())) + .isEqualTo(1); + + // first tx is the lowest value one so it is the first to be moved to ready + assertThat(layers.readyTransactions.contains(txs.get(0))).isTrue(); + verifyNoInteractions(droppedListener); + } + @Test public void addTransactionForMultipleSenders() { final var transactionSenderA = createTransaction(0, KEYS1); @@ -574,7 +612,7 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { assertTransactionPending(pendingTransactions, transaction1b); assertTransactionPending(pendingTransactions, transaction2); assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(3); assertThat( getRemovedCount( @@ -612,7 +650,7 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { assertTransactionPending(pendingTransactions, independentTx); assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(replacedTxCount + 2); assertThat( getRemovedCount( @@ -660,9 +698,9 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { final int localDuplicateCount = replacedTxCount - remoteDuplicateCount; assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(remoteDuplicateCount + 1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(localDuplicateCount + 1); assertThat( getRemovedCount( @@ -814,18 +852,20 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { pendingTransactions.addTransaction( createLocalPendingTransaction(transaction0), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); assertThat( pendingTransactions.addTransaction( createRemotePendingTransaction(transaction0), Optional.empty())) .isEqualTo(ALREADY_KNOWN); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); } @Test @@ -833,8 +873,9 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { pendingTransactions.addTransaction( createRemotePendingTransaction(transaction0), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); assertThat( @@ -842,8 +883,9 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { createLocalPendingTransaction(transaction0), Optional.empty())) .isEqualTo(ALREADY_KNOWN); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); } 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 84ac759a7..b5503ba81 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 @@ -15,6 +15,8 @@ package org.hyperledger.besu.ethereum.eth.transactions.layered; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.awaitility.Awaitility.await; import static org.hyperledger.besu.datatypes.TransactionType.ACCESS_LIST; import static org.hyperledger.besu.datatypes.TransactionType.BLOB; import static org.hyperledger.besu.datatypes.TransactionType.EIP1559; @@ -25,7 +27,7 @@ import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest. import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S4; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.SP1; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.SP2; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -35,6 +37,7 @@ import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; @@ -45,17 +48,20 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolReplacementHandler; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; import java.util.OptionalLong; +import java.util.TreeMap; import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -68,12 +74,14 @@ public class LayersTest extends BaseTransactionPoolTest { private static final int MAX_FUTURE_FOR_SENDER = 10; private static final Wei BASE_FEE = Wei.ONE; private static final Wei MIN_GAS_PRICE = BASE_FEE; + private static final byte MIN_SCORE = 125; private static final TransactionPoolConfiguration DEFAULT_TX_POOL_CONFIG = ImmutableTransactionPoolConfiguration.builder() .maxPrioritizedTransactions(MAX_PRIO_TRANSACTIONS) .maxPrioritizedTransactionsByType(Map.of(BLOB, 1)) .maxFutureBySender(MAX_FUTURE_FOR_SENDER) + .minScore(MIN_SCORE) .pendingTransactionsLayerMaxCapacityBytes( new PendingTransaction.Remote( new BaseTransactionPoolTest().createEIP1559Transaction(0, KEYS1, 1)) @@ -86,6 +94,7 @@ public class LayersTest extends BaseTransactionPoolTest { .maxPrioritizedTransactions(MAX_PRIO_TRANSACTIONS) .maxPrioritizedTransactionsByType(Map.of(BLOB, 1)) .maxFutureBySender(MAX_FUTURE_FOR_SENDER) + .minScore(MIN_SCORE) .pendingTransactionsLayerMaxCapacityBytes( new PendingTransaction.Remote( new BaseTransactionPoolTest().createEIP4844Transaction(0, KEYS1, 1, 1)) @@ -156,7 +165,7 @@ public class LayersTest extends BaseTransactionPoolTest { @ParameterizedTest @MethodSource("providerMaxPrioritizedByType") void maxPrioritizedByType(final Scenario scenario) { - assertScenario(scenario, BLOB_TX_POOL_CONFIG); + assertScenario(scenario); } @ParameterizedTest @@ -166,54 +175,7 @@ public class LayersTest extends BaseTransactionPoolTest { } private void assertScenario(final Scenario scenario) { - assertScenario(scenario, DEFAULT_TX_POOL_CONFIG); - } - - private void assertScenario( - final Scenario scenario, final TransactionPoolConfiguration poolConfig) { - final TransactionPoolMetrics txPoolMetrics = new TransactionPoolMetrics(metricsSystem); - - final EvictCollectorLayer evictCollector = new EvictCollectorLayer(txPoolMetrics); - final EthScheduler ethScheduler = new EthScheduler(1, 4, 1, 1, new NoOpMetricsSystem()); - final SparseTransactions sparseTransactions = - new SparseTransactions( - poolConfig, - ethScheduler, - evictCollector, - txPoolMetrics, - (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), - new BlobCache()); - - final ReadyTransactions readyTransactions = - new ReadyTransactions( - poolConfig, - ethScheduler, - sparseTransactions, - txPoolMetrics, - (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), - new BlobCache()); - - final BaseFeePrioritizedTransactions prioritizedTransactions = - new BaseFeePrioritizedTransactions( - poolConfig, - LayersTest::mockBlockHeader, - ethScheduler, - readyTransactions, - txPoolMetrics, - (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), - FeeMarket.london(0L), - new BlobCache(), - MiningParameters.newDefault().setMinTransactionGasPrice(MIN_GAS_PRICE)); - - final LayeredPendingTransactions pendingTransactions = - new LayeredPendingTransactions(poolConfig, prioritizedTransactions, ethScheduler); - - scenario.execute( - pendingTransactions, - prioritizedTransactions, - readyTransactions, - sparseTransactions, - evictCollector); + scenario.run(); } static Stream providerAddTransactions() { @@ -452,7 +414,7 @@ public class LayersTest extends BaseTransactionPoolTest { .expectedReadyForSenders(S1, 0, S1, 1) .expectedSparseForSender(S3, 2) .addForSenders(S3, 1) - // ToDo: only S3[1] is prioritized because there is no space to try to fill gaps + // only S3[1] is prioritized because there is no space to try to fill gaps .expectedPrioritizedForSenders(S3, 0, S3, 1, S2, 0) .expectedReadyForSenders(S2, 1, S1, 0, S1, 1) .expectedSparseForSender(S3, 2) @@ -465,11 +427,11 @@ public class LayersTest extends BaseTransactionPoolTest { Arguments.of( new Scenario("replacement cross layer") .addForSenders(S2, 0, S3, 2, S1, 1, S2, 1, S3, 0, S1, 0, S3, 1) - // ToDo: only S3[1] is prioritized because there is no space to try to fill gaps + // only S3[1] is prioritized because there is no space to try to fill gaps .expectedPrioritizedForSenders(S3, 0, S3, 1, S2, 0) .expectedReadyForSenders(S2, 1, S1, 0, S1, 1) .expectedSparseForSender(S3, 2) - .addForSenders(S3, 2) // added in prioritized, but replacement in sparse + .replaceForSenders(S3, 2) // added in prioritized, but replacement in sparse .expectedPrioritizedForSenders(S3, 0, S3, 1, S3, 2) .expectedReadyForSenders(S2, 0, S2, 1, S1, 0) .expectedSparseForSender(S1, 1))); @@ -477,6 +439,8 @@ public class LayersTest extends BaseTransactionPoolTest { static Stream providerRemoveTransactions() { return Stream.of( + // when expected*ForSender(s) is not present, by default there is a check that the layers + // are empty Arguments.of(new Scenario("remove not existing").removeForSender(S1, 0)), Arguments.of(new Scenario("add/remove first").addForSender(S1, 0).removeForSender(S1, 0)), Arguments.of( @@ -1060,10 +1024,10 @@ public class LayersTest extends BaseTransactionPoolTest { .expectedNextNonceForSenders(S1, 3) .addForSender(S1, 3) .expectedPrioritizedForSender(S1, 2, 3) - .setAccountNonce(S1, 0) // rewind nonce due to reorg - .addForSender(S1, 0) - .expectedPrioritizedForSender(S1, 0) - .expectedSparseForSender(S1, 2, 3))); + .reorgForSenders(S1, 0) // rewind nonce due to reorg + .addForSender(S1, 0, 1) // re-add reorged txs + .expectedPrioritizedForSender(S1, 0, 1, 2) + .expectedReadyForSender(S1, 3))); } static Stream providerAsyncWorldStateUpdates() { @@ -1221,22 +1185,22 @@ public class LayersTest extends BaseTransactionPoolTest { static Stream providerMaxPrioritizedByType() { return Stream.of( Arguments.of( - new Scenario("first blob tx is prioritized") + new Scenario("first blob tx is prioritized", BLOB_TX_POOL_CONFIG) .addForSender(S1, BLOB, 0) .expectedPrioritizedForSender(S1, 0)), Arguments.of( - new Scenario("multiple senders only first blob tx is prioritized") + new Scenario("multiple senders only first blob tx is prioritized", BLOB_TX_POOL_CONFIG) .addForSender(S1, BLOB, 0) .addForSender(S2, BLOB, 0) .expectedPrioritizedForSender(S1, 0) .expectedReadyForSender(S2, 0)), Arguments.of( - new Scenario("same sender following blob txs are moved to ready") + new Scenario("same sender following blob txs are moved to ready", BLOB_TX_POOL_CONFIG) .addForSender(S1, BLOB, 0, 1, 2) .expectedPrioritizedForSender(S1, 0) .expectedReadyForSender(S1, 1, 2)), Arguments.of( - new Scenario("promoting txs respect prioritized count limit") + new Scenario("promoting txs respect prioritized count limit", BLOB_TX_POOL_CONFIG) .addForSender(S1, BLOB, 0, 1, 2) .expectedPrioritizedForSender(S1, 0) .expectedReadyForSender(S1, 1, 2) @@ -1244,14 +1208,14 @@ public class LayersTest extends BaseTransactionPoolTest { .expectedPrioritizedForSender(S1, 1) .expectedReadyForSender(S1, 2)), Arguments.of( - new Scenario("filling gaps respect prioritized count limit") + new Scenario("filling gaps respect prioritized count limit", BLOB_TX_POOL_CONFIG) .addForSender(S1, BLOB, 1) .expectedSparseForSender(S1, 1) .addForSender(S1, BLOB, 0) .expectedPrioritizedForSender(S1, 0) .expectedSparseForSender(S1, 1)), Arguments.of( - new Scenario("promoting to ready is unbounded") + new Scenario("promoting to ready is unbounded", BLOB_TX_POOL_CONFIG) .addForSender(S1, BLOB, 0, 1, 2, 3, 4, 5, 6) .expectedPrioritizedForSender(S1, 0) .expectedReadyForSender(S1, 1, 2, 3) @@ -1332,7 +1296,17 @@ public class LayersTest extends BaseTransactionPoolTest { .penalizeForSender(S2, 1) .addForSender(S2, 2) .expectedReadyForSenders(S1, 0, S1, 1, S2, 1) - .expectedSparseForSender(S2, 2))); + .expectedSparseForSender(S2, 2)), + Arguments.of( + new Scenario("remove below min score") + .addForSender(S1, 0) // score 127 + .expectedPrioritizedForSender(S1, 0) + .penalizeForSender(S1, 0) // score 126 + .expectedPrioritizedForSender(S1, 0) + .penalizeForSender(S1, 0) // score 125 + .expectedPrioritizedForSender(S1, 0) + .penalizeForSender(S1, 0) // score 124, removed since decreased score < MIN_SCORE + .expectedPrioritizedForSenders())); } private static BlockHeader mockBlockHeader() { @@ -1351,18 +1325,18 @@ public class LayersTest extends BaseTransactionPoolTest { return transactionReplacementHandler.shouldReplace(pt1, pt2, mockBlockHeader()); } - static class Scenario extends BaseTransactionPoolTest { - interface TransactionLayersConsumer { - void accept( - LayeredPendingTransactions pending, - AbstractPrioritizedTransactions prioritized, - ReadyTransactions ready, - SparseTransactions sparse, - EvictCollectorLayer dropped); - } + static class Scenario extends BaseTransactionPoolTest implements Runnable { final String description; - final List actions = new ArrayList<>(); + final TransactionPoolConfiguration poolConfig; + final EvictCollectorLayer dropped; + final SparseTransactions sparse; + final ReadyTransactions ready; + final AbstractPrioritizedTransactions prio; + final LayeredPendingTransactions pending; + + final NotificationsChecker notificationsChecker = new NotificationsChecker(); + final List actions = new ArrayList<>(); List lastExpectedPrioritized = new ArrayList<>(); List lastExpectedReady = new ArrayList<>(); List lastExpectedSparse = new ArrayList<>(); @@ -1374,94 +1348,281 @@ public class LayersTest extends BaseTransactionPoolTest { Arrays.stream(Sender.values()).forEach(e -> nonceBySender.put(e, 0L)); } - final EnumMap> txsBySender = new EnumMap<>(Sender.class); + final EnumSet sendersWithReorg = EnumSet.noneOf(Sender.class); + + final EnumMap> liveTxsBySender = + new EnumMap<>(Sender.class); { - Arrays.stream(Sender.values()).forEach(e -> txsBySender.put(e, new HashMap<>())); + Arrays.stream(Sender.values()).forEach(e -> liveTxsBySender.put(e, new TreeMap<>())); + } + + final EnumMap> droppedTxsBySender = + new EnumMap<>(Sender.class); + + { + Arrays.stream(Sender.values()).forEach(e -> droppedTxsBySender.put(e, new TreeMap<>())); } Scenario(final String description) { + this(description, DEFAULT_TX_POOL_CONFIG); + } + + Scenario(final String description, final TransactionPoolConfiguration poolConfig) { this.description = description; + this.poolConfig = poolConfig; + + final TransactionPoolMetrics txPoolMetrics = new TransactionPoolMetrics(metricsSystem); + + this.dropped = new EvictCollectorLayer(txPoolMetrics); + final EthScheduler ethScheduler = new DeterministicEthScheduler(); + this.sparse = + new SparseTransactions( + poolConfig, + ethScheduler, + this.dropped, + txPoolMetrics, + (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), + new BlobCache()); + + this.ready = + new ReadyTransactions( + poolConfig, + ethScheduler, + this.sparse, + txPoolMetrics, + (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), + new BlobCache()); + + this.prio = + new BaseFeePrioritizedTransactions( + poolConfig, + LayersTest::mockBlockHeader, + ethScheduler, + this.ready, + txPoolMetrics, + (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), + FeeMarket.london(0L), + new BlobCache(), + MiningParameters.newDefault().setMinTransactionGasPrice(MIN_GAS_PRICE)); + + this.pending = new LayeredPendingTransactions(poolConfig, this.prio, ethScheduler); + + this.pending.subscribePendingTransactions(notificationsChecker::collectAddNotification); + this.pending.subscribeDroppedTransactions(notificationsChecker::collectDropNotification); } - Scenario addForSender(final Sender sender, final long... nonce) { - return addForSender(sender, EIP1559, nonce); - } - - Scenario addForSender(final Sender sender, final TransactionType type, final long... nonce) { - Arrays.stream(nonce) - .forEach( - n -> { - final var pendingTx = getOrCreate(sender, type, n); - actions.add( - (pending, prio, ready, sparse, dropped) -> { - final Account mockSender = mock(Account.class); - when(mockSender.getNonce()).thenReturn(nonceBySender.get(sender)); - pending.addTransaction(pendingTx, Optional.of(mockSender)); - }); - }); - return this; - } - - Scenario addForSenders(final Object... args) { - for (int i = 0; i < args.length; i = i + 2) { - final Sender sender = (Sender) args[i]; - final long nonce = (int) args[i + 1]; - addForSender(sender, nonce); - } - return this; - } - - public Scenario confirmedForSenders(final Object... args) { - final Map maxConfirmedNonceBySender = new HashMap<>(); - for (int i = 0; i < args.length; i = i + 2) { - final Sender sender = (Sender) args[i]; - final long nonce = (int) args[i + 1]; - maxConfirmedNonceBySender.put(sender.address, nonce); - setAccountNonce(sender, nonce + 1); - } - actions.add( - (pending, prio, ready, sparse, dropped) -> - prio.blockAdded(FeeMarket.london(0L), mockBlockHeader(), maxConfirmedNonceBySender)); - return this; - } - - Scenario setAccountNonce(final Sender sender, final long nonce) { - actions.add((pending, prio, ready, sparse, dropped) -> nonceBySender.put(sender, nonce)); - return this; - } - - void execute( - final LayeredPendingTransactions pending, - final AbstractPrioritizedTransactions prioritized, - final ReadyTransactions ready, - final SparseTransactions sparse, - final EvictCollectorLayer dropped) { - actions.forEach(action -> action.accept(pending, prioritized, ready, sparse, dropped)); - assertExpectedPrioritized(prioritized, lastExpectedPrioritized); + @Override + public void run() { + actions.forEach(Runnable::run); + assertExpectedPrioritized(prio, lastExpectedPrioritized); assertExpectedReady(ready, lastExpectedReady); assertExpectedSparse(sparse, lastExpectedSparse); assertExpectedDropped(dropped, lastExpectedDropped); } - private PendingTransaction getOrCreate( - final Sender sender, final TransactionType type, final long nonce) { - return txsBySender - .get(sender) - .computeIfAbsent( - nonce, - n -> - switch (type) { - case FRONTIER -> createFrontierPendingTransaction(sender, n); - case ACCESS_LIST -> createAccessListPendingTransaction(sender, n); - case EIP1559 -> createEIP1559PendingTransaction(sender, n); - case BLOB -> createBlobPendingTransaction(sender, n); - case SET_CODE -> throw new UnsupportedOperationException(); + public Scenario addForSender(final Sender sender, final long... nonce) { + return addForSender(sender, EIP1559, nonce); + } + + public Scenario addForSender( + final Sender sender, final TransactionType type, final long... nonce) { + internalAddForSender(sender, type, nonce); + actions.add(notificationsChecker::assertExpectedNotifications); + return this; + } + + private void internalAddForSender( + final Sender sender, final TransactionType type, final long... nonce) { + actions.add( + () -> { + Arrays.stream(nonce) + .forEach( + n -> { + final var pendingTx = create(sender, type, n); + final Account mockSender = mock(Account.class); + when(mockSender.getNonce()).thenReturn(nonceBySender.get(sender)); + pending.addTransaction(pendingTx, Optional.of(mockSender)); + notificationsChecker.addExpectedAddNotification(pendingTx); + }); + + // reorg case + if (sendersWithReorg.contains(sender)) { + // reorg is removing and re-adding all sender txs, so assert notifications accordingly + final var currentPendingTxs = + liveTxsBySender.get(sender).tailMap(nonce[nonce.length - 1], false).values(); + currentPendingTxs.forEach( + pt -> { + notificationsChecker.addExpectedAddNotification(pt); + notificationsChecker.addExpectedDropNotification(pt); }); + sendersWithReorg.remove(sender); + } + + // reconciliation case + final var txsRemovedByReconciliation = + liveTxsBySender.get(sender).headMap(nonceBySender.get(sender), false).values(); + if (!txsRemovedByReconciliation.isEmpty()) { + // reconciliation is removing all sender txs, and re-adding only the ones with a + // larger nonce, so assert notifications accordingly + final var reconciledPendingTxs = + liveTxsBySender.get(sender).tailMap(nonce[nonce.length - 1], false).values(); + txsRemovedByReconciliation.forEach(notificationsChecker::addExpectedDropNotification); + reconciledPendingTxs.forEach( + pt -> { + notificationsChecker.addExpectedDropNotification(pt); + notificationsChecker.addExpectedAddNotification(pt); + }); + txsRemovedByReconciliation.clear(); + } + + handleDropped(); + }); + } + + private void handleDropped() { + // handle dropped tx due to layer or pool full + final var droppedTxs = dropped.getEvictedTransactions(); + droppedTxs.forEach(notificationsChecker::addExpectedDropNotification); + droppedTxs.stream() + .forEach( + pt -> { + liveTxsBySender.get(Sender.getByAddress(pt.getSender())).remove(pt.getNonce()); + droppedTxsBySender.get(Sender.getByAddress(pt.getSender())).put(pt.getNonce(), pt); + }); + } + + public Scenario addForSenders(final Object... args) { + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + internalAddForSender(sender, EIP1559, nonce); + } + actions.add(notificationsChecker::assertExpectedNotifications); + return this; + } + + public Scenario replaceForSender(final Sender sender, final long... nonce) { + internalReplaceForSender(sender, nonce); + actions.add(notificationsChecker::assertExpectedNotifications); + return this; + } + + private Scenario internalReplaceForSender(final Sender sender, final long... nonce) { + actions.add( + () -> { + Arrays.stream(nonce) + .forEach( + n -> { + final var maybeExistingTx = getMaybe(sender, n); + maybeExistingTx.ifPresentOrElse( + existingTx -> { + final var pendingTx = replace(sender, existingTx); + final Account mockSender = mock(Account.class); + when(mockSender.getNonce()).thenReturn(nonceBySender.get(sender)); + pending.addTransaction(pendingTx, Optional.of(mockSender)); + notificationsChecker.addExpectedAddNotification(pendingTx); + notificationsChecker.addExpectedDropNotification(existingTx); + }, + () -> + fail( + "Could not replace non-existing transaction with nonce " + + n + + " for sender " + + sender.name())); + }); + }); + return this; + } + + public Scenario replaceForSenders(final Object... args) { + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + internalReplaceForSender(sender, nonce); + } + actions.add(notificationsChecker::assertExpectedNotifications); + return this; + } + + public Scenario confirmedForSenders(final Object... args) { + actions.add( + () -> { + final Map maxConfirmedNonceBySender = new HashMap<>(); + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + maxConfirmedNonceBySender.put(sender.address, nonce); + nonceBySender.put(sender, nonce + 1); + for (final var pendingTx : getAll(sender)) { + if (pendingTx.getNonce() <= nonce) { + notificationsChecker.addExpectedDropNotification( + liveTxsBySender.get(sender).remove(pendingTx.getNonce())); + } + } + } + + prio.blockAdded(FeeMarket.london(0L), mockBlockHeader(), maxConfirmedNonceBySender); + notificationsChecker.assertExpectedNotifications(); + }); + return this; + } + + public Scenario setAccountNonce(final Sender sender, final long nonce) { + actions.add(() -> nonceBySender.put(sender, nonce)); + return this; + } + + public Scenario reorgForSenders(final Object... args) { + actions.add( + () -> { + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + nonceBySender.put(sender, nonce); + sendersWithReorg.add(sender); + } + }); + return this; + } + + private PendingTransaction create( + final Sender sender, final TransactionType type, final long nonce) { + if (liveTxsBySender.get(sender).containsKey(nonce)) { + fail( + "Transaction for sender " + sender.name() + " with nonce " + nonce + " already exists"); + } + final var newPendingTx = + switch (type) { + case FRONTIER -> createFrontierPendingTransaction(sender, nonce); + case ACCESS_LIST -> createAccessListPendingTransaction(sender, nonce); + case EIP1559 -> createEIP1559PendingTransaction(sender, nonce); + case BLOB -> createBlobPendingTransaction(sender, nonce); + case DELEGATE_CODE -> throw new UnsupportedOperationException(); + }; + liveTxsBySender.get(sender).put(nonce, newPendingTx); + return newPendingTx; + } + + private PendingTransaction replace(final Sender sender, final PendingTransaction pendingTx) { + final var replaceTx = + createRemotePendingTransaction( + createTransactionReplacement(pendingTx.getTransaction(), sender.key), + sender.hasPriority); + liveTxsBySender.get(sender).replace(pendingTx.getNonce(), replaceTx); + return replaceTx; + } + + private Optional getMaybe(final Sender sender, final long nonce) { + return Optional.ofNullable(liveTxsBySender.get(sender).get(nonce)); } private PendingTransaction get(final Sender sender, final long nonce) { - return txsBySender.get(sender).get(nonce); + return getMaybe(sender, nonce).get(); + } + + private List getAll(final Sender sender) { + return List.copyOf(liveTxsBySender.get(sender).values()); } private PendingTransaction createFrontierPendingTransaction( @@ -1489,102 +1650,114 @@ public class LayersTest extends BaseTransactionPoolTest { } public Scenario expectedPrioritizedForSender(final Sender sender, final long... nonce) { - lastExpectedPrioritized = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedPrioritized); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedPrioritized(prio, expectedCopy)); + () -> { + lastExpectedPrioritized = expectedForSender(sender, nonce); + assertExpectedPrioritized(prio, lastExpectedPrioritized); + }); return this; } public Scenario expectedReadyForSender(final Sender sender, final long... nonce) { - lastExpectedReady = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedReady); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedReady(ready, expectedCopy)); + () -> { + lastExpectedReady = expectedForSender(sender, nonce); + assertExpectedReady(ready, lastExpectedReady); + }); return this; } public Scenario expectedSparseForSender(final Sender sender, final long... nonce) { - lastExpectedSparse = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedSparse); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedSparse(sparse, expectedCopy)); + () -> { + lastExpectedSparse = expectedForSender(sender, nonce); + assertExpectedSparse(sparse, lastExpectedSparse); + }); return this; } public Scenario expectedDroppedForSender(final Sender sender, final long... nonce) { - lastExpectedDropped = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedDropped); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedDropped(dropped, expectedCopy)); + () -> { + lastExpectedDropped = droppedForSender(sender, nonce); + assertExpectedDropped(dropped, lastExpectedDropped); + }); return this; } public Scenario expectedPrioritizedForSenders( - final Sender sender1, final long nonce1, final Sender sender2, Object... args) { - lastExpectedPrioritized = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedPrioritized); + final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedPrioritized(prio, expectedCopy)); + () -> { + lastExpectedPrioritized = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedPrioritized(prio, lastExpectedPrioritized); + }); return this; } public Scenario expectedPrioritizedForSenders() { - lastExpectedPrioritized = List.of(); - final var expectedCopy = List.copyOf(lastExpectedPrioritized); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedPrioritized(prio, expectedCopy)); + () -> { + lastExpectedPrioritized = List.of(); + assertExpectedPrioritized(prio, lastExpectedPrioritized); + }); return this; } public Scenario expectedReadyForSenders( final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { - lastExpectedReady = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedReady); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedReady(ready, expectedCopy)); + () -> { + lastExpectedReady = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedReady(ready, lastExpectedReady); + }); return this; } public Scenario expectedReadyForSenders() { - lastExpectedReady = List.of(); - final var expectedCopy = List.copyOf(lastExpectedReady); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedReady(ready, expectedCopy)); + () -> { + lastExpectedReady = List.of(); + assertExpectedReady(ready, lastExpectedReady); + }); return this; } public Scenario expectedSparseForSenders( final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { - lastExpectedSparse = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedSparse); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedSparse(sparse, expectedCopy)); + () -> { + lastExpectedSparse = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedSparse(sparse, lastExpectedSparse); + }); return this; } public Scenario expectedSparseForSenders() { - lastExpectedSparse = List.of(); - final var expectedCopy = List.copyOf(lastExpectedSparse); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedSparse(sparse, expectedCopy)); + () -> { + lastExpectedSparse = List.of(); + assertExpectedSparse(sparse, lastExpectedSparse); + }); return this; } public Scenario expectedDroppedForSenders( final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { - lastExpectedDropped = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedDropped); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedDropped(dropped, expectedCopy)); + () -> { + lastExpectedDropped = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedDropped(dropped, lastExpectedDropped); + }); return this; } public Scenario expectedDroppedForSenders() { - lastExpectedDropped = List.of(); - final var expectedCopy = List.copyOf(lastExpectedDropped); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedDropped(dropped, expectedCopy)); + () -> { + lastExpectedDropped = List.of(); + assertExpectedDropped(dropped, lastExpectedDropped); + }); return this; } @@ -1639,59 +1812,74 @@ public class LayersTest extends BaseTransactionPoolTest { return Arrays.stream(nonce).mapToObj(n -> get(sender, n)).toList(); } + private List droppedForSender(final Sender sender, final long... nonce) { + return Arrays.stream(nonce).mapToObj(n -> droppedTxsBySender.get(sender).get(n)).toList(); + } + public Scenario expectedNextNonceForSenders(final Object... args) { for (int i = 0; i < args.length; i = i + 2) { final Sender sender = (Sender) args[i]; final Integer nullableInt = (Integer) args[i + 1]; final OptionalLong nonce = nullableInt == null ? OptionalLong.empty() : OptionalLong.of(nullableInt); - actions.add( - (pending, prio, ready, sparse, dropped) -> - assertThat(prio.getNextNonceFor(sender.address)).isEqualTo(nonce)); + actions.add(() -> assertThat(prio.getNextNonceFor(sender.address)).isEqualTo(nonce)); } return this; } public Scenario removeForSender(final Sender sender, final long... nonce) { - Arrays.stream(nonce) - .forEach( - n -> { - final var pendingTx = getOrCreate(sender, EIP1559, n); - actions.add( - (pending, prio, ready, sparse, dropped) -> prio.remove(pendingTx, INVALIDATED)); - }); + actions.add( + () -> { + Arrays.stream(nonce) + .forEach( + n -> { + final var maybeLiveTx = getMaybe(sender, n); + final var pendingTx = maybeLiveTx.orElseGet(() -> create(sender, EIP1559, n)); + prio.remove(pendingTx, INVALIDATED); + maybeLiveTx.ifPresent( + liveTx -> { + notificationsChecker.addExpectedDropNotification(liveTx); + liveTxsBySender.get(sender).remove(liveTx.getNonce()); + droppedTxsBySender.get(sender).put(liveTx.getNonce(), liveTx); + }); + }); + handleDropped(); + notificationsChecker.assertExpectedNotifications(); + }); return this; } public Scenario penalizeForSender(final Sender sender, final long... nonce) { - Arrays.stream(nonce) - .forEach( - n -> { - actions.add( - (pending, prio, ready, sparse, dropped) -> { - final var senderTxs = prio.getAllFor(sender.address); - Arrays.stream(nonce) - .mapToObj( - n2 -> senderTxs.stream().filter(pt -> pt.getNonce() == n2).findAny()) - .map(Optional::get) - .forEach(prio::penalize); - }); - }); + actions.add( + () -> + Arrays.stream(nonce) + .forEach( + n -> { + final var senderTxs = prio.getAllFor(sender.address); + Arrays.stream(nonce) + .mapToObj( + n2 -> + senderTxs.stream().filter(pt -> pt.getNonce() == n2).findAny()) + .map(Optional::get) + .forEach(prio::penalize); + })); return this; } public Scenario expectedSelectedTransactions(final Object... args) { - List expectedSelected = new ArrayList<>(); - for (int i = 0; i < args.length; i = i + 2) { - final Sender sender = (Sender) args[i]; - final long nonce = (int) args[i + 1]; - expectedSelected.add(get(sender, nonce)); - } actions.add( - (pending, prio, ready, sparse, dropped) -> - assertThat(prio.getBySender()) - .flatExtracting(SenderPendingTransactions::pendingTransactions) - .containsExactlyElementsOf(expectedSelected)); + () -> { + List expectedSelected = new ArrayList<>(); + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + expectedSelected.add(get(sender, nonce)); + } + + assertThat(prio.getBySender()) + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expectedSelected); + }); return this; } @@ -1721,6 +1909,62 @@ public class LayersTest extends BaseTransactionPoolTest { this.gasFeeMultiplier = gasFeeMultiplier; this.hasPriority = hasPriority; } + + static Sender getByAddress(final Address address) { + return Arrays.stream(values()).filter(s -> s.address.equals(address)).findAny().get(); + } + } + + static class NotificationsChecker { + private final List collectedAddNotifications = + Collections.synchronizedList(new ArrayList<>()); + private final List collectedDropNotifications = + Collections.synchronizedList(new ArrayList<>()); + private final List expectedAddNotifications = new ArrayList<>(); + private final List expectedDropNotifications = new ArrayList<>(); + + void collectAddNotification(final Transaction tx) { + collectedAddNotifications.add(tx); + } + + void collectDropNotification(final Transaction tx) { + collectedDropNotifications.add(tx); + } + + void addExpectedAddNotification(final PendingTransaction tx) { + expectedAddNotifications.add(tx.getTransaction()); + } + + void addExpectedDropNotification(final PendingTransaction tx) { + expectedDropNotifications.add(tx.getTransaction()); + } + + void assertExpectedNotifications() { + assertAddNotifications(expectedAddNotifications); + assertDropNotifications(expectedDropNotifications); + } + + private void assertAddNotifications(final List expectedAddedTxs) { + await() + .untilAsserted( + () -> + assertThat(collectedAddNotifications) + .describedAs("Added notifications") + .containsExactlyInAnyOrderElementsOf(expectedAddedTxs)); + collectedAddNotifications.clear(); + expectedAddNotifications.clear(); + } + + private void assertDropNotifications(final List expectedDroppedTxs) { + await() + .untilAsserted( + () -> + assertThat(collectedDropNotifications) + .describedAs("Dropped notifications") + .containsExactlyInAnyOrderElementsOf(expectedDroppedTxs)); + collectedDropNotifications.clear(); + expectedDropNotifications.clear(); + } } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java index 14ed39c2e..18ce6f49e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java @@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.eth.transactions.layered; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; 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 6f222166b..fb0ccf491 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 @@ -22,10 +22,12 @@ import static org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocol import org.hyperledger.besu.config.StubGenesisConfigOptions; 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.AccessListEntry; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.CodeDelegation; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; @@ -35,7 +37,6 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; 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; @@ -223,8 +224,7 @@ public class T8nExecutor { continue; } - List authorizations = - new ArrayList<>(authorizationList.size()); + List authorizations = new ArrayList<>(authorizationList.size()); for (JsonNode entryAsJson : authorizationList) { final BigInteger authorizationChainId = Bytes.fromHexStringLenient(entryAsJson.get("chainId").textValue()) @@ -232,26 +232,8 @@ public class T8nExecutor { 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 long authorizationNonce = + Bytes.fromHexStringLenient(entryAsJson.get("nonce").textValue()).toLong(); final byte authorizationV = Bytes.fromHexStringLenient(entryAsJson.get("v").textValue()) @@ -264,16 +246,17 @@ public class T8nExecutor { Bytes.fromHexStringLenient(entryAsJson.get("s").textValue()) .toUnsignedBigInteger(); + final SECPSignature authorizationSignature = + new SECPSignature(authorizationR, authorizationS, authorizationV); + authorizations.add( - SetCodeAuthorization.createSetCodeAuthorizationEntry( + new org.hyperledger.besu.ethereum.core.CodeDelegation( authorizationChainId, authorizationAddress, - authorizationNonces, - authorizationV, - authorizationR, - authorizationS)); + authorizationNonce, + authorizationSignature)); } - builder.setCodeTransactionPayloads(authorizations); + builder.codeDelegations(authorizations); } if (txNode.has("blobVersionedHashes")) { @@ -328,8 +311,8 @@ public class T8nExecutor { } else { out.printf("TX json node unparseable: %s%n", txNode); } - } catch (IllegalArgumentException iae) { - rejections.add(new RejectedTransaction(i, iae.getMessage())); + } catch (IllegalArgumentException | ArithmeticException e) { + rejections.add(new RejectedTransaction(i, e.getMessage())); } i++; } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java index e1d158f3f..e087edd43 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java @@ -381,10 +381,11 @@ public class EnodeURLImpl implements EnodeURL { return ipAddress(ip, EnodeDnsConfiguration.dnsDisabled()); } - public Builder ipAddress(final String ip, final EnodeDnsConfiguration enodeDnsConfiguration) { + public Builder ipAddress( + final String hostField, final EnodeDnsConfiguration enodeDnsConfiguration) { if (enodeDnsConfiguration.dnsEnabled()) { try { - this.ip = InetAddress.getByName(ip); + this.ip = InetAddress.getByName(hostField); if (enodeDnsConfiguration.updateEnabled()) { if (this.ip.isLoopbackAddress()) { this.ip = InetAddress.getLocalHost(); @@ -398,10 +399,10 @@ public class EnodeURLImpl implements EnodeURL { this.ip = InetAddresses.forString("127.0.0.1"); } } - } else if (InetAddresses.isUriInetAddress(ip)) { - this.ip = InetAddresses.forUriString(ip); - } else if (InetAddresses.isInetAddress(ip)) { - this.ip = InetAddresses.forString(ip); + } else if (InetAddresses.isUriInetAddress(hostField)) { + this.ip = InetAddresses.forUriString(hostField); + } else if (InetAddresses.isInetAddress(hostField)) { + this.ip = InetAddresses.forString(hostField); } else { throw new IllegalArgumentException("Invalid ip address."); } diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle index 426c1084d..22f22c70a 100644 --- a/ethereum/referencetests/build.gradle +++ b/ethereum/referencetests/build.gradle @@ -232,7 +232,7 @@ tasks.register('validateReferenceTestSubmodule') { description = "Checks that the reference tests submodule is not accidentally changed" doLast { def result = new ByteArrayOutputStream() - def expectedHash = 'faf33b471465d3c6cdc3d04fbd690895f78d33f2' + def expectedHash = '9201075490807f58811078e9bb5ec895b4ac01a5' def submodulePath = java.nio.file.Path.of("${rootProject.projectDir}", "ethereum/referencetests/src/reference-test/external-resources").toAbsolutePath() try { exec { diff --git a/ethereum/referencetests/src/reference-test/external-resources b/ethereum/referencetests/src/reference-test/external-resources index faf33b471..920107549 160000 --- a/ethereum/referencetests/src/reference-test/external-resources +++ b/ethereum/referencetests/src/reference-test/external-resources @@ -1 +1 @@ -Subproject commit faf33b471465d3c6cdc3d04fbd690895f78d33f2 +Subproject commit 9201075490807f58811078e9bb5ec895b4ac01a5 diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java index 3c0c2dbbb..6a736831f 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java @@ -79,27 +79,6 @@ public class EOFReferenceTestTools { if (EIPS_TO_RUN.isEmpty()) { params.ignoreAll(); } - - // TXCREATE still in tests, but has been removed - params.ignore("EOF1_undefined_opcodes_186"); - - // embedded containers rules changed - params.ignore("efValidation/EOF1_embedded_container-Prague\\[EOF1_embedded_container_\\d+\\]"); - - // truncated data is only allowed in embedded containers - params.ignore("ori/validInvalid-Prague\\[validInvalid_48\\]"); - params.ignore("efExample/validInvalid-Prague\\[validInvalid_1\\]"); - params.ignore("efValidation/EOF1_truncated_section-Prague\\[EOF1_truncated_section_3\\]"); - params.ignore("efValidation/EOF1_truncated_section-Prague\\[EOF1_truncated_section_4\\]"); - params.ignore("EIP3540/validInvalid-Prague\\[validInvalid_2\\]"); - params.ignore("EIP3540/validInvalid-Prague\\[validInvalid_3\\]"); - - // Orphan containers are no longer allowed - params.ignore("efValidation/EOF1_returncontract_valid-Prague\\[EOF1_returncontract_valid_1\\]"); - params.ignore("efValidation/EOF1_returncontract_valid-Prague\\[EOF1_returncontract_valid_2\\]"); - params.ignore("efValidation/EOF1_eofcreate_valid-Prague\\[EOF1_eofcreate_valid_1\\]"); - params.ignore("efValidation/EOF1_eofcreate_valid-Prague\\[EOF1_eofcreate_valid_2\\]"); - params.ignore("efValidation/EOF1_section_order-Prague\\[EOF1_section_order_6\\]"); } private EOFReferenceTestTools() { @@ -124,7 +103,7 @@ public class EOFReferenceTestTools { // hardwire in the magic byte transaction checks if (evm.getMaxEOFVersion() < 1) { assertThat(expected.exception()).isEqualTo("EOF_InvalidCode"); - } else if (code.size() > evm.getEvmVersion().getMaxInitcodeSize()) { + } else if (code.size() > evm.getMaxInitcodeSize()) { // this check is in EOFCREATE and Transaction validator, but unit tests sniff it out. assertThat(false) .withFailMessage( diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index f2eb7cf29..695751439 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -142,6 +142,24 @@ public class EVM { return evmSpecVersion.maxEofVersion; } + /** + * Gets the max code size, taking configuration and version into account + * + * @return The max code size override, if not set the max code size for the EVM version. + */ + public int getMaxCodeSize() { + return evmConfiguration.maxCodeSizeOverride().orElse(evmSpecVersion.maxCodeSize); + } + + /** + * Gets the max initcode Size, taking configuration and version into account + * + * @return The max initcode size override, if not set the max initcode size for the EVM version. + */ + public int getMaxInitcodeSize() { + return evmConfiguration.maxInitcodeSizeOverride().orElse(evmSpecVersion.maxInitcodeSize); + } + /** * Returns the non-fork related configuration parameters of the EVM. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java index 8f20eba62..8a65adf61 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java @@ -17,6 +17,10 @@ package org.hyperledger.besu.evm.account; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + /** * A world state account. * @@ -49,4 +53,31 @@ public interface Account extends AccountState { * is set. */ boolean isStorageEmpty(); + + /** + * Returns the address of the delegated code account if it has one. + * + * @return the address of the delegated code account if it has one otherwise empty. + */ + default Optional
delegatedCodeAddress() { + return Optional.empty(); + } + + /** + * Returns a boolean to indicate if the account has delegated code. + * + * @return true if the account has delegated code otherwise false. + */ + default boolean hasDelegatedCode() { + return false; + } + + /** + * Returns the code as it is stored in the trie even if it's a delegated code account. + * + * @return the code as it is stored in the trie. + */ + default Bytes getUnprocessedCode() { + return getCode(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java new file mode 100644 index 000000000..0e5219d83 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java @@ -0,0 +1,92 @@ +/* + * 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 org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +class BaseDelegatedCodeAccount { + private final WorldUpdater worldUpdater; + + /** The address of the account that has delegated code to be loaded into it. */ + protected final Address delegatedCodeAddress; + + protected BaseDelegatedCodeAccount( + final WorldUpdater worldUpdater, final Address delegatedCodeAddress) { + this.worldUpdater = worldUpdater; + this.delegatedCodeAddress = delegatedCodeAddress; + } + + /** + * Returns the delegated code. + * + * @return the delegated code. + */ + protected Bytes getCode() { + return resolveDelegatedCode(); + } + + /** + * Returns the hash of the delegated code. + * + * @return the hash of the delegated code. + */ + protected Hash getCodeHash() { + final Bytes code = getCode(); + return (code == null || code.isEmpty()) ? Hash.EMPTY : Hash.hash(code); + } + + /** + * Returns the balance of the delegated account. + * + * @return the balance of the delegated account. + */ + protected Wei getDelegatedBalance() { + return getDelegatedAccount().map(Account::getBalance).orElse(Wei.ZERO); + } + + /** + * Returns the nonce of the delegated account. + * + * @return the nonce of the delegated account. + */ + protected long getDelegatedNonce() { + return getDelegatedAccount().map(Account::getNonce).orElse(Account.DEFAULT_NONCE); + } + + /** + * Returns the address of the delegated code. + * + * @return the address of the delegated code. + */ + protected Optional
delegatedCodeAddress() { + return Optional.of(delegatedCodeAddress); + } + + private Optional getDelegatedAccount() { + return Optional.ofNullable(worldUpdater.getAccount(delegatedCodeAddress)); + } + + private Bytes resolveDelegatedCode() { + + return getDelegatedAccount().map(Account::getUnprocessedCode).orElse(Bytes.EMPTY); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java similarity index 64% rename from evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java rename to evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java index 46acac74f..1eba364c1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/AuthorizedCodeAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java @@ -17,30 +17,33 @@ 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 org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.NavigableMap; +import java.util.Optional; 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; +/** Wraps an EOA account and includes delegated code to be run on behalf of it. */ +public class DelegatedCodeAccount extends BaseDelegatedCodeAccount implements Account { - /** The hash of the authorized code. */ - protected Hash codeHash = null; + private final Account wrappedAccount; /** * Creates a new AuthorizedCodeAccount. * - * @param wrappedAccount the account that has authorized code to be loaded into it. - * @param authorizedCode the authorized code. + * @param worldUpdater the world updater. + * @param wrappedAccount the account that has delegated code to be loaded into it. + * @param codeDelegationAddress the address of the delegated code. */ - public AuthorizedCodeAccount(final Account wrappedAccount, final Bytes authorizedCode) { + public DelegatedCodeAccount( + final WorldUpdater worldUpdater, + final Account wrappedAccount, + final Address codeDelegationAddress) { + super(worldUpdater, codeDelegationAddress); this.wrappedAccount = wrappedAccount; - this.authorizedCode = authorizedCode; } @Override @@ -53,6 +56,11 @@ public class AuthorizedCodeAccount implements Account { return wrappedAccount.isStorageEmpty(); } + @Override + public Optional
delegatedCodeAddress() { + return super.delegatedCodeAddress(); + } + @Override public Hash getAddressHash() { return wrappedAccount.getAddressHash(); @@ -70,16 +78,17 @@ public class AuthorizedCodeAccount implements Account { @Override public Bytes getCode() { - return authorizedCode; + return super.getCode(); + } + + @Override + public Bytes getUnprocessedCode() { + return wrappedAccount.getCode(); } @Override public Hash getCodeHash() { - if (codeHash == null) { - codeHash = authorizedCode.equals(Bytes.EMPTY) ? Hash.EMPTY : Hash.hash(authorizedCode); - } - - return codeHash; + return super.getCodeHash(); } @Override @@ -92,9 +101,24 @@ public class AuthorizedCodeAccount implements Account { return wrappedAccount.getOriginalStorageValue(key); } + @Override + public boolean isEmpty() { + return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode(); + } + + @Override + public boolean hasCode() { + return !getCode().isEmpty(); + } + @Override public NavigableMap storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { return wrappedAccount.storageEntriesFrom(startKeyHash, limit); } + + @Override + public boolean hasDelegatedCode() { + return true; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java similarity index 69% rename from evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java rename to evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java index 6d4e30a9c..0e1e1145d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableAuthorizedCodeAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java @@ -17,33 +17,35 @@ 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 org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Map; import java.util.NavigableMap; +import java.util.Optional; 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 { +/** Wraps an EOA account and includes delegated code to be run on behalf of it. */ +public class MutableDelegatedCodeAccount extends BaseDelegatedCodeAccount + 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. + * @param worldUpdater the world updater. + * @param wrappedAccount the account that has delegated code to be loaded into it. + * @param codeDelegationAddress the address of the delegated code. */ - public MutableAuthorizedCodeAccount( - final MutableAccount wrappedAccount, final Bytes authorizedCode) { + public MutableDelegatedCodeAccount( + final WorldUpdater worldUpdater, + final MutableAccount wrappedAccount, + final Address codeDelegationAddress) { + super(worldUpdater, codeDelegationAddress); this.wrappedAccount = wrappedAccount; - this.authorizedCode = authorizedCode; } @Override @@ -56,6 +58,11 @@ public class MutableAuthorizedCodeAccount implements MutableAccount { return wrappedAccount.isStorageEmpty(); } + @Override + public Optional
delegatedCodeAddress() { + return super.delegatedCodeAddress(); + } + @Override public Hash getAddressHash() { return wrappedAccount.getAddressHash(); @@ -73,16 +80,17 @@ public class MutableAuthorizedCodeAccount implements MutableAccount { @Override public Bytes getCode() { - return authorizedCode; + return super.getCode(); + } + + @Override + public Bytes getUnprocessedCode() { + return wrappedAccount.getCode(); } @Override public Hash getCodeHash() { - if (codeHash == null) { - codeHash = authorizedCode.equals(Bytes.EMPTY) ? Hash.EMPTY : Hash.hash(authorizedCode); - } - - return codeHash; + return super.getCodeHash(); } @Override @@ -95,6 +103,16 @@ public class MutableAuthorizedCodeAccount implements MutableAccount { return wrappedAccount.getOriginalStorageValue(key); } + @Override + public boolean isEmpty() { + return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode(); + } + + @Override + public boolean hasCode() { + return !getCode().isEmpty(); + } + @Override public NavigableMap storageEntriesFrom( final Bytes32 startKeyHash, final int limit) { @@ -113,7 +131,7 @@ public class MutableAuthorizedCodeAccount implements MutableAccount { @Override public void setCode(final Bytes code) { - throw new RuntimeException("Cannot set code on an AuthorizedCodeAccount"); + wrappedAccount.setCode(code); } @Override @@ -135,4 +153,9 @@ public class MutableAuthorizedCodeAccount implements MutableAccount { public void becomeImmutable() { wrappedAccount.becomeImmutable(); } + + @Override + public boolean hasDelegatedCode() { + return true; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java index 794650a13..9fd7cb801 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.util.Optional; @@ -75,16 +76,19 @@ public class MaxCodeSizeRule implements ContractValidationRule { * @return the contract validation rule */ public static ContractValidationRule from(final EVM evm) { - return from(evm.getEvmVersion()); + return from(evm.getEvmVersion(), evm.getEvmConfiguration()); } /** * Fluent MaxCodeSizeRule from the EVM it is working with. * * @param evmspec The evm spec version to get the size rules from. + * @param evmConfiguration The evm configuration, including overrides * @return the contract validation rule */ - public static ContractValidationRule from(final EvmSpecVersion evmspec) { - return new MaxCodeSizeRule(evmspec.getMaxCodeSize()); + public static ContractValidationRule from( + final EvmSpecVersion evmspec, final EvmConfiguration evmConfiguration) { + return new MaxCodeSizeRule( + evmConfiguration.maxCodeSizeOverride().orElse(evmspec.getMaxCodeSize())); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java index 3908e0dcb..9519fba70 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java @@ -77,7 +77,9 @@ public class EVMExecutor { private OperationTracer tracer = OperationTracer.NO_TRACING; private boolean requireDeposit = true; private List contractValidationRules = - List.of(MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON), PrefixCodeRule.of()); + List.of( + MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON, EvmConfiguration.DEFAULT), + PrefixCodeRule.of()); private long initialNonce = 1; private Collection
forceCommitAddresses = List.of(Address.fromHexString("0x03")); private Set
accessListWarmAddresses = new BytesTrieSet<>(Address.SIZE); @@ -242,8 +244,7 @@ public class EVMExecutor { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.spuriousDragon(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.frontier(executor.evm.getGasCalculator()); - executor.contractValidationRules = - List.of(MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -257,7 +258,7 @@ public class EVMExecutor { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.byzantium(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.byzantium(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.from(EvmSpecVersion.BYZANTIUM)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -271,7 +272,7 @@ public class EVMExecutor { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.constantinople(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.byzantium(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.from(EvmSpecVersion.CONSTANTINOPLE)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -285,7 +286,7 @@ public class EVMExecutor { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.petersburg(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.byzantium(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.from(EvmSpecVersion.PETERSBURG)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -320,7 +321,7 @@ public class EVMExecutor { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.istanbul(chainId, evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.istanbul(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.from(EvmSpecVersion.ISTANBUL)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -355,7 +356,7 @@ public class EVMExecutor { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.berlin(chainId, evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.istanbul(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.from(EvmSpecVersion.BERLIN)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java index f86bba699..6ad10d37e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java @@ -214,21 +214,4 @@ public class SimpleAccount implements MutableAccount { return false; } } - - /** - * Push changes into the parent account, if one exists - * - * @return true if a parent account was updated, false if not (this indicates the account should - * be inserted into the parent contact). - */ - public boolean updateParent() { - if (parent instanceof SimpleAccount simpleAccount) { - simpleAccount.balance = balance; - simpleAccount.nonce = nonce; - simpleAccount.storage.putAll(storage); - return true; - } else { - return false; - } - } } 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 f52e788ba..6d2437a88 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 @@ -29,10 +29,10 @@ import java.util.Optional; public class SimpleWorld implements WorldUpdater { /** The Parent. */ - SimpleWorld parent; + private final SimpleWorld parent; /** The Accounts. */ - Map accounts = new HashMap<>(); + private Map> accounts = new HashMap<>(); /** Instantiates a new Simple world. */ public SimpleWorld() { @@ -55,13 +55,15 @@ public class SimpleWorld implements WorldUpdater { @Override public Account get(final Address address) { + Optional account = Optional.empty(); if (accounts.containsKey(address)) { - return accounts.get(address); + account = accounts.get(address); } else if (parent != null) { - return parent.get(address); - } else { - return null; + if (parent.get(address) instanceof SimpleAccount accountFromParent) { + account = Optional.of(accountFromParent); + } } + return account.orElse(null); } @Override @@ -70,45 +72,46 @@ public class SimpleWorld implements WorldUpdater { throw new IllegalStateException("Cannot create an account when one already exists"); } SimpleAccount account = new SimpleAccount(address, nonce, balance); - accounts.put(address, account); + accounts.put(address, Optional.of(account)); return account; } @Override public MutableAccount getAccount(final Address address) { - SimpleAccount account = accounts.get(address); + Optional account = accounts.get(address); if (account != null) { - return account; + return account.orElse(null); } Account parentAccount = parent == null ? null : parent.getAccount(address); if (parentAccount != null) { account = - new SimpleAccount( - parentAccount, - parentAccount.getAddress(), - parentAccount.getNonce(), - parentAccount.getBalance(), - parentAccount.getCode()); + Optional.of( + new SimpleAccount( + parentAccount, + parentAccount.getAddress(), + parentAccount.getNonce(), + parentAccount.getBalance(), + parentAccount.getCode())); accounts.put(address, account); - return account; + return account.get(); } return null; } @Override public void deleteAccount(final Address address) { - accounts.put(address, null); + accounts.put(address, Optional.empty()); } @Override public Collection getTouchedAccounts() { - return accounts.values(); + return accounts.values().stream().filter(Optional::isPresent).map(Optional::get).toList(); } @Override public Collection
getDeletedAccountAddresses() { return accounts.entrySet().stream() - .filter(e -> e.getValue() == null) + .filter(e -> e.getValue().isEmpty()) .map(Map.Entry::getKey) .toList(); } @@ -122,8 +125,10 @@ public class SimpleWorld implements WorldUpdater { public void commit() { accounts.forEach( (address, account) -> { - if (!account.updateParent()) { - parent.accounts.put(address, account); + if (account.isEmpty() || !account.get().commit()) { + if (parent != null) { + parent.accounts.put(address, account); + } } }); } 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 0af023851..fd453deac 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 @@ -647,12 +647,33 @@ public interface GasCalculator { } /** - * Returns the upfront gas cost for EIP 7702 operation. + * Returns the upfront gas cost for EIP 7702 authorization processing. * - * @param authorizationListLength The length of the authorization list + * @param delegateCodeListLength The length of the code delegation list * @return the gas cost */ - default long setCodeListGasCost(final int authorizationListLength) { + default long delegateCodeGasCost(final int delegateCodeListLength) { + return 0L; + } + + /** + * Calculates the refund for proessing the 7702 code delegation list if an delegater account + * already exist in the trie. + * + * @param alreadyExistingAccountSize The number of accounts already in the trie + * @return the gas refund + */ + default long calculateDelegateCodeGasRefund(final long alreadyExistingAccountSize) { + return 0L; + } + + /** + * Returns the gas cost for resolving the code of a delegate account. + * + * @param isWarm whether the account is warm + * @return the gas cost + */ + default long delegatedCodeResolutionGasCost(final boolean isWarm) { 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 0c223d780..33fe98b99 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 @@ -16,19 +16,17 @@ package org.hyperledger.besu.evm.gascalculator; import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2; +import org.hyperledger.besu.datatypes.CodeDelegation; + /** * Gas Calculator for Prague * - *

Placeholder for new gas schedule items. If Prague finalzies without changes this can be - * removed - * *

    - *
  • TBD + *
  • Gas costs for EIP-7702 (Code Delegation) *
*/ public class PragueGasCalculator extends CancunGasCalculator { - - static final long PER_CONTRACT_CODE_BASE_COST = 2500L; + final long existingAccountGasRefund; /** Instantiates a new Prague Gas Calculator. */ public PragueGasCalculator() { @@ -42,10 +40,21 @@ public class PragueGasCalculator extends CancunGasCalculator { */ protected PragueGasCalculator(final int maxPrecompile) { super(maxPrecompile); + this.existingAccountGasRefund = newAccountGasCost() - CodeDelegation.PER_AUTH_BASE_COST; } @Override - public long setCodeListGasCost(final int authorizationListLength) { - return PER_CONTRACT_CODE_BASE_COST * authorizationListLength; + public long delegateCodeGasCost(final int delegateCodeListLength) { + return newAccountGasCost() * delegateCodeListLength; + } + + @Override + public long calculateDelegateCodeGasRefund(final long alreadyExistingAccounts) { + return existingAccountGasRefund * alreadyExistingAccounts; + } + + @Override + public long delegatedCodeResolutionGasCost(final boolean isWarm) { + return isWarm ? getWarmStorageReadCost() : getColdAccountAccessCost(); } } 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 12cb06eaa..932bb4c39 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 @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -26,6 +27,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.frame.MessageFrame.State; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; @@ -190,6 +192,15 @@ public abstract class AbstractCallOperation extends AbstractOperation { final Account contract = frame.getWorldUpdater().get(to); + if (contract != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), contract); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); final Wei balance = account == null ? Wei.ZERO : account.getBalance(); // If the call is sending more value than the account has or the message frame is to deep 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 3a78016f2..5d9bea61c 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 @@ -97,7 +97,7 @@ public abstract class AbstractCreateOperation extends AbstractOperation { Code code = codeSupplier.get(); - if (code != null && code.getSize() > evm.getEvmVersion().getMaxInitcodeSize()) { + if (code != null && code.getSize() > evm.getMaxInitcodeSize()) { frame.popStackItems(getStackItemsConsumed()); return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); } 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 7b58e956f..c82e8c163 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 @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; @@ -24,6 +26,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import javax.annotation.Nonnull; @@ -119,6 +122,16 @@ public abstract class AbstractExtCallOperation extends AbstractCallOperation { } Address to = Words.toAddress(toBytes); final Account contract = frame.getWorldUpdater().get(to); + + if (contract != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), contract); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + boolean accountCreation = contract == null && !zeroValue; long cost = gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java index 2f1c13918..ed1e46977 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java @@ -16,6 +16,7 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedAdd; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; @@ -25,6 +26,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; @@ -93,6 +95,16 @@ public class ExtCodeCopyOperation extends AbstractOperation { } final Account account = frame.getWorldUpdater().get(address); + + if (account != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), account); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + final Bytes code = account != null ? account.getCode() : Bytes.EMPTY; if (enableEIP3540 diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index c08331b00..9a8cfe838 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.EVM; @@ -25,6 +27,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; @@ -78,23 +81,34 @@ public class ExtCodeHashOperation extends AbstractOperation { final long cost = cost(accountIsWarm); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); - } else { - final Account account = frame.getWorldUpdater().get(address); - if (account == null || account.isEmpty()) { - frame.pushStackItem(Bytes.EMPTY); - } else { - final Bytes code = account.getCode(); - if (enableEIP3540 - && code.size() >= 2 - && code.get(0) == EOFLayout.EOF_PREFIX_BYTE - && code.get(1) == 0) { - frame.pushStackItem(EOF_REPLACEMENT_HASH); - } else { - frame.pushStackItem(account.getCodeHash()); - } - } - return new OperationResult(cost, null); } + + final Account account = frame.getWorldUpdater().get(address); + + if (account != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), account); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + + if (account == null || account.isEmpty()) { + frame.pushStackItem(Bytes.EMPTY); + } else { + final Bytes code = account.getCode(); + if (enableEIP3540 + && code.size() >= 2 + && code.get(0) == EOFLayout.EOF_PREFIX_BYTE + && code.get(1) == 0) { + frame.pushStackItem(EOF_REPLACEMENT_HASH); + } else { + frame.pushStackItem(account.getCodeHash()); + } + } + return new OperationResult(cost, null); + } catch (final UnderflowException ufe) { return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); } catch (final OverflowException ofe) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index 1779175f1..c2795f364 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; @@ -24,6 +26,7 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; @@ -78,6 +81,16 @@ public class ExtCodeSizeOperation extends AbstractOperation { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } else { final Account account = frame.getWorldUpdater().get(address); + + if (account != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), account); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + Bytes codeSize; if (account == null) { codeSize = Bytes.EMPTY; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java index 978958007..2860c9379 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java @@ -64,7 +64,7 @@ public class ReturnContractOperation extends AbstractOperation { } Bytes auxData = frame.readMemory(from, length); - if (code.getDataSize() + auxData.size() > evm.getEvmVersion().getMaxCodeSize()) { + if (code.getDataSize() + auxData.size() > evm.getMaxCodeSize()) { return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); } if (code.getDataSize() + auxData.size() < code.getDeclaredDataSize()) { 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 deleted file mode 100644 index ff5d94343..000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/AuthorizedCodeService.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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/DelegatedCodeGasCostHelper.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java new file mode 100644 index 000000000..fc6777031 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java @@ -0,0 +1,80 @@ +/* + * 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.evm.account.Account; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** + * Helper class to deduct gas cost for delegated code resolution. + * + *

Delegated code resolution is the process of determining the address of the contract that will + * be executed when a contract has delegated code. This process is necessary to determine the + * contract that will be executed and to ensure that the contract is warm in the cache. + */ +public class DelegatedCodeGasCostHelper { + + /** Private constructor to prevent instantiation. */ + private DelegatedCodeGasCostHelper() { + // empty constructor + } + + /** The status of the operation. */ + public enum Status { + /** The operation failed due to insufficient gas. */ + INSUFFICIENT_GAS, + /** The operation was successful. */ + SUCCESS + } + + /** + * The result of the operation. + * + * @param gasCost the gas cost + * @param status of the operation + */ + public record Result(long gasCost, Status status) {} + + /** + * Deducts the gas cost for delegated code resolution. + * + * @param frame the message frame + * @param gasCalculator the gas calculator + * @param account the account + * @return the gas cost and result of the operation + */ + public static Result deductDelegatedCodeGasCost( + final MessageFrame frame, final GasCalculator gasCalculator, final Account account) { + if (!account.hasDelegatedCode()) { + return new Result(0, Status.SUCCESS); + } + + if (account.delegatedCodeAddress().isEmpty()) { + throw new RuntimeException("A delegated code account must have a delegated code address"); + } + + final boolean delegatedCodeIsWarm = frame.warmUpAddress(account.delegatedCodeAddress().get()); + final long delegatedCodeResolutionGas = + gasCalculator.delegatedCodeResolutionGasCost(delegatedCodeIsWarm); + + if (frame.getRemainingGas() < delegatedCodeResolutionGas) { + return new Result(delegatedCodeResolutionGas, Status.INSUFFICIENT_GAS); + } + + frame.decrementRemainingGas(delegatedCodeResolutionGas); + return new Result(delegatedCodeResolutionGas, Status.SUCCESS); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java new file mode 100644 index 000000000..1c89fad8b --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java @@ -0,0 +1,97 @@ +/* + * 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.DelegatedCodeAccount; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.account.MutableDelegatedCodeAccount; + +import org.apache.tuweni.bytes.Bytes; + +/** A service that manages the code injection of delegated code. */ +public class DelegatedCodeService { + private static final Bytes DELEGATED_CODE_PREFIX = Bytes.fromHexString("ef0100"); + private static final int DELEGATED_CODE_SIZE = DELEGATED_CODE_PREFIX.size() + Address.SIZE; + + /** Creates a new DelegatedCodeService. */ + public DelegatedCodeService() {} + + /** + * Add the delegated code to the given account. + * + * @param account the account to which the delegated code is added. + * @param delegatedCodeAddress the address of the delegated code. + */ + public void addDelegatedCode(final MutableAccount account, final Address delegatedCodeAddress) { + account.setCode(Bytes.concatenate(DELEGATED_CODE_PREFIX, delegatedCodeAddress)); + } + + /** + * Returns if the provided account has either no code set or has already delegated code. + * + * @param account the account to check. + * @return {@code true} if the account can set delegated code, {@code false} otherwise. + */ + public boolean canSetDelegatedCode(final Account account) { + return account.getCode().isEmpty() || hasDelegatedCode(account.getUnprocessedCode()); + } + + /** + * Processes the provided account, resolving the code if delegated. + * + * @param worldUpdater the world updater to retrieve the delegated code. + * @param account the account to process. + * @return the processed account, containing the delegated code if set, the unmodified account + * otherwise. + */ + public Account processAccount(final WorldUpdater worldUpdater, final Account account) { + if (account == null || !hasDelegatedCode(account.getCode())) { + return account; + } + + return new DelegatedCodeAccount( + worldUpdater, account, resolveDelegatedAddress(account.getCode())); + } + + /** + * Processes the provided mutable account, resolving the code if delegated. + * + * @param worldUpdater the world updater to retrieve the delegated code. + * @param account the mutable account to process. + * @return the processed mutable account, containing the delegated code if set, the unmodified + * mutable account otherwise. + */ + public MutableAccount processMutableAccount( + final WorldUpdater worldUpdater, final MutableAccount account) { + if (account == null || !hasDelegatedCode(account.getCode())) { + return account; + } + + return new MutableDelegatedCodeAccount( + worldUpdater, account, resolveDelegatedAddress(account.getCode())); + } + + private Address resolveDelegatedAddress(final Bytes code) { + return Address.wrap(code.slice(DELEGATED_CODE_PREFIX.size())); + } + + private boolean hasDelegatedCode(final Bytes code) { + return code != null + && code.size() == DELEGATED_CODE_SIZE + && code.slice(0, DELEGATED_CODE_PREFIX.size()).equals(DELEGATED_CODE_PREFIX); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java index bac21e73f..928118acf 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java @@ -29,7 +29,7 @@ import java.util.Optional; */ public class EVMWorldUpdater implements WorldUpdater { private final WorldUpdater rootWorldUpdater; - private final AuthorizedCodeService authorizedCodeService; + private final DelegatedCodeService delegatedCodeService; /** * Instantiates a new EVM world updater. @@ -37,13 +37,13 @@ public class EVMWorldUpdater implements WorldUpdater { * @param rootWorldUpdater the root world updater */ public EVMWorldUpdater(final WorldUpdater rootWorldUpdater) { - this(rootWorldUpdater, new AuthorizedCodeService()); + this(rootWorldUpdater, new DelegatedCodeService()); } private EVMWorldUpdater( - final WorldUpdater rootWorldUpdater, final AuthorizedCodeService authorizedCodeService) { + final WorldUpdater rootWorldUpdater, final DelegatedCodeService delegatedCodeService) { this.rootWorldUpdater = rootWorldUpdater; - this.authorizedCodeService = authorizedCodeService; + this.delegatedCodeService = delegatedCodeService; } /** @@ -51,38 +51,36 @@ public class EVMWorldUpdater implements WorldUpdater { * * @return the authorized code service */ - public AuthorizedCodeService authorizedCodeService() { - return authorizedCodeService; + public DelegatedCodeService authorizedCodeService() { + return delegatedCodeService; } @Override public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { - return authorizedCodeService.processMutableAccount( - this, rootWorldUpdater.createAccount(address, nonce, balance), address); + return delegatedCodeService.processMutableAccount( + this, rootWorldUpdater.createAccount(address, nonce, balance)); } @Override public MutableAccount getAccount(final Address address) { - return authorizedCodeService.processMutableAccount( - this, rootWorldUpdater.getAccount(address), address); + return delegatedCodeService.processMutableAccount(this, rootWorldUpdater.getAccount(address)); } @Override public MutableAccount getOrCreate(final Address address) { - return authorizedCodeService.processMutableAccount( - this, rootWorldUpdater.getOrCreate(address), address); + return delegatedCodeService.processMutableAccount(this, rootWorldUpdater.getOrCreate(address)); } @Override public MutableAccount getOrCreateSenderAccount(final Address address) { - return authorizedCodeService.processMutableAccount( - this, rootWorldUpdater.getOrCreateSenderAccount(address), address); + return delegatedCodeService.processMutableAccount( + this, rootWorldUpdater.getOrCreateSenderAccount(address)); } @Override public MutableAccount getSenderAccount(final MessageFrame frame) { - return authorizedCodeService.processMutableAccount( - this, rootWorldUpdater.getSenderAccount(frame), frame.getSenderAddress()); + return delegatedCodeService.processMutableAccount( + this, rootWorldUpdater.getSenderAccount(frame)); } @Override @@ -117,11 +115,11 @@ public class EVMWorldUpdater implements WorldUpdater { @Override public WorldUpdater updater() { - return new EVMWorldUpdater(rootWorldUpdater.updater(), authorizedCodeService); + return new EVMWorldUpdater(rootWorldUpdater.updater(), delegatedCodeService); } @Override public Account get(final Address address) { - return authorizedCodeService.processAccount(this, rootWorldUpdater.get(address), address); + return delegatedCodeService.processAccount(this, rootWorldUpdater.get(address)); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/fluent/SimpleWorldTest.java b/evm/src/test/java/org/hyperledger/besu/evm/fluent/SimpleWorldTest.java new file mode 100644 index 000000000..c7f997bfb --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/fluent/SimpleWorldTest.java @@ -0,0 +1,331 @@ +/* + * 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.fluent; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SimpleWorldTest { + private static final Address ADDRESS1 = Address.fromHexString("0x0"); + private static final Address ADDRESS2 = Address.fromHexString("0x1"); + private static final Address ADDRESS3 = Address.fromHexString("0x2"); + + private SimpleWorld simpleWorld; + + @BeforeEach + void setUp() { + simpleWorld = new SimpleWorld(); + } + + @Test + void get_noAccounts() { + assertThat(simpleWorld.get(ADDRESS1)).isNull(); + } + + @Test + void get_noAccountsWithParent() { + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.get(ADDRESS1)).isNull(); + } + + @Test + void createAccount_cannotCreateIfExists() { + simpleWorld.createAccount(ADDRESS1); + assertThatThrownBy(() -> simpleWorld.createAccount(ADDRESS1)) + .isInstanceOf(IllegalStateException.class); + } + + @Test + void get_createdAccountExistsInParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.get(ADDRESS1)).isEqualTo(account); + } + + @Test + void get_createdAccountExistsInMultiParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + assertThat(childUpdater.get(ADDRESS1)).isEqualTo(account); + } + + @Test + void get_createdAccountExists() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + assertThat(simpleWorld.get(ADDRESS1)).isEqualTo(account); + } + + @Test + void get_createdAccountDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS1); + assertThat(simpleWorld.get(ADDRESS1)).isNull(); + } + + @Test + void get_revertRemovesAllAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.get(ADDRESS1)).isNull(); + assertThat(simpleWorld.get(ADDRESS2)).isNull(); + assertThat(simpleWorld.get(ADDRESS3)).isNull(); + } + + @Test + void get_commitKeepsAllAccounts() { + MutableAccount acc1 = simpleWorld.createAccount(ADDRESS1); + MutableAccount acc2 = simpleWorld.createAccount(ADDRESS2); + MutableAccount acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.get(ADDRESS1)).isEqualTo(acc1); + assertThat(simpleWorld.get(ADDRESS2)).isEqualTo(acc2); + assertThat(simpleWorld.get(ADDRESS3)).isEqualTo(acc3); + } + + @Test + void get_createdAccountDeletedInChild() { + simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater.deleteAccount(ADDRESS1); + assertThat(childUpdater.get(ADDRESS1)).isNull(); + } + + @Test + void getAccount_noAccounts() { + assertThat(simpleWorld.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getAccount_noAccountsWithParent() { + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getAccount_createdAccountExistsInParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.ONE); + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.getAccount(ADDRESS1).getOriginalStorageValue(UInt256.MAX_VALUE)) + .isEqualTo(UInt256.ONE); + } + + @Test + void getAccount_createdAccountExistsInMultiParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.ONE); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + assertThat(childUpdater.getAccount(ADDRESS1).getOriginalStorageValue(UInt256.MAX_VALUE)) + .isEqualTo(UInt256.ONE); + } + + @Test + void getAccount_createdAccountExists() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + assertThat(simpleWorld.getAccount(ADDRESS1)).isEqualTo(account); + } + + @Test + void getAccount_createdAccountDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS1); + assertThat(simpleWorld.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getAccount_revertRemovesAllAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.getAccount(ADDRESS1)).isNull(); + assertThat(simpleWorld.getAccount(ADDRESS2)).isNull(); + assertThat(simpleWorld.getAccount(ADDRESS3)).isNull(); + } + + @Test + void getAccount_commitKeepsAllAccounts() { + MutableAccount acc1 = simpleWorld.createAccount(ADDRESS1); + MutableAccount acc2 = simpleWorld.createAccount(ADDRESS2); + MutableAccount acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.getAccount(ADDRESS1)).isEqualTo(acc1); + assertThat(simpleWorld.getAccount(ADDRESS2)).isEqualTo(acc2); + assertThat(simpleWorld.getAccount(ADDRESS3)).isEqualTo(acc3); + } + + @Test + void getAccount_createdAccountDeletedInChild() { + simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater.deleteAccount(ADDRESS1); + assertThat(childUpdater.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getTouchedAccounts_createdAccounts() { + Account acc1 = simpleWorld.createAccount(ADDRESS1); + Account acc2 = simpleWorld.createAccount(ADDRESS2); + Account acc3 = simpleWorld.createAccount(ADDRESS3); + assertThat(simpleWorld.getTouchedAccounts().toArray()) + .containsExactlyInAnyOrder(acc1, acc2, acc3); + } + + @Test + void getTouchedAccounts_revertedAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.getTouchedAccounts()).isEmpty(); + } + + @Test + void getTouchedAccounts_createdAndDeletedAccounts() { + Account acc1 = simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + Account acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS2); + assertThat(simpleWorld.getTouchedAccounts().toArray()).containsExactlyInAnyOrder(acc1, acc3); + } + + @Test + void getTouchedAccounts_allDeletedAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + assertThat(simpleWorld.getTouchedAccounts()).isEmpty(); + } + + @Test + void getTouchedAccounts_createdAndCommittedAccounts() { + Account acc1 = simpleWorld.createAccount(ADDRESS1); + Account acc2 = simpleWorld.createAccount(ADDRESS2); + Account acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.getTouchedAccounts().toArray()) + .containsExactlyInAnyOrder(acc1, acc2, acc3); + } + + @Test + void getDeletedAccountAddresses_singleDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS2); + assertThat(simpleWorld.getDeletedAccountAddresses().toArray()).containsExactly(ADDRESS2); + } + + @Test + void getDeletedAccountAddresses_allDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + assertThat(simpleWorld.getDeletedAccountAddresses().toArray()) + .containsExactlyInAnyOrder(ADDRESS1, ADDRESS2, ADDRESS3); + } + + @Test + void getDeletedAccountAddresses_allDeletedThenRevert() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.getDeletedAccountAddresses()).isEmpty(); + } + + @Test + void getDeletedAccountAddresses_allDeletedThenCommit() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.getDeletedAccountAddresses().toArray()) + .containsExactlyInAnyOrder(ADDRESS1, ADDRESS2, ADDRESS3); + } + + @Test + void commit_deletedAccountNoNPEs() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.commit(); + } + + @Test + void commit_onlyCommitsNewAccountsToDirectParent() { + WorldUpdater simpleWorldLevel1 = simpleWorld.updater(); + WorldUpdater simpleWorldLevel2 = simpleWorldLevel1.updater(); + MutableAccount createdAccount = simpleWorldLevel2.createAccount(ADDRESS1); + simpleWorldLevel2.commit(); + assertThat(simpleWorldLevel1.getTouchedAccounts().toArray()).containsExactly(createdAccount); + assertThat(simpleWorld.getTouchedAccounts()).isEmpty(); + } + + @Test + void commit_onlyCommitsDeletedAccountsToDirectParent() { + WorldUpdater simpleWorldLevel1 = simpleWorld.updater(); + WorldUpdater simpleWorldLevel2 = simpleWorldLevel1.updater(); + simpleWorldLevel2.createAccount(ADDRESS2); + simpleWorldLevel2.deleteAccount(ADDRESS2); + simpleWorldLevel2.commit(); + assertThat(simpleWorldLevel1.getDeletedAccountAddresses().toArray()).containsExactly(ADDRESS2); + assertThat(simpleWorld.getDeletedAccountAddresses()).isEmpty(); + } + + @Test + void commit_accountsReflectChangesAfterCommit() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.ONE); + WorldUpdater simpleWorldUpdater = simpleWorld.updater(); + + account = simpleWorldUpdater.getAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.valueOf(22L)); + simpleWorldUpdater.commit(); + + assertThat(simpleWorldUpdater.get(ADDRESS1).getStorageValue(UInt256.MAX_VALUE)) + .isEqualTo(simpleWorldUpdater.get(ADDRESS1).getOriginalStorageValue(UInt256.MAX_VALUE)); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java index 40ef063db..3420df88d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java @@ -206,7 +206,8 @@ class ContractCreationProcessorTest new ContractCreationProcessor( evm, true, - Collections.singletonList(MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON)), + Collections.singletonList( + MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON, EvmConfiguration.DEFAULT)), 1, Collections.emptyList()); final Bytes contractCode = @@ -227,7 +228,8 @@ class ContractCreationProcessorTest new ContractCreationProcessor( evm, true, - Collections.singletonList(MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON)), + Collections.singletonList( + MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON, EvmConfiguration.DEFAULT)), 1, Collections.emptyList()); final Bytes contractCode = diff --git a/gradle/license-normalizer-bundle.json b/gradle/license-normalizer-bundle.json index 8c0af450f..06c6f8d27 100644 --- a/gradle/license-normalizer-bundle.json +++ b/gradle/license-normalizer-bundle.json @@ -57,6 +57,7 @@ { "bundleName" : "CC0-1.0", "licenseUrlPattern" : ".*(www\\.)?creativecommons\\.org/publicdomain/zero/1\\.0/" }, { "bundleName" : "CDDL-1.0", "licenseFileContentPattern" : ".*CDDL.*1\\.0" }, { "bundleName" : "CDDL-1.0", "licenseUrlPattern" : ".*CDDL.*.?1\\.0" }, + { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "CDDL-1\\.0" }, { "bundleName" : "CDDL-1.1", "licenseUrlPattern" : ".*CDDL.*.?1\\.1" }, { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "Common Development and Distribution License( \\(CDDL\\),?)? (version )?(.?\\s?)?1\\.0" }, { "bundleName" : "CDDL-1.1", "licenseNamePattern" : "Common Development and Distribution License( \\(CDDL\\),?)? (version )?(.?\\s?)?1\\.1" }, @@ -68,7 +69,7 @@ { "bundleName" : "BSD-2-Clause", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/BSD-2-Clause" }, { "bundleName" : "BSD-2-Clause", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/bsd-license(\\.php)?" }, { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "Common Development and Distribution( License)?" }, - { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "CDDL 1(\\.0)" }, + { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "CDDL( |-)1(\\.0)" }, { "bundleName" : "CDDL-1.1", "licenseNamePattern" : "CDDL 1\\.1" }, { "bundleName" : "CDDL-1.1", "licenseUrlPattern" : ".*(www\\.).opensource\\.org/licenses/CDDL-1\\.0" }, { "bundleName" : "EPL-1.0", "licenseNamePattern" : "Eclipse Publish License.*(v|version)\\.?\\s?1(\\.?0)?" }, diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 62efb721a..4ab26ffb5 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -86,12 +86,12 @@ - - - + + + - - + + @@ -104,16 +104,16 @@ - - - - - + + + + + @@ -134,6 +134,11 @@ + + + + + @@ -154,21 +159,16 @@ - - - - - - - - - - + + + + + @@ -179,16 +179,16 @@ - - - - - + + + + + @@ -213,12 +213,12 @@ - - - + + + - - + + @@ -253,6 +253,14 @@ + + + + + + + + @@ -277,55 +285,55 @@ - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + @@ -352,46 +360,46 @@ - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -426,6 +434,17 @@ + + + + + + + + + + + @@ -453,6 +472,14 @@ + + + + + + + + @@ -461,6 +488,14 @@ + + + + + + + + @@ -477,6 +512,14 @@ + + + + + + + + @@ -485,6 +528,14 @@ + + + + + + + + @@ -493,6 +544,14 @@ + + + + + + + + @@ -517,17 +576,17 @@ - - - + + + - - + + - - - + + + @@ -559,33 +618,33 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -635,12 +694,12 @@ - - - + + + - - + + @@ -651,9 +710,9 @@ - - - + + + @@ -661,6 +720,19 @@ + + + + + + + + + + + + + @@ -677,6 +749,14 @@ + + + + + + + + @@ -687,41 +767,46 @@ + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -732,40 +817,24 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + @@ -782,31 +851,21 @@ - - - - - - - - - - - - - - - + + + + + @@ -870,14 +929,6 @@ - - - - - - - - @@ -886,23 +937,15 @@ - - - + + + - - + + - - - - - - - - - - + + @@ -910,14 +953,9 @@ - - - - - - - - + + + @@ -928,30 +966,30 @@ - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + @@ -967,12 +1005,12 @@ - - - + + + - - + + @@ -990,9 +1028,9 @@ - - - + + + @@ -1016,20 +1054,20 @@ - - - + + + - - + + - - - + + + - - + + @@ -1047,9 +1085,9 @@ - - - + + + @@ -1076,31 +1114,23 @@ - - - + + + - - + + - - - + + + - - + + - - - - - - - - - - + + @@ -1111,6 +1141,14 @@ + + + + + + + + @@ -1124,28 +1162,28 @@ - - - + + + - - + + - - + + - - - + + + - - - + + + - - + + @@ -1215,6 +1253,17 @@ + + + + + + + + + + + @@ -1305,6 +1354,14 @@ + + + + + + + + @@ -1322,9 +1379,6 @@ - - - @@ -1337,6 +1391,14 @@ + + + + + + + + @@ -1361,12 +1423,12 @@ - - - + + + - - + + @@ -1385,20 +1447,12 @@ - - - + + + - - - - - - - - - - + + @@ -1414,20 +1468,20 @@ - - - + + + - - + + - - - + + + - - + + @@ -1446,6 +1500,17 @@ + + + + + + + + + + + @@ -1467,180 +1532,188 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + @@ -1680,22 +1753,22 @@ - - - + + + - - + + - - - + + + - - - + + + @@ -1703,44 +1776,73 @@ - - - - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1748,84 +1850,113 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1833,20 +1964,28 @@ - - - + + + - - + + - - - + + + - - + + + + + + + + + + @@ -1854,395 +1993,407 @@ - - - + + + - - - - - - + + + - - - - - - + + + - - - + + + - - + + - - - + + + - - - - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + + + + - - - + + + - - + + - - - + + + - - + + - - - - - - + + + - - - - - - - - - - - - - - - + + + - - - + + + - - - - - - - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + + - - - + + + - - + + + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - + + - - - + + + - - - - - + + - - - + + + - - - - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + + + + - - - + + + - - + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2295,15 +2446,15 @@ - - - + + + - - + + - - + + @@ -2327,15 +2478,15 @@ - - - + + + - - + + - - + + @@ -2441,17 +2592,17 @@ - - - + + + - - - + + + - - + + @@ -2563,49 +2714,49 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -2613,9 +2764,9 @@ - - - + + + @@ -2628,12 +2779,12 @@ - - - + + + - - + + @@ -2662,49 +2813,49 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -2778,38 +2929,38 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + @@ -2838,30 +2989,30 @@ - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + @@ -2872,20 +3023,20 @@ - - - + + + - - + + - - - + + + - - + + @@ -2996,6 +3147,11 @@ + + + + + @@ -3019,20 +3175,20 @@ - - - + + + - - + + - - - + + + - - + + @@ -3044,9 +3200,6 @@ - - - @@ -3059,6 +3212,14 @@ + + + + + + + + @@ -3125,11 +3286,6 @@ - - - - - @@ -3140,9 +3296,9 @@ - - - + + + @@ -3150,12 +3306,22 @@ - - - + + + - - + + + + + + + + + + + + @@ -3171,14 +3337,6 @@ - - - - - - - - @@ -3187,21 +3345,11 @@ - - - - - - - - - - @@ -3217,14 +3365,6 @@ - - - - - - - - @@ -3233,38 +3373,38 @@ - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -3275,51 +3415,51 @@ - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -3340,44 +3480,44 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -3396,25 +3536,25 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -3433,20 +3573,12 @@ - - - + + + - - - - - - - - - - + + @@ -3465,12 +3597,20 @@ - - - + + + - - + + + + + + + + + + @@ -3481,51 +3621,43 @@ - - - - - - - - - - - + + + - - + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + @@ -3644,173 +3776,173 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + @@ -4745,25 +4877,25 @@ - - - + + + - - - + + + - - + + - - - + + + - - + + @@ -4784,6 +4916,14 @@ + + + + + + + + @@ -4797,12 +4937,17 @@ - - - + + + - - + + + + + + + @@ -4821,12 +4966,12 @@ - - - + + + - - + + @@ -4932,15 +5077,15 @@ - - - + + + - - + + - - + + @@ -4974,12 +5119,12 @@ - - - + + + - - + + @@ -5098,36 +5243,36 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -5154,6 +5299,22 @@ + + + + + + + + + + + + + + + + @@ -5162,6 +5323,14 @@ + + + + + + + + @@ -5194,14 +5363,6 @@ - - - - - - - - @@ -5210,26 +5371,26 @@ - - - + + + - - + + - - + + - - - + + + - - + + - - + + @@ -5240,15 +5401,15 @@ - - - + + + - - + + - - + + @@ -5259,26 +5420,26 @@ - - - + + + - - + + - - + + - - - + + + - - + + - - + + @@ -5294,23 +5455,28 @@ - - - + + + - - + + - - - - - + + + + + + + + + + @@ -5326,12 +5492,12 @@ - - - + + + - - + + @@ -5342,12 +5508,12 @@ - - - + + + - - + + @@ -5366,30 +5532,30 @@ - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -5397,28 +5563,20 @@ - - - + + + - - + + - - - + + + - - - - - - - - - - + + @@ -5471,12 +5629,12 @@ - - - + + + - - + + @@ -5627,12 +5785,12 @@ - - - + + + - - + + @@ -5648,6 +5806,11 @@ + + + + + @@ -5656,12 +5819,12 @@ - - - + + + - - + + @@ -5672,12 +5835,12 @@ - - - + + + - - + + @@ -5688,17 +5851,17 @@ - - - + + + - - + + - - - + + + @@ -5757,14 +5920,6 @@ - - - - - - - - @@ -5773,9 +5928,12 @@ - - - + + + + + + @@ -5783,12 +5941,17 @@ - - - + + + - - + + + + + + + @@ -5796,19 +5959,19 @@ - - - - - - - - + + + + + + + + @@ -5829,12 +5992,12 @@ - - - + + + - - + + @@ -5852,28 +6015,36 @@ - - - + + + - - - - - + + - - - + + + - - + + + + + - - - + + + + + + + + + + + @@ -5884,17 +6055,6 @@ - - - - - - - - - - - @@ -5903,15 +6063,26 @@ - - - + + + - - + + - - + + + + + + + + + + + + + @@ -5922,17 +6093,6 @@ - - - - - - - - - - - @@ -5944,15 +6104,15 @@ - - - + + + - - + + - - + + @@ -5963,15 +6123,26 @@ - - - + + + - - + + - - + + + + + + + + + + + + + @@ -5979,17 +6150,6 @@ - - - - - - - - - - - @@ -5998,15 +6158,15 @@ - - - + + + - - + + - - + + @@ -6020,15 +6180,15 @@ - - - + + + - - + + - - + + @@ -6039,6 +6199,17 @@ + + + + + + + + + + + @@ -6068,52 +6239,52 @@ - - - + + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + @@ -6143,15 +6314,15 @@ - - - + + + - - + + - - + + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index f7599a849..b7a286e69 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -17,12 +17,7 @@ dependencyManagement { dependencies { applyMavenExclusions = false - dependencySet(group: 'org.antlr', version: '4.11.1') { - entry 'antlr4' - entry 'antlr4-runtime' - } - - dependencySet(group:'com.fasterxml.jackson.core', version:'2.16.1') { + dependencySet(group:'com.fasterxml.jackson.core', version:'2.17.2') { entry 'jackson-databind' entry 'jackson-datatype' entry 'jackson-datatype-jdk8' @@ -30,20 +25,20 @@ dependencyManagement { dependency 'com.github.ben-manes.caffeine:caffeine:3.1.8' - dependency 'com.github.oshi:oshi-core:6.4.10' + dependency 'com.github.oshi:oshi-core:6.6.3' dependency 'com.google.auto.service:auto-service:1.1.1' - dependencySet(group: 'com.google.dagger', version: '2.50') { + dependencySet(group: 'com.google.dagger', version: '2.52') { entry'dagger-compiler' entry'dagger' } dependency 'org.hyperledger.besu:besu-errorprone-checks:1.0.0' - dependency 'com.google.guava:guava:33.0.0-jre' + dependency 'com.google.guava:guava:33.3.0-jre' - dependency 'com.graphql-java:graphql-java:21.5' + dependency 'com.graphql-java:graphql-java:22.2' dependency 'com.splunk.logging:splunk-library-javalogging:1.11.8' @@ -51,16 +46,18 @@ dependencyManagement { dependency 'commons-codec:commons-codec:1.16.0' - dependency 'commons-io:commons-io:2.15.1' + dependency 'commons-io:commons-io:2.16.1' - dependency 'dnsjava:dnsjava:3.6.0' + dependency 'commons-net:commons-net:3.11.1' - dependencySet(group: 'info.picocli', version: '4.7.5') { + dependency 'dnsjava:dnsjava:3.6.1' + + dependencySet(group: 'info.picocli', version: '4.7.6') { entry 'picocli' entry 'picocli-codegen' } - dependencySet(group: 'io.grpc', version: '1.60.1') { + dependencySet(group: 'io.grpc', version: '1.66.0') { entry 'grpc-all' entry 'grpc-core' entry 'grpc-netty' @@ -69,22 +66,22 @@ dependencyManagement { dependency 'io.kubernetes:client-java:18.0.1' - dependency 'io.netty:netty-all:4.1.110.Final' - dependency 'io.netty:netty-tcnative-boringssl-static:2.0.62.Final' - dependency group: 'io.netty', name: 'netty-transport-native-epoll', version:'4.1.110.Final', classifier: 'linux-x86_64' - dependency group: 'io.netty', name: 'netty-transport-native-kqueue', version:'4.1.110.Final', classifier: 'osx-x86_64' - dependency 'io.netty:netty-transport-native-unix-common:4.1.110.Final' + dependency 'io.netty:netty-all:4.1.112.Final' + dependency 'io.netty:netty-tcnative-boringssl-static:2.0.66.Final' + dependency group: 'io.netty', name: 'netty-transport-native-epoll', version:'4.1.112.Final', classifier: 'linux-x86_64' + dependency group: 'io.netty', name: 'netty-transport-native-kqueue', version:'4.1.112.Final', classifier: 'osx-x86_64' + dependency 'io.netty:netty-transport-native-unix-common:4.1.112.Final' - dependency 'io.opentelemetry:opentelemetry-api:1.33.0' - dependency 'io.opentelemetry:opentelemetry-exporter-otlp:1.33.0' - dependency 'io.opentelemetry:opentelemetry-extension-trace-propagators:1.33.0' - dependency 'io.opentelemetry:opentelemetry-sdk-metrics:1.33.0' - dependency 'io.opentelemetry:opentelemetry-sdk-trace:1.33.0' - dependency 'io.opentelemetry:opentelemetry-sdk:1.33.0' - dependency 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.33.0' - dependency 'io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0:1.32.0-alpha' - dependency 'io.opentelemetry.proto:opentelemetry-proto:1.0.0-alpha' - dependency 'io.opentelemetry.semconv:opentelemetry-semconv:1.23.1-alpha' + dependency 'io.opentelemetry:opentelemetry-api:1.41.0' + dependency 'io.opentelemetry:opentelemetry-exporter-otlp:1.41.0' + dependency 'io.opentelemetry:opentelemetry-extension-trace-propagators:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk-metrics:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk-trace:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.41.0' + dependency 'io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0:2.7.0-alpha' + dependency 'io.opentelemetry.proto:opentelemetry-proto:1.3.2-alpha' + dependency 'io.opentelemetry.semconv:opentelemetry-semconv:1.27.0-alpha' dependency 'io.opentracing.contrib:opentracing-okhttp3:3.0.0' dependency 'io.opentracing:opentracing-api:0.33.0' @@ -115,7 +112,7 @@ dependencyManagement { entry 'tuweni-units' } - dependencySet(group: 'io.vertx', version: '4.5.8') { + dependencySet(group: 'io.vertx', version: '4.5.9') { entry 'vertx-auth-jwt' entry 'vertx-codegen' entry 'vertx-core' @@ -130,24 +127,28 @@ dependencyManagement { dependency 'net.java.dev.jna:jna:5.14.0' - dependency 'org.apache.commons:commons-compress:1.26.0' - dependency 'org.apache.commons:commons-lang3:3.14.0' - dependency 'org.apache.commons:commons-text:1.11.0' - dependency 'org.apache.commons:commons-collections4:4.4' - dependency 'commons-net:commons-net:3.11.0' + dependencySet(group: 'org.antlr', version: '4.11.1') { + entry 'antlr4' + entry 'antlr4-runtime' + } - dependencySet(group: 'org.apache.logging.log4j', version: '2.22.1') { + dependency 'org.apache.commons:commons-collections4:4.4' + dependency 'org.apache.commons:commons-compress:1.27.1' + dependency 'org.apache.commons:commons-lang3:3.17.0' + dependency 'org.apache.commons:commons-text:1.12.0' + + dependencySet(group: 'org.apache.logging.log4j', version: '2.23.1') { entry 'log4j-api' entry 'log4j-core' entry 'log4j-jul' entry 'log4j-slf4j2-impl' } - dependency 'org.assertj:assertj-core:3.25.1' + dependency 'org.assertj:assertj-core:3.26.3' - dependency 'org.awaitility:awaitility:4.2.0' + dependency 'org.awaitility:awaitility:4.2.2' - dependencySet(group: 'org.bouncycastle', version: '1.77') { + dependencySet(group: 'org.bouncycastle', version: '1.78.1') { entry'bcpkix-jdk18on' entry'bcprov-jdk18on' } @@ -164,19 +165,21 @@ dependencyManagement { entry 'gnark' } - dependencySet(group: 'org.immutables', version: '2.10.0') { + dependencySet(group: 'org.immutables', version: '2.10.1') { entry 'value-annotations' entry 'value' } - dependency 'org.java-websocket:Java-WebSocket:1.5.5' + dependency 'org.java-websocket:Java-WebSocket:1.5.7' - dependency 'org.jacoco:org.jacoco.agent:0.8.11' - dependency 'org.jacoco:org.jacoco.core:0.8.11' + dependencySet(group: 'org.jacoco', version: '0.8.12') { + entry 'org.jacoco.agent' + entry 'org.jacoco.core' + } - dependency 'org.jetbrains.kotlin:kotlin-stdlib:1.9.22' + dependency 'org.jetbrains.kotlin:kotlin-stdlib:2.0.20' - dependencySet(group: 'org.junit.jupiter', version: '5.10.1') { + dependencySet(group: 'org.junit.jupiter', version: '5.11.0') { entry 'junit-jupiter' entry 'junit-jupiter-api' entry 'junit-jupiter-engine' @@ -189,47 +192,47 @@ dependencyManagement { dependency 'org.junit.vintage:junit-vintage-engine:5.10.1' - dependencySet(group: 'org.jupnp', version:'2.7.1') { + dependencySet(group: 'org.jupnp', version:'3.0.2') { entry 'org.jupnp.support' entry 'org.jupnp' } - dependencySet(group: 'org.mockito', version:'5.8.0') { + dependencySet(group: 'org.mockito', version:'5.13.0') { entry 'mockito-core' entry 'mockito-junit-jupiter' } - dependencySet(group: 'org.openjdk.jmh', version:'1.36') { + dependencySet(group: 'org.openjdk.jmh', version:'1.37') { entry 'jmh-core' entry 'jmh-generator-annprocess' } - dependency 'org.owasp.encoder:encoder:1.2.3' + dependency 'org.owasp.encoder:encoder:1.3.1' dependency 'org.rocksdb:rocksdbjni:8.3.2' // 8.9.1 causes a bug with a FOREST canary - dependencySet(group: 'org.slf4j', version:'2.0.10') { + dependencySet(group: 'org.slf4j', version:'2.0.16') { entry 'slf4j-api' entry 'slf4j-nop' } - dependency 'org.springframework.security:spring-security-crypto:6.2.1' + dependency 'org.springframework.security:spring-security-crypto:6.3.3' - dependency 'org.testcontainers:testcontainers:1.19.3' + dependency 'org.testcontainers:testcontainers:1.20.1' dependency 'org.web3j:quorum:4.10.0' - dependencySet(group: 'org.web3j', version: '4.11.1') { + dependencySet(group: 'org.web3j', version: '4.12.1') { entry 'abi' entry 'besu' entry 'core' entry 'crypto' } - dependencySet(group: 'org.wiremock', version: '3.3.1') { + dependencySet(group: 'org.wiremock', version: '3.9.1') { entry 'wiremock' } - dependency 'org.xerial.snappy:snappy-java:1.1.10.5' + dependency 'org.xerial.snappy:snappy-java:1.1.10.6' dependency 'org.yaml:snakeyaml:2.0' @@ -237,6 +240,6 @@ dependencyManagement { dependency 'tech.pegasys:jc-kzg-4844:1.0.0' - dependency 'tech.pegasys.discovery:discovery:22.12.0' + dependency 'tech.pegasys.discovery:discovery:24.6.0' } } diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/LayeredKeyValueStorageTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/LayeredKeyValueStorageTest.java new file mode 100644 index 000000000..6ecc057d6 --- /dev/null +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/LayeredKeyValueStorageTest.java @@ -0,0 +1,339 @@ +/* + * 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.plugin.services.storage.rocksdb.segmented; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.services.kvstore.LayeredKeyValueStorage; + +import java.util.List; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class LayeredKeyValueStorageTest { + + @Mock private SegmentedKeyValueStorage parentStorage; + + private LayeredKeyValueStorage layeredKeyValueStorage; + private SegmentIdentifier segmentId; + + @BeforeEach + void setUp() { + segmentId = mock(SegmentIdentifier.class); + layeredKeyValueStorage = new LayeredKeyValueStorage(parentStorage); + } + + @Test + void shouldReturnEmptyStreamWhenParentAndLayerAreEmpty() { + when(parentStorage.stream(segmentId)).thenReturn(Stream.empty()); + Stream> result = layeredKeyValueStorage.stream(segmentId); + assertTrue(result.collect(Collectors.toList()).isEmpty()); + } + + private ConcurrentMap>> + createSegmentMap() { + ConcurrentMap>> map = + new ConcurrentHashMap<>(); + NavigableMap> segmentMap = new TreeMap<>(); + map.put(segmentId, segmentMap); + return map; + } + + @Test + void shouldReturnParentDataWhenLayerIsEmpty() { + byte[] key1 = {1}; + byte[] value1 = {10}; + + when(parentStorage.stream(segmentId)).thenReturn(Stream.of(Pair.of(key1, value1))); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.collect(Collectors.toList()); + assertEquals(1, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + } + + @Test + void shouldReturnLayerDataWhenParentIsEmpty() { + byte[] key1 = {1}; + byte[] value1 = {10}; + + when(parentStorage.stream(segmentId)).thenReturn(Stream.empty()); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + List> resultList = result.toList(); + assertEquals(1, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + } + + @Test + void shouldMergeParentAndLayerData() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, value1), Pair.of(key3, value3))); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } + + @Test + void shouldPreferLayerDataOverParentDataForSameKey() { + byte[] key = {1}; + byte[] parentValue = {10}; + byte[] layerValue = {20}; + + when(parentStorage.stream(segmentId)).thenReturn(Stream.of(Pair.of(key, parentValue))); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key), Optional.of(layerValue)); + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(1, resultList.size()); + assertArrayEquals(key, resultList.get(0).getKey()); + // Layer value should be returned + assertArrayEquals(layerValue, resultList.get(0).getValue()); + } + + @Test + void shouldNotStreamKeyIfLayerKeyIsEmpty() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, value1), Pair.of(key2, value2))); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + var resultList = layeredKeyValueStorage.stream(segmentId).toList(); + assertEquals(1, resultList.size()); + assertArrayEquals(key2, resultList.get(0).getKey()); + assertArrayEquals(value2, resultList.get(0).getValue()); + } + + /** + * Tests that the stream method correctly handles multiple layers where the current layer + * overrides the parent layers. + */ + @Test + void shouldStreamWithMultipleLayersAndCurrentLayerOverrides() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, null), Pair.of(key2, value2))); + + // Parent Layer 1 + var parentLayer1 = createSegmentMap(); + parentLayer1.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + parentLayer1.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = + new LayeredKeyValueStorage( + currentLayer, new LayeredKeyValueStorage(parentLayer1, parentStorage)); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(2, resultList.size()); + assertArrayEquals(key2, resultList.get(0).getKey()); + assertArrayEquals(value2, resultList.get(0).getValue()); + assertArrayEquals(key3, resultList.get(1).getKey()); + assertArrayEquals(value3, resultList.get(1).getValue()); + } + + /** + * Tests that the stream method correctly handles multiple layers where the current layer + * overrides the parent layers with specific values. + */ + @Test + void shouldStreamWithMultipleLayersAndCurrentLayerOverridesWithValues() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, value1), Pair.of(key2, value2))); + + // Parent Layer 1 + var parentLayer1 = createSegmentMap(); + parentLayer1.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + parentLayer1.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = + new LayeredKeyValueStorage( + currentLayer, new LayeredKeyValueStorage(parentLayer1, parentStorage)); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } + + /** + * Tests that the stream method correctly handles multiple layers where the current layer + * overrides the parent layers with empty values. + */ + @Test + void shouldStreamWithMultipleLayersAndCurrentLayerOverridesWithEmptyValues() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, null), Pair.of(key2, value2))); + + // Parent Layer 1 + var parentLayer1 = createSegmentMap(); + parentLayer1.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + parentLayer1.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = + new LayeredKeyValueStorage( + currentLayer, new LayeredKeyValueStorage(parentLayer1, parentStorage)); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } + + /** + * Tests that the stream method correctly handles a parent layer and a current layer where the + * current layer overrides the parent layer. + */ + @Test + void shouldStreamWithParentLayerAndCurrentLayerOverrides() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, null), Pair.of(key2, value2))); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = new LayeredKeyValueStorage(currentLayer, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } +} diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java index de9abcaf2..9f44c3bc7 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java @@ -178,61 +178,60 @@ public class LayeredKeyValueStorage extends SegmentedInMemoryKeyValueStorage throwIfClosed(); var ourLayerState = hashValueStore.computeIfAbsent(segmentId, s -> newSegmentMap()); - // otherwise, interleave the sorted streams: - final PeekingIterator>> ourIterator = - new PeekingIterator<>( - ourLayerState.entrySet().stream() - .filter(entry -> entry.getValue().isPresent()) - .iterator()); - - final PeekingIterator> parentIterator = + PeekingIterator>> ourIterator = + new PeekingIterator<>(ourLayerState.entrySet().stream().iterator()); + PeekingIterator> parentIterator = new PeekingIterator<>(parent.stream(segmentId).iterator()); return StreamSupport.stream( - Spliterators.spliteratorUnknownSize( - new Iterator<>() { - @Override - public boolean hasNext() { - return ourIterator.hasNext() || parentIterator.hasNext(); - } + Spliterators.spliteratorUnknownSize( + new LayeredIterator(ourIterator, parentIterator), ORDERED | SORTED | DISTINCT), + false) + .filter(e -> e.getValue() != null); + } - private Pair mapEntryToPair( - final Map.Entry> entry) { - return Optional.of(entry) - .map( - e -> - Pair.of( - e.getKey().toArrayUnsafe(), - e.getValue().orElseGet(() -> new byte[0]))) - .get(); - } + private static class LayeredIterator implements Iterator> { + private final PeekingIterator>> ourIterator; + private final PeekingIterator> parentIterator; - @Override - public Pair next() { - var ourPeek = ourIterator.peek(); - var parentPeek = parentIterator.peek(); + LayeredIterator( + final PeekingIterator>> ourIterator, + final PeekingIterator> parentIterator) { + this.ourIterator = ourIterator; + this.parentIterator = parentIterator; + } - if (ourPeek == null || parentPeek == null) { - return ourPeek == null - ? parentIterator.next() - : mapEntryToPair(ourIterator.next()); - } + @Override + public boolean hasNext() { + return ourIterator.hasNext() || parentIterator.hasNext(); + } - // otherwise compare: - int comparison = ourPeek.getKey().compareTo(Bytes.wrap(parentPeek.getKey())); - if (comparison < 0) { - return mapEntryToPair(ourIterator.next()); - } else if (comparison == 0) { - // skip dupe key from parent, return ours: - parentIterator.next(); - return mapEntryToPair(ourIterator.next()); - } else { - return parentIterator.next(); - } - } - }, - ORDERED | SORTED | DISTINCT), - false); + private Pair mapEntryToPair(final Map.Entry> entry) { + byte[] value = entry.getValue().orElse(null); + return Pair.of(entry.getKey().toArrayUnsafe(), value); + } + + @Override + public Pair next() { + var ourPeek = ourIterator.peek(); + var parentPeek = parentIterator.peek(); + + if (ourPeek == null || parentPeek == null) { + return ourPeek == null ? parentIterator.next() : mapEntryToPair(ourIterator.next()); + } + + // otherwise compare: + int comparison = ourPeek.getKey().compareTo(Bytes.wrap(parentPeek.getKey())); + if (comparison < 0) { + return mapEntryToPair(ourIterator.next()); + } else if (comparison == 0) { + // skip dupe key from parent, return ours: + parentIterator.next(); + return mapEntryToPair(ourIterator.next()); + } else { + return parentIterator.next(); + } + } } @Override