diff --git a/README.md b/README.md index 6e8f780ffad..390b2f0d650 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,47 @@ # .NET Core Ethereum client -| | | | -| :-------- | :------ | :------------ | -| Documentation | | https://docs.nethermind.io | -| Gitter | [![Gitter](https://img.shields.io/gitter/room/nethermindeth/nethermind.svg)](https://gitter.im/nethermindeth/nethermind) | https://gitter.im/nethermindeth/nethermind | -| Discord | [![Discord](https://img.shields.io/discord/629004402170134531)](https://discord.gg/GXJFaYk) | -| Medium | | https://medium.com/nethermind-eth | -| Twitter | | https://twitter.com/nethermindeth | -| Releases | [![GitHub release](https://img.shields.io/github/release/NethermindEth/nethermind.svg)](https://github.com/NethermindEth/nethermind/releases) | https://github.com/NethermindEth/nethermind/releases | -| Website | | https://nethermind.io/ | -|Docker||https://hub.docker.com/r/nethermind/nethermind| -|Codecov.io| [![codecov](https://codecov.io/gh/NethermindEth/nethermind/branch/master/graph/badge.svg)](https://codecov.io/gh/NethermindEth/nethermind) | https://codecov.io/gh/NethermindEth/nethermind | -| Fund | with Gitcoin | https://gitcoin.co/grants/142/nethermind | -| Github Actions | [![[RUN] Consensus Legacy Tests](https://github.com/NethermindEth/nethermind/actions/workflows/run-consesus-legacy-tests.yml/badge.svg)](https://github.com/NethermindEth/nethermind/actions/workflows/run-consesus-legacy-tests.yml) [![[RUN] Nethermind/Ethereum Tests with Code Coverage](https://github.com/NethermindEth/nethermind/actions/workflows/run-nethermind-tests-with-code-coverage.yml/badge.svg)](https://github.com/NethermindEth/nethermind/actions/workflows/run-nethermind-tests-with-code-coverage.yml) [![[UPDATE] GitBook Docs](https://github.com/NethermindEth/nethermind/actions/workflows/update-gitbook-docs.yml/badge.svg)](https://github.com/NethermindEth/nethermind/actions/workflows/update-gitbook-docs.yml) | https://github.com/NethermindEth/nethermind/actions | - -## Download and run: - -[Windows](http://downloads.nethermind.io)
-[Linux x64/arm64](http://downloads.nethermind.io)
-[MacOS](http://downloads.nethermind.io)
- -It syncs fully on: -* `Mainnet` -* `Goerli` -* `Rinkeby` -* `Ropsten` -* `Sepolia` -* `xDai` -* `Poacore` -* `Sokol` -* `Energyweb` -* `Volta` -* `Kovan` (only fast sync and may fail if pWASM transactions appear) - -**PPA** + +Nethermind is a is a high-performance, highly configurable full Ethereum protocol client built on .NET Core that runs on Linux, Windows and MacOS, and supports Clique, AuRa, Ethash and Proof of Stake consensus algorithms. Nethermind offers very fast sync speeds and support for external plug-ins. Enjoy reliable access to rich on-chain data thanks to high performance JSON-RPC based on Kestrel web server. Healthy node monitoring is secured with a Grafana dashboard and Seq enterprise logging. + +[![Documentation](https://img.shields.io/badge/GitBook-docs-7B36ED?style=for-the-badge&logo=gitbook&logoColor=white)](https://docs.nethermind.io) +[![Releases](https://img.shields.io/github/release/NethermindEth/nethermind.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/NethermindEth/nethermind/releases) +[![Docker Pulls](https://img.shields.io/docker/pulls/nethermind/nethermind?style=for-the-badge&logo=docker&logoColor=white)](https://hub.docker.com/r/nethermind/nethermind) +[![Codecov](https://img.shields.io/codecov/c/github/nethermindeth/nethermind?style=for-the-badge&logo=codecov&logoColor=white)](https://codecov.io/gh/NethermindEth/nethermind) +[![Website](https://img.shields.io/website?down_color=lightgrey&down_message=offline&style=for-the-badge&up_color=brightgreen&up_message=online&url=https%3A%2F%2Fnethermind.io)](https://nethermind.io) + +### :speaking_head: Chats +[![Discord](https://img.shields.io/discord/629004402170134531?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/GXJFaYk) +[![Gitter](https://img.shields.io/gitter/room/nethermindeth/nethermind.svg?style=for-the-badge&logo=gitter&logoColor=white)](https://gitter.im/nethermindeth/nethermind) + +### :loudspeaker: Social +[![Twitter Follow](https://img.shields.io/twitter/follow/nethermindeth?style=for-the-badge&logo=twitter&logoColor=white)](https://twitter.com/nethermindeth) +[![LinkedIn Follow](https://img.shields.io/badge/LinkedIn-follow-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/nethermind) +[![Medium Follow](https://img.shields.io/badge/Medium-articles-12100E?style=for-the-badge&logo=medium&logoColor=white)](https://medium.com/nethermind-eth) + +## Download and run + +[![Windows](https://img.shields.io/badge/Windows-AMD64-0078D6?style=for-the-badge&logo=windows&logoColor=white)](http://downloads.nethermind.io) +[![Linux](https://img.shields.io/badge/Linux-AMD64/ARM64-FCC624?style=for-the-badge&logo=linux&logoColor=black)](http://downloads.nethermind.io) +[![MacOS](https://img.shields.io/badge/MacOS-AMD64/ARM64-000000?style=for-the-badge&logo=apple&logoColor=white)](http://downloads.nethermind.io) + +### :chains: Currently supported list of networks + +| `Network name` | +| :------------ | +| Mainnet | +| Goerli | +| Rinkeby | +| Ropsten | +| Sepolia | +| xDai (Gnosis) | +| Poacore | +| Sokol | +| EnergyWeb | +| Volta | +| Kovan | + +#### Using PPA (Tested on Ubuntu Series: `Focal`, `Bionic`, `Xenial` and `Trusty`) 1. `sudo add-apt-repository ppa:nethermindeth/nethermind` 1. `sudo apt install nethermind` @@ -43,7 +50,7 @@ It syncs fully on: 1. To execute the runner ``nethermind --config mainnet_pruned`` -**Homebrew** +#### Using Homebrew 1. `brew tap nethermindeth/nethermind` 1. `brew install nethermind` 1. To execute the launcher @@ -53,9 +60,10 @@ It syncs fully on: # Build from Source -## Prerequisites :construction: +## :construction: Prerequisites -**.NET 6.0** SDK +[![.NET SDK](https://img.shields.io/badge/SDK-6.0-512BD4?style=for-the-badge&logo=dotnet&logoColor=white +)](https://dotnet.microsoft.com/en-us/download) ### Windows @@ -122,17 +130,17 @@ brew install rocksdb gmp snappy lz4 zstd sudo ln -s `find /opt/homebrew/Cellar/snappy -name "libsnappy.dylib"` /usr/local/lib/libsnappy.dylib ``` -## Build and Run +## :building_construction: Build and Run ```sh git clone https://github.com/NethermindEth/nethermind --recursive cd nethermind/src/Nethermind dotnet build Nethermind.sln -c Release cd Nethermind.Runner -dotnet run -c Release --no-build -- --config mainnet_pruned +dotnet run -c Release --no-build --config mainnet ``` -## Docker Image +## :whale: Docker Image Official Nethermind docker images are available on [Docker Hub](https://hub.docker.com/r/nethermind/nethermind). @@ -146,7 +154,7 @@ docker inspect --format='{{index .RepoDigests 0}}' The output must show the image digest, and then you can copy that output in the `FROM` tag inside the Dockerfile -## Test +## :test_tube: Test If you want to run the Nethermind or Ethereum Foundation tests, then: ```sh @@ -157,20 +165,15 @@ dotnet build EthereumTests.sln -c Debug dotnet test EthereumTests.sln ``` -## IDE - -* [JetBrains Rider](https://www.jetbrains.com/rider) -* [Visual Studio Code](https://code.visualstudio.com/docs/other/dotnet) - - -## Contributors welcome -[![GitHub Issues](https://img.shields.io/github/issues/nethermindeth/nethermind.svg)](https://github.com/NethermindEth/nethermind/issues) -[![Gitter](https://img.shields.io/gitter/room/nethermindeth/nethermind.svg)](https://gitter.im/nethermindeth/nethermind) -[![GitHub Contributors](https://img.shields.io/github/contributors/nethermindeth/nethermind.svg)](https://github.com/NethermindEth/nethermind/graphs/contributors) +## :bricks: IDE -At Nethermind we are building an open source multiplatform Ethereum client implementation in .NET Core (running seamlessly on Linux, Windows and MacOS). Simultaneously our team works on Nethermind Data Marketplace and on-chain data extraction tools and client customizations. +[![JetBrains Rider](https://img.shields.io/badge/Rider-000000?style=for-the-badge&logo=Rider&logoColor=white)](https://www.jetbrains.com/rider) +[![Visual Studio Code](https://img.shields.io/badge/Visual_Studio_Code-0078D4?style=for-the-badge&logo=visual%20studio%20code&logoColor=white)](https://code.visualstudio.com/docs/other/dotnet) +[![Visual Studio](https://img.shields.io/badge/Visual_Studio-5C2D91?style=for-the-badge&logo=visual%20studio&logoColor=white)](https://visualstudio.microsoft.com/downloads) -Nethermind client can be used in your projects, when setting up private Ethereum networks or dApps. The latest prod version of Nethermind can be found at downloads.nethermind.io. +## :footprints: Contributors welcome +[![GitHub Issues](https://img.shields.io/github/issues/nethermindeth/nethermind.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/NethermindEth/nethermind/issues) +[![GitHub Contributors](https://img.shields.io/github/contributors/nethermindeth/nethermind.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/NethermindEth/nethermind/graphs/contributors) -# License +## License [![GitHub](https://img.shields.io/github/license/nethermindeth/nethermind.svg)](https://github.com/NethermindEth/nethermind/blob/master/LICENSE) diff --git a/src/Nethermind/Chains/xdai.json b/src/Nethermind/Chains/xdai.json index c3c0d7ec663..d0ffc5bd14e 100644 --- a/src/Nethermind/Chains/xdai.json +++ b/src/Nethermind/Chains/xdai.json @@ -87,39 +87,14 @@ "gasLimit": "0x989680" }, "nodes": [ - "enode://a20c13b1712d32028a277958346a5c29350e8a3e32d40de43a62cb35baa99f96f274960591e46be5a643be7ee77a15d6a4963170460156a77abcf500f0ba0ff0@104.237.150.151:30303", - "enode://f372b16932a4ee5b6be947556bcca1cf57e498267dd78a7a643a87514a0a5ef4f112cb6934aab5775d3e8940ba535e8f53dfa704e162a72970de61e6ef9fd9aa@45.79.158.26:30303", - "enode://fe9720c93e6335b8cacffa10df594c8c166208fa4be8dcb9275788e54111ae88899022fa9c358f2d0029ee57c2223a7fbc97eb06b294422d35ef796a49d87bff@94.237.98.201:30303", - "enode://6d12181aa8527251dd8f9d37a2ff7eadb46f2a90c69f2282352ac7889b105d6b5787a532facee656b29599ee1ee51eb5b1eb01d2a17190e32a6cbe6dfc996828@45.79.158.8:30303", - "enode://389a625160876776946bfea5a6ce4f4c761bd2062cf8e45e510da77595399ee50a802060868d9bf4580431fc2248cadc9ce61826b3513e090bd1cdd4ce11a9d8@161.97.172.191:30303", - "enode://d086bfbe0d15e841e403695c151920459261dd5d5f259858b32727e2bc64d92f48bdad3cc0120703dbb0e2abd2f51c0459cff517bfa16e683ca27018d82a6dcf@66.175.215.67:30303", - "enode://540a0bc258ba93e6fafc238f49eca0a2032b5d40b79c077dc9b9a304fd636af4167b638eb0f80aa455f5da1cf49b76c881f651ed301a1e28d6855a8a3fbe21a0@167.71.174.1:30303", - "enode://1ceea9d3fb22247edf85102f1e78cd31c2f330ace6ac2789c82766a232d56d7c3c1b2aeb504f2f761de754da22e7dfc5bdde3b00b8b98d1d003f6dd81612f8b3@66.175.211.178:30303", - "enode://4175e9ffd9ac9819c9c596a60b7748cc1c1846cf7e8db47ee97f8aa57e42e8cb3dad4201726498d34af94505b2c1429f9d3a508594c521080e8cd1a3fa24a3d5@37.120.245.155:30303", - "enode://b5eba653df9c583238ea238ca7cadf5d2746f1b4da81a8cfb2c1b600f62fe37df000fa5b5292059f74faa6bbf5af01b3b81f99284a1fbd5c0971d74db7dc4a34@66.175.213.149:30303", - "enode://2a8b64e96da7e67e525f04a157a0016b886dfb2d02553501abd0326b7b061aace7e92ad87c1225dc7d261b05e7886391552c812b386451597bff4b040eb43bec@173.212.236.163:30303", - "enode://5f187ae73b07db889c91e8955619b1e6b799696a4b7aab306f7c500a2ec4c7f66dfbea8ac28bde65100653a2fe84b96f2079aed2d61871e06b5a1e32c0f021af@45.79.150.105:30303", - "enode://22e442bf7b7fa9bb5960344ef5aa907b920a931bce83e14620b0378318b7eb73753f7245f0a8252590a71ad9568bcfbb9e5f03bd88ab3feb9c1096227bb8704a@168.119.136.44:30303", - "enode://ef98bc3c9195b9f27312ac646edb6d0096b04c983f93864c30b8f2b20c699ec974a7066cfae090832679e497d23655e0e315a2c96da4a27d75cc4693e6335bba@192.155.90.129:30303", - "enode://de1ab49beedda656976a8fcb01f91ebdb474178fd46e5ce87cf22f0eb90bc3a6721d619ec90c4a6453770a2c24a4bdbc4ef8b8111ffd49f491eb84016a3842d6@54.217.41.94:30303", - "enode://ba04a77c7c8ac0fdd325de91536c33bce3b71095de563aafc72e6ac4111ebf093570c7bdba48b06cc83b0af5596f72fc32563547160e067fcb45a1786b8f7150@45.56.105.53:30303", - "enode://56510b2d296000427e56eb0016d8454998c16347ec2c4ffed84cb82a996707de40ca2b9ea13c8796b4230b4c19ce46f844720ded93135d710b7bbc7352a061d3@50.35.89.213:30304", - "enode://a68b3f3f58ea56dcc70450d371bf0b83363d74cbfdb5f982be00536ae3168aa679c7e7e93bd9ffe34b59527173d73e0ebed0a105c095af2ee16bd1cc66103c80@69.164.215.62:30303", - "enode://e8c7a0db430429bb374c981438c0dbd95e565088a483388aa46d8377a3bd62f02cd83d7e2c7e5fc77606141bfef29d23d4285a7c1d9b7e743cf3029314506df7@80.240.16.221:30303", - "enode://80c8f6f27f80ba91830002a8ca64771f6baf440fd134e88fbecae3a67c8bc58722d624cecbd6439e1a2d28fbd0297d489fdaa40b10c2f3e07fee1913d52b3e30@45.79.185.92:30303", - "enode://da2449aaba873c40c6daf764de55f4b9eae24c4738daec893ef95b6ada96463c6b9624f8e376e1073d21dd820c5bb361e14575121b09bbd7735b6b556ee1b768@67.205.176.117:30303", - "enode://481e43a8e30cdfecfe3159dde960d9e65347c3e8c64dcedea87922df875e4d47a813f53c012920b6754e43cde47302cdfb120fd409b6aa2b47c48e391494c7f5@173.255.233.100:30303", - "enode://90b0a0e74a9a1ad258531b4ceec25587d8b52ff2cfb36206a34bf6ba1a8d21b2abd20da13260102508a2ac67afbeb2d2ab7a5e9d6bea3bce845cd81e655585cc@45.77.110.159:30303", - "enode://6012c883efeee664847a48784459980176a22f31bc98c2aae30011ad7ef0b44011364a0a9ae5eb056db1f052cf3556757bd97485677bbaf1781b131e43204971@69.164.222.63:30303", - "enode://5bc43a57273eb4012b59ce268f986cbeeb5f0f878aa25e3d2d71d9b7ff64029a9dd25a84303f80820a78d83ff3a2c570988d0fc68a17d355a98c20c0784aa14d@8.9.5.108:30303", - "enode://89e046a4f10c64265941789b2e3be900adf5132ced13756aeea126cf59b516445ed8053b600aa764860f1aad552f4f4f3b4250c59b3b8a84ead3d3527c005606@172.104.24.215:30303", - "enode://ab7f6c633ba2dc54795dfd2c739ba7d964f499541c0b8d8ba9d275bd3df1b789470a21a921a469fa515a3dfccc96a434a3fd016a169d88d0043fc6744f34288e@67.205.180.17:30303", - "enode://6674773f7aac78d5527fa90c847dcbca198de4081306406a8fec5c15f7a2e141362344041291dd10d0aafa7706a3d8f21a08b6f6834a5b1aab9cccd8ca35ccee@143.110.226.15:30303", - "enode://0caa2d84aef00d0bc5de6cf9db3e736da245d882ec8f91e201b3e1635960e62cbb2f8bfc57e679ff3e1d53da2773e31df624a56b2f457ecb51d09fdf9970c86b@167.99.4.175:30303", - "enode://7aa4c137b1ec078f2df3c17552e23c7213662819132821ed3aaa42f0212cb889dbb21211f9c5912c68fce577ab7fc99b0a47c0cb469ec0ad29c0acd9ce297659@45.33.84.107:30303", - "enode://e026b1a68e8a19106d14effc0df66050c494e10a6b8a4e9f6fd196d95306d7062d129a8c9510ffdbeaf3fe0154b884c116a0e77aec876c368e507de3420fba05@149.28.32.225:30303", - "enode://0a978bd436b850f61e31778fbbeb3e0182f91bb98a30c073674c741c182611e71842333c098d3db5108f06cd589c3a8341172e34be0421fa66d82f0dd83d8ae1@51.81.244.170:30303", - "enode://75f05df1e5a3094ed2c9df36f122b95852206c52288f777982503946d5b273c7ffd8bb06ad60a0df7a31510906d4090c7bd5fd9bcb04a5b4ac1825a2b7212f32@45.63.18.245:30303" + "enode://172fd36d5ff1bf9db202e0646c90719cec55507a1fa231ce955f6882d2a9295e65841c885d94d23a020347e3169889cef0718eba5a5f5f58dd185f3d5fa0e9b7@147.28.151.154:30303", + "enode://ee4eb9844cbd8f684a734b839b4931e37124358cb17c632e7d5a44fd9ce6d457c4a126a914ea1366670d186b9236820c1e9b2cb2d3584eaf6c540fc10d77f00d@147.28.151.146:30303", + "enode://86f5849a24b158cef9c0f9b03554c918cd20f9e3397b63ae2744d6392a28beb9e0373299cfc4446d9f8b97c1598bc161fe7c6b8e84929f5dec8ea6865e9e7414@145.40.69.162:30303", + "enode://b6ba2a508682143d3159f36b9ce96cf62c711c2e9d47763fe846602a62acb15dc20112fac40c488ce42ce2c9ad28635255a9e47688c1a8f0611b5a38706fd771@147.28.147.242:30303", + "enode://389a625160876776946bfea5a6ce4f4c761bd2062cf8e45e510da77595399ee50a802060868d9bf4580431fc2248cadc9ce61826b3513e090bd1cdd4ce11a9d8@161.97.172.191:30303", + "enode://540a0bc258ba93e6fafc238f49eca0a2032b5d40b79c077dc9b9a304fd636af4167b638eb0f80aa455f5da1cf49b76c881f651ed301a1e28d6855a8a3fbe21a0@167.71.174.1:30303", + "enode://4175e9ffd9ac9819c9c596a60b7748cc1c1846cf7e8db47ee97f8aa57e42e8cb3dad4201726498d34af94505b2c1429f9d3a508594c521080e8cd1a3fa24a3d5@37.120.245.155:30303", + "enode://22e442bf7b7fa9bb5960344ef5aa907b920a931bce83e14620b0378318b7eb73753f7245f0a8252590a71ad9568bcfbb9e5f03bd88ab3feb9c1096227bb8704a@168.119.136.44:30303" ], "accounts": { "0x0000000000000000000000000000000000000005": { diff --git a/src/Nethermind/Nethermind.AccountAbstraction.Test/Network/UserOperationsMessageSerializerTests.cs b/src/Nethermind/Nethermind.AccountAbstraction.Test/Network/UserOperationsMessageSerializerTests.cs index f0bb4967eb5..cec730fe569 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction.Test/Network/UserOperationsMessageSerializerTests.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction.Test/Network/UserOperationsMessageSerializerTests.cs @@ -21,6 +21,7 @@ using Nethermind.AccountAbstraction.Network; using Nethermind.Core; using Nethermind.Network; +using Nethermind.Network.Test.P2P; using Nethermind.Network.Test.P2P.Subprotocols.Eth.V62; using Nethermind.Serialization.Rlp; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs b/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs index 742d864fe2c..6aa7908a562 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs @@ -52,6 +52,7 @@ public class UserOperationSubscribeTests private IBlockTree _blockTree = null!; private ITxPool _txPool = null!; private IReceiptStorage _receiptStorage = null!; + private IReceiptFinder _receiptFinder = null!; private IFilterStore _filterStore = null!; private ISubscriptionManager _subscriptionManager = null!; private IJsonRpcDuplexClient _jsonRpcDuplexClient = null!; @@ -69,6 +70,7 @@ public void Setup() _blockTree = Substitute.For(); _txPool = Substitute.For(); _receiptStorage = Substitute.For(); + _receiptFinder = Substitute.For(); _specProvider = Substitute.For(); _userOperationPools[_testPoolAddress] = Substitute.For(); _filterStore = new FilterStore(); @@ -83,12 +85,13 @@ public void Setup() _blockTree, _txPool, _receiptStorage, + _receiptFinder, _filterStore, new EthSyncingInfo(_blockTree), _specProvider, jsonSerializer); - subscriptionFactory.RegisterSubscriptionType( + subscriptionFactory.RegisterSubscriptionType( "newPendingUserOperations", (jsonRpcDuplexClient,entryPoints) => new NewPendingUserOpsSubscription( jsonRpcDuplexClient, @@ -96,7 +99,7 @@ public void Setup() _logManager, entryPoints) ); - subscriptionFactory.RegisterSubscriptionType( + subscriptionFactory.RegisterSubscriptionType( "newReceivedUserOperations", (jsonRpcDuplexClient,entryPoints) => new NewReceivedUserOpsSubscription( jsonRpcDuplexClient, @@ -113,11 +116,15 @@ public void Setup() _subscribeRpcModule.Context = new JsonRpcContext(RpcEndpoint.Ws, _jsonRpcDuplexClient); } - private JsonRpcResult GetNewPendingUserOpsResult(UserOperationEventArgs userOperationEventArgs, - out string subscriptionId) + private JsonRpcResult GetNewPendingUserOpsResult( + UserOperationEventArgs userOperationEventArgs, + out string subscriptionId, + bool includeUserOperations = false) { + UserOperationSubscriptionParam param = new() {IncludeUserOperations = includeUserOperations}; + NewPendingUserOpsSubscription newPendingUserOpsSubscription = - new(_jsonRpcDuplexClient, _userOperationPools, _logManager); + new(_jsonRpcDuplexClient, _userOperationPools, _logManager, param); JsonRpcResult jsonRpcResult = new(); ManualResetEvent manualResetEvent = new(false); @@ -134,11 +141,15 @@ private JsonRpcResult GetNewPendingUserOpsResult(UserOperationEventArgs userOper return jsonRpcResult; } - private JsonRpcResult GetNewReceivedUserOpsResult(UserOperationEventArgs userOperationEventArgs, - out string subscriptionId) + private JsonRpcResult GetNewReceivedUserOpsResult( + UserOperationEventArgs userOperationEventArgs, + out string subscriptionId, + bool includeUserOperations = false) { + UserOperationSubscriptionParam param = new() {IncludeUserOperations = includeUserOperations}; + NewReceivedUserOpsSubscription newReceivedUserOpsSubscription = - new(_jsonRpcDuplexClient, _userOperationPools, _logManager); + new(_jsonRpcDuplexClient, _userOperationPools, _logManager, param); JsonRpcResult jsonRpcResult = new(); ManualResetEvent manualResetEvent = new(false); @@ -191,14 +202,30 @@ public void NewPendingUserOperationsSubscription_creating_result_with_wrong_entr public void NewPendingUserOperationsSubscription_on_NewPending_event() { UserOperation userOperation = Build.A.UserOperation.TestObject; + userOperation.CalculateRequestId(_entryPointAddress, 1); UserOperationEventArgs userOperationEventArgs = new(userOperation, _entryPointAddress); - JsonRpcResult jsonRpcResult = GetNewPendingUserOpsResult(userOperationEventArgs, out var subscriptionId); + JsonRpcResult jsonRpcResult = GetNewPendingUserOpsResult(userOperationEventArgs, out var subscriptionId, true); jsonRpcResult.Response.Should().NotBeNull(); string serialized = _jsonSerializer.Serialize(jsonRpcResult.Response); - string expectedResult = Expected_text_response_to_UserOperation_event(userOperation, subscriptionId); - expectedResult.Should().Be(serialized); + string expectedResult = Expected_text_response_to_UserOperation_event_with_full_ops(userOperation, subscriptionId); + serialized.Should().Be(expectedResult); + } + + [Test] + public void NewPendingUserOperationsSubscription_on_NewPending_event_without_full_user_operations() + { + UserOperation userOperation = Build.A.UserOperation.TestObject; + userOperation.CalculateRequestId(_entryPointAddress, 1); + UserOperationEventArgs userOperationEventArgs = new(userOperation, _entryPointAddress); + + JsonRpcResult jsonRpcResult = GetNewPendingUserOpsResult(userOperationEventArgs, out var subscriptionId, false); + + jsonRpcResult.Response.Should().NotBeNull(); + string serialized = _jsonSerializer.Serialize(jsonRpcResult.Response); + string expectedResult = Expected_text_response_to_UserOperation_event_without_full_ops(userOperation, subscriptionId); + serialized.Should().Be(expectedResult); } [Test] @@ -236,13 +263,29 @@ public void NewReceivedUserOperationsSubscription_creating_result_with_wrong_ent public void NewReceivedUserOperationsSubscription_on_NewPending_event() { UserOperation userOperation = Build.A.UserOperation.TestObject; + userOperation.CalculateRequestId(_entryPointAddress, 1); UserOperationEventArgs userOperationEventArgs = new(userOperation, _entryPointAddress); - JsonRpcResult jsonRpcResult = GetNewReceivedUserOpsResult(userOperationEventArgs, out var subscriptionId); + JsonRpcResult jsonRpcResult = GetNewReceivedUserOpsResult(userOperationEventArgs, out var subscriptionId, true); jsonRpcResult.Response.Should().NotBeNull(); string serialized = _jsonSerializer.Serialize(jsonRpcResult.Response); - string expectedResult = Expected_text_response_to_UserOperation_event(userOperation, subscriptionId); + string expectedResult = Expected_text_response_to_UserOperation_event_with_full_ops(userOperation, subscriptionId); + expectedResult.Should().Be(serialized); + } + + [Test] + public void NewReceivedUserOperationsSubscription_on_NewPending_event_without_full_user_operations() + { + UserOperation userOperation = Build.A.UserOperation.TestObject; + userOperation.CalculateRequestId(_entryPointAddress, 1); + UserOperationEventArgs userOperationEventArgs = new(userOperation, _entryPointAddress); + + JsonRpcResult jsonRpcResult = GetNewReceivedUserOpsResult(userOperationEventArgs, out var subscriptionId, false); + + jsonRpcResult.Response.Should().NotBeNull(); + string serialized = _jsonSerializer.Serialize(jsonRpcResult.Response); + string expectedResult = Expected_text_response_to_UserOperation_event_without_full_ops(userOperation, subscriptionId); expectedResult.Should().Be(serialized); } @@ -277,7 +320,7 @@ public void Subscriptions_remove_after_closing_websockets_client() expectedLogsUnsub.Should().Be(serializedLogsUnsub); } - private string Expected_text_response_to_UserOperation_event(UserOperation userOperation, string subscriptionId) => + private string Expected_text_response_to_UserOperation_event_with_full_ops(UserOperation userOperation, string subscriptionId) => "{\"jsonrpc\":\"2.0\",\"method\":\"eth_subscription\",\"params\":{\"subscription\":\"" + subscriptionId + "\",\"result\":{\"userOperation\":{\"sender\":\"" @@ -305,5 +348,14 @@ private string Expected_text_response_to_UserOperation_event(UserOperation userO + "\"},\"entryPoint\":\"" + _entryPointAddress + "\"}}}"; + + private string Expected_text_response_to_UserOperation_event_without_full_ops(UserOperation userOperation, string subscriptionId) => + "{\"jsonrpc\":\"2.0\",\"method\":\"eth_subscription\",\"params\":{\"subscription\":\"" + + subscriptionId + + "\",\"result\":{\"userOperation\":\"" + + userOperation.RequestId + + "\",\"entryPoint\":\"" + + _entryPointAddress + + "\"}}}"; } } diff --git a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionConfig.cs b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionConfig.cs index 7ceb63cb9b3..2a9a7feeb66 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionConfig.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionConfig.cs @@ -22,6 +22,7 @@ namespace Nethermind.AccountAbstraction public class AccountAbstractionConfig : IAccountAbstractionConfig { public bool Enabled { get; set; } + public int AaPriorityPeersMaxCount { get; set; } = 20; public int UserOperationPoolSize { get; set; } = 200; public int MaximumUserOperationPerSender { get; set; } = 1; public string EntryPointContractAddresses {get; set;} = ""; diff --git a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs index 8cd5511b230..794d4ecb8a7 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs @@ -11,6 +11,7 @@ using Nethermind.Api; using Nethermind.Api.Extensions; using Nethermind.Blockchain.Contracts.Json; +using Nethermind.Consensus.Producers; using Nethermind.Consensus; using Nethermind.Core; using Nethermind.Core.Extensions; @@ -26,7 +27,7 @@ using Nethermind.AccountAbstraction.Bundler; using Nethermind.AccountAbstraction.Subscribe; using Nethermind.JsonRpc.Modules.Subscribe; -using Nethermind.Consensus.Producers; +using Nethermind.Network.Config; namespace Nethermind.AccountAbstraction @@ -183,6 +184,9 @@ public Task Init(INethermindApi nethermindApi) if (_accountAbstractionConfig.Enabled) { + // Increasing number of priority peers in network config by AaPriorityPeersMaxCount. + // Be careful if there is another plugin with priority peers - they won't be distinguished in SyncPeerPool. + _nethermindApi.Config().PriorityPeersMaxCount += _accountAbstractionConfig.AaPriorityPeersMaxCount; IList entryPointContractAddressesString = _accountAbstractionConfig.GetEntryPointAddresses().ToList(); foreach (string addressString in entryPointContractAddressesString){ bool parsed = Address.TryParse( @@ -270,7 +274,7 @@ public Task InitNetworkProtocol() ILogManager logManager = _nethermindApi.LogManager ?? throw new ArgumentNullException(nameof(_nethermindApi.LogManager)); - AccountAbstractionPeerManager peerManager = new(_userOperationPools, UserOperationBroadcaster, _logger); + AccountAbstractionPeerManager peerManager = new(_userOperationPools, UserOperationBroadcaster, _accountAbstractionConfig.AaPriorityPeersMaxCount, _logger); serializer.Register(new UserOperationsMessageSerializer()); protocolsManager.AddProtocol(Protocol.AA, @@ -311,7 +315,7 @@ public Task InitRpcModules() ISubscriptionFactory subscriptionFactory = _nethermindApi.SubscriptionFactory; //Register custom UserOperation websocket subscription types in the SubscriptionFactory. - subscriptionFactory.RegisterSubscriptionType( + subscriptionFactory.RegisterSubscriptionType( "newPendingUserOperations", (jsonRpcDuplexClient, param) => new NewPendingUserOpsSubscription( jsonRpcDuplexClient, @@ -319,7 +323,7 @@ public Task InitRpcModules() logManager, param) ); - subscriptionFactory.RegisterSubscriptionType( + subscriptionFactory.RegisterSubscriptionType( "newReceivedUserOperations", (jsonRpcDuplexClient, param) => new NewReceivedUserOpsSubscription( jsonRpcDuplexClient, diff --git a/src/Nethermind/Nethermind.AccountAbstraction/IAccountAbstractionConfig.cs b/src/Nethermind/Nethermind.AccountAbstraction/IAccountAbstractionConfig.cs index 2d24ca63c4f..d183fa366f6 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/IAccountAbstractionConfig.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/IAccountAbstractionConfig.cs @@ -28,6 +28,11 @@ public interface IAccountAbstractionConfig : IConfig Description = "Defines whether UserOperations are allowed.", DefaultValue = "false")] bool Enabled { get; set; } + + [ConfigItem( + Description = "Max number of priority AccountAbstraction peers.", + DefaultValue = "20")] + int AaPriorityPeersMaxCount { get; set; } [ConfigItem( Description = "Defines the maximum number of UserOperations that can be kept in memory by clients", diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Network/AaProtocolHandler.cs b/src/Nethermind/Nethermind.AccountAbstraction/Network/AaProtocolHandler.cs index 2a8bcaefaa3..0293a532514 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Network/AaProtocolHandler.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Network/AaProtocolHandler.cs @@ -53,6 +53,8 @@ public AaProtocolHandler(ISession session, _session = session ?? throw new ArgumentNullException(nameof(session)); _userOperationPools = userOperationPools ?? throw new ArgumentNullException(nameof(userOperationPools)); _peerManager = peerManager; + + IsPriority = _peerManager.NumberOfPriorityAaPeers > 0; } public PublicKey Id => _session.Node.Id; diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Network/AccountAbstractionPeerManager.cs b/src/Nethermind/Nethermind.AccountAbstraction/Network/AccountAbstractionPeerManager.cs index c3d0f1bc4f1..399fa82055f 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Network/AccountAbstractionPeerManager.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Network/AccountAbstractionPeerManager.cs @@ -32,13 +32,27 @@ public class AccountAbstractionPeerManager : IAccountAbstractionPeerManager private readonly IUserOperationBroadcaster _broadcaster; private readonly ILogger _logger; - public AccountAbstractionPeerManager(IDictionary userOperationPools, IUserOperationBroadcaster broadcaster, ILogger logger) + public AccountAbstractionPeerManager(IDictionary userOperationPools, + IUserOperationBroadcaster broadcaster, + ILogger logger) + : this(userOperationPools, broadcaster, 0, logger) + { + } + + public AccountAbstractionPeerManager(IDictionary userOperationPools, + IUserOperationBroadcaster broadcaster, + int numberOfPriorityAaPeers, + ILogger logger) { _userOperationPools = userOperationPools; _broadcaster = broadcaster; _logger = logger; + + NumberOfPriorityAaPeers = numberOfPriorityAaPeers; } + public int NumberOfPriorityAaPeers { get; set; } + public void AddPeer(IUserOperationPoolPeer peer) { PeerInfo peerInfo = new(peer); diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Network/IAccountAbstractionPeerManager.cs b/src/Nethermind/Nethermind.AccountAbstraction/Network/IAccountAbstractionPeerManager.cs index 292925fea02..51053c85884 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Network/IAccountAbstractionPeerManager.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Network/IAccountAbstractionPeerManager.cs @@ -22,6 +22,7 @@ namespace Nethermind.AccountAbstraction.Network { public interface IAccountAbstractionPeerManager { + int NumberOfPriorityAaPeers { get; set; } void AddPeer(IUserOperationPoolPeer peer); void RemovePeer(PublicKey nodeId); } diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewPendingUserOpsSubscription.cs b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewPendingUserOpsSubscription.cs index 7c10f2a01ef..18c1b8c840b 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewPendingUserOpsSubscription.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewPendingUserOpsSubscription.cs @@ -30,21 +30,31 @@ namespace Nethermind.AccountAbstraction.Subscribe public class NewPendingUserOpsSubscription : Subscription { private readonly IUserOperationPool[] _userOperationPoolsToTrack; + private readonly bool _includeUserOperations; public NewPendingUserOpsSubscription( IJsonRpcDuplexClient jsonRpcDuplexClient, IDictionary? userOperationPools, ILogManager? logManager, - EntryPointsParam? entryPoints = null) + UserOperationSubscriptionParam? userOperationSubscriptionParam = null) : base(jsonRpcDuplexClient) { if (userOperationPools is null) throw new ArgumentNullException(nameof(userOperationPools)); - if (entryPoints is not null) + if (userOperationSubscriptionParam is not null) { - _userOperationPoolsToTrack = userOperationPools - .Where(kv => entryPoints.EntryPoints.Contains(kv.Key)) - .Select(kv => kv.Value) - .ToArray(); + if (userOperationSubscriptionParam.EntryPoints.Length == 0) + { + _userOperationPoolsToTrack = userOperationPools.Values.ToArray(); + } + else + { + _userOperationPoolsToTrack = userOperationPools + .Where(kv => userOperationSubscriptionParam.EntryPoints.Contains(kv.Key)) + .Select(kv => kv.Value) + .ToArray(); + } + + _includeUserOperations = userOperationSubscriptionParam.IncludeUserOperations; } else { @@ -66,7 +76,15 @@ private void OnNewPending(object? sender, UserOperationEventArgs e) { ScheduleAction(() => { - JsonRpcResult result = CreateSubscriptionMessage(new { UserOperation = new UserOperationRpc(e.UserOperation), EntryPoint = e.EntryPoint }); + JsonRpcResult result; + if (_includeUserOperations) + { + result = CreateSubscriptionMessage(new { UserOperation = new UserOperationRpc(e.UserOperation), EntryPoint = e.EntryPoint }); + } + else + { + result = CreateSubscriptionMessage(new { UserOperation = e.UserOperation.RequestId, EntryPoint = e.EntryPoint }); + } JsonRpcDuplexClient.SendJsonRpcResult(result); if(_logger.IsTrace) _logger.Trace($"newPendingUserOperations subscription {Id} printed hash of newPendingUserOperations."); }); diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewReceivedUserOpsSubscription.cs b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewReceivedUserOpsSubscription.cs index 98775421f3c..9e562950f55 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewReceivedUserOpsSubscription.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/NewReceivedUserOpsSubscription.cs @@ -22,29 +22,40 @@ using Nethermind.AccountAbstraction.Source; using Nethermind.Core; using Nethermind.JsonRpc; -using Nethermind.Logging; +using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Modules.Subscribe; +using Nethermind.Logging; namespace Nethermind.AccountAbstraction.Subscribe { public class NewReceivedUserOpsSubscription : Subscription { private readonly IUserOperationPool[] _userOperationPoolsToTrack; + private readonly bool _includeUserOperations; public NewReceivedUserOpsSubscription( - IJsonRpcDuplexClient jsonRpcDuplexClient, + IJsonRpcDuplexClient jsonRpcDuplexClient, IDictionary? userOperationPools, ILogManager? logManager, - EntryPointsParam? entryPoints = null) + UserOperationSubscriptionParam? userOperationSubscriptionParam = null) : base(jsonRpcDuplexClient) { if (userOperationPools is null) throw new ArgumentNullException(nameof(userOperationPools)); - if (entryPoints is not null) + if (userOperationSubscriptionParam is not null) { - _userOperationPoolsToTrack = userOperationPools - .Where(kv => entryPoints.EntryPoints.Contains(kv.Key)) - .Select(kv => kv.Value) - .ToArray(); + if (userOperationSubscriptionParam.EntryPoints.Length == 0) + { + _userOperationPoolsToTrack = userOperationPools.Values.ToArray(); + } + else + { + _userOperationPoolsToTrack = userOperationPools + .Where(kv => userOperationSubscriptionParam.EntryPoints.Contains(kv.Key)) + .Select(kv => kv.Value) + .ToArray(); + } + + _includeUserOperations = userOperationSubscriptionParam.IncludeUserOperations; } else { @@ -54,7 +65,7 @@ public NewReceivedUserOpsSubscription( _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - foreach (IUserOperationPool pool in _userOperationPoolsToTrack) + foreach (var pool in _userOperationPoolsToTrack) { pool.NewReceived += OnNewReceived; } @@ -66,7 +77,15 @@ private void OnNewReceived(object? sender, UserOperationEventArgs e) { ScheduleAction(() => { - JsonRpcResult result = CreateSubscriptionMessage(new { UserOperation = new UserOperationRpc(e.UserOperation), EntryPoint = e.EntryPoint }); + JsonRpcResult result; + if (_includeUserOperations) + { + result = CreateSubscriptionMessage(new { UserOperation = new UserOperationRpc(e.UserOperation), EntryPoint = e.EntryPoint }); + } + else + { + result = CreateSubscriptionMessage(new { UserOperation = e.UserOperation.RequestId, EntryPoint = e.EntryPoint }); + } JsonRpcDuplexClient.SendJsonRpcResult(result); if(_logger.IsTrace) _logger.Trace($"newReceivedUserOperations subscription {Id} printed hash of newReceivedUserOperations."); }); @@ -76,7 +95,7 @@ private void OnNewReceived(object? sender, UserOperationEventArgs e) public override void Dispose() { - foreach (IUserOperationPool pool in _userOperationPoolsToTrack) + foreach (var pool in _userOperationPoolsToTrack) { pool.NewReceived -= OnNewReceived; } diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/UserOperationSubscriptionParam.cs b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/UserOperationSubscriptionParam.cs new file mode 100644 index 00000000000..5dc73caf671 --- /dev/null +++ b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/UserOperationSubscriptionParam.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using Nethermind.Core; +using Nethermind.JsonRpc; +using Newtonsoft.Json; + +namespace Nethermind.AccountAbstraction.Subscribe +{ + public class UserOperationSubscriptionParam : IJsonRpcParam + { + public Address[] EntryPoints { get; set; } = Array.Empty
(); + public bool IncludeUserOperations { get; set; } + + public void FromJson(JsonSerializer serializer, string jsonValue) + { + UserOperationSubscriptionParam ep = serializer.Deserialize(jsonValue.ToJsonTextReader()) + ?? throw new ArgumentException($"Invalid 'entryPoints' filter: {jsonValue}"); + EntryPoints = ep.EntryPoints; + IncludeUserOperations = ep.IncludeUserOperations; + + } + } +} diff --git a/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs b/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs index 18c87dc2752..ceb44b478ba 100644 --- a/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs +++ b/src/Nethermind/Nethermind.Api/IApiWithNetwork.cs @@ -29,6 +29,7 @@ using Nethermind.Synchronization; using Nethermind.Synchronization.Peers; using Nethermind.Sockets; +using Nethermind.Synchronization.SnapSync; using Nethermind.Synchronization.Blocks; namespace Nethermind.Api @@ -61,5 +62,6 @@ public interface IApiWithNetwork : IApiWithBlockchain ISyncServer? SyncServer { get; set; } IWebSocketsManager WebSocketsManager { get; set; } ISubscriptionFactory SubscriptionFactory { get; set; } + ISnapProvider SnapProvider { get; set; } } } diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index 527d914af41..fba3909d3d6 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -66,6 +66,8 @@ using Nethermind.TxPool; using Nethermind.Wallet; using Nethermind.Sockets; +using Nethermind.State.Snap; +using Nethermind.Synchronization.SnapSync; using Nethermind.Synchronization.Blocks; namespace Nethermind.Api @@ -232,5 +234,6 @@ public ISealEngine SealEngine public IReadOnlyList Plugins { get; } = new List(); public IList Publishers { get; } = new List(); // this should be called publishers public CompositePruningTrigger PruningTrigger { get; } = new(); + public ISnapProvider SnapProvider { get; set; } } } diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs index 797cc5d090d..f9f9456f802 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuRaBlockFinalizationManagerTests.cs @@ -53,8 +53,6 @@ public void Initialize() _validSealerStrategy = Substitute.For(); _validatorStore.GetValidators(Arg.Any()).Returns(new Address[] {TestItem.AddressA, TestItem.AddressB, TestItem.AddressC}); - - Rlp.Decoders[typeof(BlockInfo)] = new BlockInfoDecoder(true); } [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs index f0304329187..1a97a428b5a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs @@ -59,7 +59,7 @@ private void SetUp(bool allowReceiptIterator) _blockTree = Build.A.BlockTree().WithTransactions(_receiptStorage, specProvider, LogsForBlockBuilder).OfChainLength(5).TestObject; _bloomStorage = new BloomStorage(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); _receiptsRecovery = Substitute.For(); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); } private IEnumerable LogsForBlockBuilder(Block block, Transaction transaction) @@ -137,7 +137,7 @@ public void filter_all_logs_when_receipts_are_missing([ValueSource(nameof(WithBl { StoreTreeBlooms(withBloomDb); _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); var logFilter = AllBlockFilter().Build(); var logs = _logFinder.FindLogs(logFilter); @@ -149,7 +149,7 @@ public void filter_all_logs_should_throw_when_to_block_is_not_found([ValueSource { StoreTreeBlooms(withBloomDb); var blockFinder = Substitute.For(); - _logFinder = new LogFinder(blockFinder, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = new LogFinder(blockFinder, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); var logFilter = AllBlockFilter().Build(); var action = new Func>(() =>_logFinder.FindLogs(logFilter)); action.Should().Throw(); @@ -251,7 +251,7 @@ public void filter_by_blocks(LogFilter filter, int expectedCount, bool withBloom public void filter_by_blocks_with_limit([ValueSource(nameof(WithBloomValues))]bool withBloomDb) { StoreTreeBlooms(withBloomDb); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, 2); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, 2); var filter = FilterBuilder.New().FromLatestBlock().ToLatestBlock().Build(); var logs = _logFinder.FindLogs(filter).ToArray(); @@ -298,7 +298,7 @@ public async Task Throw_log_finder_operation_canceled_after_given_timeout([Value StoreTreeBlooms(true); _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); var logFilter = AllBlockFilter().Build(); var logs = _logFinder.FindLogs(logFilter, cancellationToken); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs index 2f63af970e1..d87525e02a0 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs @@ -103,7 +103,7 @@ public void Should_not_cache_empty_non_processed_blocks() .WithReceiptsRoot(TestItem.KeccakA) .TestObject; - var emptyReceipts = new TxReceipt[] {null}; + var emptyReceipts = Array.Empty(); _storage.Get(block).Should().BeEquivalentTo(emptyReceipts); // can be from cache: _storage.Get(block).Should().BeEquivalentTo(emptyReceipts); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs new file mode 100644 index 00000000000..229a2b944ae --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2022 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using FluentAssertions; +using Nethermind.Blockchain.Receipts; +using Nethermind.Core; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Specs; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test.Receipts; + +public class ReceiptsRecoveryTests +{ + private IReceiptsRecovery _receiptsRecovery; + + [SetUp] + public void Setup() + { + RopstenSpecProvider specProvider = RopstenSpecProvider.Instance; + EthereumEcdsa ethereumEcdsa = new(specProvider.ChainId, LimboLogs.Instance); + + _receiptsRecovery = new ReceiptsRecovery(ethereumEcdsa, specProvider); + } + + [TestCase(5, 5, true, ReceiptsRecoveryResult.Success)] + [TestCase(5, 5, false, ReceiptsRecoveryResult.Skipped)] + [TestCase(0, 0, true, ReceiptsRecoveryResult.Skipped)] + [TestCase(1, 0, true, ReceiptsRecoveryResult.Fail)] + [TestCase(0, 1, true, ReceiptsRecoveryResult.Fail)] + [TestCase(5, 4, true, ReceiptsRecoveryResult.Fail)] + [TestCase(1, 2, true, ReceiptsRecoveryResult.Fail)] + public void TryRecover_should_return_correct_receipts_recovery_result(int blockTxsLength, int receiptsLength, bool forceRecoverSender, ReceiptsRecoveryResult expected) + { + Transaction[] txs = new Transaction[blockTxsLength]; + for (int i = 0; i < blockTxsLength; i++) + { + txs[i] = Build.A.Transaction.SignedAndResolved().TestObject; + } + + Block block = Build.A.Block.WithTransactions(txs).TestObject; + + TxReceipt[] receipts = new TxReceipt[receiptsLength]; + for (int i = 0; i < receiptsLength; i++) + { + receipts[i] = Build.A.Receipt.WithBlockHash(block.Hash).TestObject; + } + + _receiptsRecovery.TryRecover(block, receipts, forceRecoverSender).Should().Be(expected); + } +} diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/FullInfoReceiptFinder.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/FullInfoReceiptFinder.cs index 168360d12a2..5392d6df0d7 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/FullInfoReceiptFinder.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/FullInfoReceiptFinder.cs @@ -23,40 +23,47 @@ namespace Nethermind.Blockchain.Receipts { public class FullInfoReceiptFinder : IReceiptFinder { - private readonly IReceiptFinder _innerFinder; + private readonly IReceiptStorage _receiptStorage; private readonly IReceiptsRecovery _receiptsRecovery; private readonly IBlockFinder _blockFinder; - public FullInfoReceiptFinder(IReceiptFinder innerFinder, IReceiptsRecovery receiptsRecovery, IBlockFinder blockFinder) + public FullInfoReceiptFinder(IReceiptStorage receiptStorage, IReceiptsRecovery receiptsRecovery, IBlockFinder blockFinder) { - _innerFinder = innerFinder ?? throw new ArgumentNullException(nameof(innerFinder)); + _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); } - public Keccak FindBlockHash(Keccak txHash) => _innerFinder.FindBlockHash(txHash); + public Keccak FindBlockHash(Keccak txHash) => _receiptStorage.FindBlockHash(txHash); public TxReceipt[] Get(Block block) { - var receipts = _innerFinder.Get(block); - _receiptsRecovery.TryRecover(block, receipts); + var receipts = _receiptStorage.Get(block); + if (_receiptsRecovery.TryRecover(block, receipts) == ReceiptsRecoveryResult.Success) + { + _receiptStorage.Insert(block, receipts); + } + return receipts; } public TxReceipt[] Get(Keccak blockHash) { - var receipts = _innerFinder.Get(blockHash); + var receipts = _receiptStorage.Get(blockHash); if (_receiptsRecovery.NeedRecover(receipts)) { var block = _blockFinder.FindBlock(blockHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded); - _receiptsRecovery.TryRecover(block, receipts); + if (_receiptsRecovery.TryRecover(block, receipts) == ReceiptsRecoveryResult.Success) + { + _receiptStorage.Insert(block, receipts); + } } return receipts; } - public bool CanGetReceiptsByHash(long blockNumber) => _innerFinder.CanGetReceiptsByHash(blockNumber); - public bool TryGetReceiptsIterator(long blockNumber, Keccak blockHash, out ReceiptsIterator iterator) => _innerFinder.TryGetReceiptsIterator(blockNumber, blockHash, out iterator); + public bool CanGetReceiptsByHash(long blockNumber) => _receiptStorage.CanGetReceiptsByHash(blockNumber); + public bool TryGetReceiptsIterator(long blockNumber, Keccak blockHash, out ReceiptsIterator iterator) => _receiptStorage.TryGetReceiptsIterator(blockNumber, blockHash, out iterator); } } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs index 22a3c297420..bae0e355196 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs @@ -20,7 +20,7 @@ namespace Nethermind.Blockchain.Receipts { public interface IReceiptsRecovery { - bool TryRecover(Block block, TxReceipt[] receipts); - bool NeedRecover(TxReceipt[] receipts); + ReceiptsRecoveryResult TryRecover(Block block, TxReceipt[] receipts, bool forceRecoverSender = true); + bool NeedRecover(TxReceipt[] receipts, bool forceRecoverSender = true); } } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs index 3af1c832a48..d0ea2cffa8b 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/InMemoryReceiptStorage.cs @@ -75,7 +75,9 @@ public void Insert(Block block, params TxReceipt[] txReceipts) txReceipt.BlockHash = block.Hash; _transactions[txReceipt.TxHash] = txReceipt; } - ReceiptsInserted?.Invoke(this, new ReceiptsEventArgs(block.Header, txReceipts)); + + bool wasRemoved = txReceipts.Length > 0 && txReceipts[0].Removed; + ReceiptsInserted?.Invoke(this, new ReceiptsEventArgs(block.Header, txReceipts, wasRemoved)); } public long? LowestInsertedReceiptBlockNumber { get; set; } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs index 913dd1ce26c..efe4dc38f90 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs @@ -107,42 +107,29 @@ public TxReceipt[] Get(Block block) return Array.Empty(); } - if (_receiptsCache.TryGet(block.Hash, out var receipts)) + return Get(block.Hash); + } + + public TxReceipt[] Get(Keccak blockHash) + { + if (_receiptsCache.TryGet(blockHash, out var receipts)) { - return receipts; + return receipts ?? Array.Empty(); } - var receiptsData = _blocksDb.GetSpan(block.Hash); + var receiptsData = _blocksDb.GetSpan(blockHash); try { - bool shouldCache = true; - - if (!receiptsData.IsNullOrEmpty()) + if (receiptsData.IsNullOrEmpty()) { - receipts = DecodeArray(receiptsData); + return Array.Empty(); } else { - // didn't bring performance uplift that was expected - // var data = _database.MultiGet(block.Transactions.Select(t => t.Hash)); - // return data.Select(kvp => DeserializeObsolete(new Keccak(kvp.Key), kvp.Value)).ToArray(); - - receipts = new TxReceipt[block.Transactions.Length]; - for (int i = 0; i < block.Transactions.Length; i++) - { - receipts[i] = FindReceiptObsolete(block.Transactions[i].Hash); - shouldCache &= receipts[i] != null; - } - } - - shouldCache &= receipts.Length > 0; - - if (shouldCache) - { - _receiptsCache.Set(block.Hash, receipts); + receipts = DecodeArray(receiptsData); + _receiptsCache.Set(blockHash, receipts); + return receipts; } - - return receipts; } finally { @@ -164,33 +151,6 @@ private static TxReceipt[] DecodeArray(in Span receiptsData) } } - public TxReceipt[] Get(Keccak blockHash) - { - if (_receiptsCache.TryGet(blockHash, out var receipts)) - { - return receipts; - } - - var receiptsData = _blocksDb.GetSpan(blockHash); - try - { - if (receiptsData.IsNullOrEmpty()) - { - return Array.Empty(); - } - else - { - receipts = DecodeArray(receiptsData); - _receiptsCache.Set(blockHash, receipts); - return receipts; - } - } - finally - { - _blocksDb.DangerousReleaseMemory(receiptsData); - } - } - public bool CanGetReceiptsByHash(long blockNumber) => blockNumber >= MigratedBlockNumber; public bool TryGetReceiptsIterator(long blockNumber, Keccak blockHash, out ReceiptsIterator iterator) @@ -219,14 +179,15 @@ public void Insert(Block block, params TxReceipt[] txReceipts) $"of transactions {block.Transactions.Length} and receipts {txReceipts.Length}."); } - _receiptsRecovery.TryRecover(block, txReceipts); + _receiptsRecovery.TryRecover(block, txReceipts, false); var blockNumber = block.Number; var spec = _specProvider.GetSpec(blockNumber); RlpBehaviors behaviors = spec.IsEip658Enabled ? RlpBehaviors.Eip658Receipts | RlpBehaviors.Storage : RlpBehaviors.Storage; _blocksDb.Set(block.Hash, StorageDecoder.Encode(txReceipts, behaviors).Bytes); - if (txReceiptsLength > 0 && !txReceipts[0].Removed) + bool wasRemoved = txReceiptsLength > 0 && txReceipts[0].Removed; + if (!wasRemoved) { for (int i = 0; i < txReceiptsLength; i++) { @@ -241,7 +202,8 @@ public void Insert(Block block, params TxReceipt[] txReceipts) } _receiptsCache.Set(block.Hash, txReceipts); - ReceiptsInserted?.Invoke(this, new ReceiptsEventArgs(block.Header, txReceipts)); + + ReceiptsInserted?.Invoke(this, new ReceiptsEventArgs(block.Header, txReceipts, wasRemoved)); } public long? LowestInsertedReceiptBlockNumber diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsEventArgs.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsEventArgs.cs index 5e52161fd76..4aabc173c9c 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsEventArgs.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsEventArgs.cs @@ -24,11 +24,13 @@ public class ReceiptsEventArgs : EventArgs { public TxReceipt[] TxReceipts { get; } public BlockHeader BlockHeader { get; } + public bool WasRemoved { get; } - public ReceiptsEventArgs(BlockHeader blockHeader, TxReceipt[] txReceipts) + public ReceiptsEventArgs(BlockHeader blockHeader, TxReceipt[] txReceipts, bool wasRemoved) { BlockHeader = blockHeader; TxReceipts = txReceipts; + WasRemoved = wasRemoved; } } } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs index c71800217ca..3c232cfb19e 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs @@ -33,12 +33,12 @@ public ReceiptsRecovery(IEthereumEcdsa? ecdsa, ISpecProvider? specProvider) _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); } - public bool TryRecover(Block block, TxReceipt[] receipts) + public ReceiptsRecoveryResult TryRecover(Block block, TxReceipt[] receipts, bool forceRecoverSender = true) { var canRecover = block.Transactions.Length == receipts?.Length; if (canRecover) { - var needRecover = NeedRecover(receipts); + var needRecover = NeedRecover(receipts, forceRecoverSender); if (needRecover) { var releaseSpec = _specProvider.GetSpec(block.Number); @@ -49,31 +49,33 @@ public bool TryRecover(Block block, TxReceipt[] receipts) if (receipts.Length > receiptIndex) { TxReceipt receipt = receipts[receiptIndex]; - RecoverReceiptData(releaseSpec, receipt, block, transaction, receiptIndex, gasUsedBefore); + RecoverReceiptData(releaseSpec, receipt, block, transaction, receiptIndex, gasUsedBefore, forceRecoverSender); gasUsedBefore = receipt.GasUsedTotal; } } + + return ReceiptsRecoveryResult.Success; } - return true; + return ReceiptsRecoveryResult.Skipped; } - return false; + return ReceiptsRecoveryResult.Fail; } - public bool NeedRecover(TxReceipt[] receipts) => receipts?.Length > 0 && (receipts[0].BlockHash == null || receipts[0].Sender == null); + public bool NeedRecover(TxReceipt[] receipts, bool forceRecoverSender = true) => receipts?.Length > 0 && (receipts[0].BlockHash == null || (forceRecoverSender && receipts[0].Sender == null)); - private void RecoverReceiptData(IReleaseSpec releaseSpec, TxReceipt receipt, Block block, Transaction transaction, int transactionIndex, long gasUsedBefore) + private void RecoverReceiptData(IReleaseSpec releaseSpec, TxReceipt receipt, Block block, Transaction transaction, int transactionIndex, long gasUsedBefore, bool force) { receipt.BlockHash = block.Hash; receipt.BlockNumber = block.Number; receipt.TxHash = transaction.Hash; receipt.Index = transactionIndex; - receipt.Sender = transaction.SenderAddress ?? _ecdsa.RecoverAddress(transaction, !releaseSpec.ValidateChainId); + receipt.Sender = transaction.SenderAddress ?? (force ? _ecdsa.RecoverAddress(transaction, !releaseSpec.ValidateChainId) : null); receipt.Recipient = transaction.IsContractCreation ? null : transaction.To; // how would it be in CREATE2? - receipt.ContractAddress = transaction.IsContractCreation ? ContractAddress.From(receipt.Sender, transaction.Nonce) : null; + receipt.ContractAddress = transaction.IsContractCreation && transaction.SenderAddress is not null ? ContractAddress.From(receipt.Sender, transaction.Nonce) : null; receipt.GasUsed = receipt.GasUsedTotal - gasUsedBefore; if (receipt.StatusCode != StatusCode.Success) { diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecoveryResult.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecoveryResult.cs new file mode 100644 index 00000000000..13335e07bb6 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecoveryResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2022 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +namespace Nethermind.Blockchain.Receipts; + +public enum ReceiptsRecoveryResult +{ + Success, + Fail, + Skipped +} diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISnapSyncPeer.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISnapSyncPeer.cs new file mode 100644 index 00000000000..6c95a95aa1f --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISnapSyncPeer.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Blockchain.Synchronization +{ + public interface ISnapSyncPeer + { + Task GetAccountRange(AccountRange range, CancellationToken token); + Task GetStorageRange(StorageRange range, CancellationToken token); + Task GetByteCodes(Keccak[] codeHashes, CancellationToken token); + Task GetTrieNodes(AccountsToRefreshRequest request, CancellationToken token); + } +} diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs index 670dfb45e76..18ab74a6fe7 100644 --- a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs @@ -88,6 +88,9 @@ public interface ISyncConfig : IConfig [ConfigItem(Description = "Enables witness protocol.", DefaultValue = "false")] public bool WitnessProtocolEnabled { get; set; } + [ConfigItem(Description = "Enables SNAP sync protocol.", DefaultValue = "false")] + public bool SnapSync { get; set; } + [ConfigItem(Description = "[ONLY FOR MISSING RECEIPTS ISSUE] Turns on receipts validation that checks for ones that might be missing due to previous bug. It downloads them from network if needed." + "If used please check that PivotNumber is same as original used when syncing the node as its used as a cut-off point.", DefaultValue = "false")] public bool FixReceipts { get; set; } diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncPeer.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncPeer.cs index 58dc9b3896d..50123ca613e 100644 --- a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncPeer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncPeer.cs @@ -40,6 +40,7 @@ public interface ISyncPeer : ITxPoolPeer, IPeerWithSatelliteProtocol long HeadNumber { get; set; } UInt256 TotalDifficulty { get; set; } bool IsInitialized { get; set; } + bool IsPriority { get; set; } void Disconnect(DisconnectReason reason, string details); Task GetBlockBodies(IReadOnlyList blockHashes, CancellationToken token); Task GetBlockHeaders(long number, int maxBlocks, int skip, CancellationToken token); diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs index e5b0895f240..9fdb9b9770a 100644 --- a/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs @@ -50,6 +50,7 @@ public bool SynchronizationEnabled public string PivotNumber { get; set; } public string PivotHash { get; set; } public bool WitnessProtocolEnabled { get; set; } = false; + public bool SnapSync { get; set; } = false; public bool FixReceipts { get; set; } = false; public bool BlockGossipEnabled { get; set; } = true; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs index 35aab9c1cba..6e55776bbc6 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs @@ -60,6 +60,7 @@ public class BlockchainProcessor : IBlockchainProcessor, IBlockProcessingQueue private int _currentRecoveryQueueSize; private readonly CompositeBlockTracer _compositeBlockTracer = new(); + private Stopwatch _stopwatch = new(); /// /// @@ -237,7 +238,6 @@ private void RunRecoveryLoop() private void RunProcessingLoop() { - _stats.Start(); if (_logger.IsDebug) _logger.Debug($"Starting block processor - {_blockQueue.Count} blocks waiting in the queue."); @@ -253,6 +253,7 @@ private void RunProcessingLoop() Block block = blockRef.Block; if (_logger.IsTrace) _logger.Trace($"Processing block {block.ToString(Block.Format.Short)})."); + _stats.Start(); Block processedBlock = Process(block, blockRef.ProcessingOptions, _compositeBlockTracer.GetTracer()); if (processedBlock == null) @@ -313,9 +314,8 @@ private void FireProcessingQueueEmpty() ProcessingBranch processingBranch = PrepareProcessingBranch(suggestedBlock, options); PrepareBlocksToProcess(suggestedBlock, options, processingBranch); - Stopwatch stopwatch = Stopwatch.StartNew(); + _stopwatch.Restart(); Block[]? processedBlocks = ProcessBranch(processingBranch, suggestedBlock, options, tracer); - stopwatch.Stop(); if (processedBlocks == null) { return null; @@ -343,7 +343,7 @@ private void FireProcessingQueueEmpty() $"Updating main chain: {lastProcessed}, blocks count: {processedBlocks.Length}"); _blockTree.UpdateMainChain(processingBranch.Blocks.ToArray(), true); - Metrics.LastBlockProcessingTimeInMs = stopwatch.ElapsedMilliseconds; + Metrics.LastBlockProcessingTimeInMs = _stopwatch.ElapsedMilliseconds; } if ((options & ProcessingOptions.MarkAsProcessed) != 0) @@ -353,7 +353,7 @@ private void FireProcessingQueueEmpty() $"Marked blocks as processed {lastProcessed}, blocks count: {processedBlocks.Length}"); _blockTree.MarkChainAsProcessed(processingBranch.Blocks.ToArray()); - Metrics.LastBlockProcessingTimeInMs = stopwatch.ElapsedMilliseconds; + Metrics.LastBlockProcessingTimeInMs = _stopwatch.ElapsedMilliseconds; } if ((options & ProcessingOptions.ReadOnlyChain) == ProcessingOptions.None) @@ -455,7 +455,7 @@ void DeleteInvalidBlocks(Keccak invalidBlockHash) finally { - if (invalidBlockHash is not null) + if (invalidBlockHash is not null && (options & ProcessingOptions.ReadOnlyChain) == 0) { DeleteInvalidBlocks(invalidBlockHash); } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs index 659a0f7a308..ecb9fc155de 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs @@ -26,8 +26,10 @@ internal class ProcessingStats { private readonly ILogger _logger; private readonly Stopwatch _processingStopwatch = new(); + private readonly Stopwatch _runStopwatch = new(); private long _lastBlockNumber; - private long _lastElapsedTicks; + private long _lastElapsedProcessingTicks; + private long _lastElapsedRunningTicks; private decimal _lastTotalMGas; private long _lastTotalTx; private long _lastStateDbReads; @@ -60,6 +62,8 @@ public void UpdateStats(Block? block, int recoveryQueueSize, int blockQueueSize) return; } + _processingStopwatch.Stop(); + if (_lastBlockNumber == 0) { _lastBlockNumber = block.Number; @@ -72,22 +76,19 @@ public void UpdateStats(Block? block, int recoveryQueueSize, int blockQueueSize) Metrics.ProcessingQueueSize = blockQueueSize; long currentTicks = _processingStopwatch.ElapsedTicks; - decimal totalMicroseconds = _processingStopwatch.ElapsedTicks * (1_000_000m / Stopwatch.Frequency); - decimal chunkMicroseconds = (_processingStopwatch.ElapsedTicks - _lastElapsedTicks) * (1_000_000m / Stopwatch.Frequency); + decimal totalMicroseconds = currentTicks * (1_000_000m / Stopwatch.Frequency); + decimal chunkMicroseconds = (currentTicks - _lastElapsedProcessingTicks) * (1_000_000m / Stopwatch.Frequency); + + long runningTicks = _runStopwatch.ElapsedTicks; + decimal runMicroseconds = (runningTicks - _lastElapsedRunningTicks) * (1_000_000m / Stopwatch.Frequency); - if (chunkMicroseconds > 1 * 1000 * 1000) + if (runMicroseconds > 1 * 1000 * 1000) { - long currentGen0 = GC.CollectionCount(0); - long currentGen1 = GC.CollectionCount(1); - long currentGen2 = GC.CollectionCount(2); - long currentMemory = GC.GetTotalMemory(false); - _maxMemory = Math.Max(_maxMemory, currentMemory); long currentStateDbReads = Db.Metrics.StateDbReads; long currentStateDbWrites = Db.Metrics.StateDbWrites; long currentTreeNodeRlp = Trie.Metrics.TreeNodeRlpEncodings + Trie.Metrics.TreeNodeRlpDecodings; long evmExceptions = Evm.Metrics.EvmExceptions; long currentSelfDestructs = Evm.Metrics.SelfDestructs; - long chunkTx = Metrics.Transactions - _lastTotalTx; long chunkBlocks = Metrics.Blocks - _lastBlockNumber; decimal chunkMGas = Metrics.Mgas - _lastTotalMGas; @@ -101,16 +102,25 @@ public void UpdateStats(Block? block, int recoveryQueueSize, int blockQueueSize) decimal txps = chunkMicroseconds == 0 ? -1 : chunkTx / chunkMicroseconds * 1000m * 1000m; decimal bps = chunkMicroseconds == 0 ? -1 : chunkBlocks / chunkMicroseconds * 1000m * 1000m; - if (_logger.IsInfo) _logger.Info($"Processed {block.Number,9} | {(chunkMicroseconds == 0 ? -1 : chunkMicroseconds / 1000),7:N0}ms, mgasps {mgasPerSecond,7:F2} total {totalMgasPerSecond,7:F2}, tps {txps,7:F2} total {totalTxPerSecond,7:F2}, bps {bps,7:F2} total {totalBlocksPerSecond,7:F2}, recv queue {recoveryQueueSize}, proc queue {blockQueueSize}"); - if (_logger.IsDebug) _logger.Trace($"Gen0 {currentGen0 - _lastGen0,6}, Gen1 {currentGen1 - _lastGen1,6}, Gen2 {currentGen2 - _lastGen2,6}, maxmem {_maxMemory / 1000000,5}, mem {currentMemory / 1000000,5}, reads {currentStateDbReads - _lastStateDbReads,9}, writes {currentStateDbWrites - _lastStateDbWrites,9}, rlp {currentTreeNodeRlp - _lastTreeNodeRlp,9}, exceptions {evmExceptions - _lastEvmExceptions}, selfdstrcs {currentSelfDestructs - _lastSelfDestructs}"); + if (_logger.IsInfo) _logger.Info($"Processed {block.Number,9} | {(chunkMicroseconds == 0 ? -1 : chunkMicroseconds / 1000),7:N0}ms of {(runMicroseconds == 0 ? -1 : runMicroseconds / 1000),7:N0}ms, mgasps {mgasPerSecond,7:F2} total {totalMgasPerSecond,7:F2}, tps {txps,7:F2} total {totalTxPerSecond,7:F2}, bps {bps,7:F2} total {totalBlocksPerSecond,7:F2}, recv queue {recoveryQueueSize}, proc queue {blockQueueSize}"); + if (_logger.IsDebug) + { + long currentGen0 = GC.CollectionCount(0); + long currentGen1 = GC.CollectionCount(1); + long currentGen2 = GC.CollectionCount(2); + long currentMemory = GC.GetTotalMemory(false); + _maxMemory = Math.Max(_maxMemory, currentMemory); + _logger.Trace($"Gen0 {currentGen0 - _lastGen0,6}, Gen1 {currentGen1 - _lastGen1,6}, Gen2 {currentGen2 - _lastGen2,6}, maxmem {_maxMemory / 1000000,5}, mem {currentMemory / 1000000,5}, reads {currentStateDbReads - _lastStateDbReads,9}, writes {currentStateDbWrites - _lastStateDbWrites,9}, rlp {currentTreeNodeRlp - _lastTreeNodeRlp,9}, exceptions {evmExceptions - _lastEvmExceptions}, selfdstrcs {currentSelfDestructs - _lastSelfDestructs}"); + _lastGen0 = currentGen0; + _lastGen1 = currentGen1; + _lastGen2 = currentGen2; + } _lastBlockNumber = Metrics.Blocks; _lastTotalMGas = Metrics.Mgas; - _lastElapsedTicks = currentTicks; + _lastElapsedProcessingTicks = currentTicks; + _lastElapsedRunningTicks = runningTicks; _lastTotalTx = Metrics.Transactions; - _lastGen0 = currentGen0; - _lastGen1 = currentGen1; - _lastGen2 = currentGen2; _lastStateDbReads = currentStateDbReads; _lastStateDbWrites = currentStateDbWrites; _lastTreeNodeRlp = currentTreeNodeRlp; @@ -122,6 +132,7 @@ public void UpdateStats(Block? block, int recoveryQueueSize, int blockQueueSize) public void Start() { _processingStopwatch.Start(); + _runStopwatch.Start(); } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs new file mode 100644 index 00000000000..09a474fd307 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + + +using System; +using System.Net; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State; +using Nethermind.State.Snap; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Core.Test.Builders +{ + public static partial class TestItem + { + public static class Tree + { + public static Keccak AccountAddress0 = new Keccak("0000000000000000000000000000000000000000000000000000000001101234"); + + private static readonly Account _account0 = Build.An.Account.WithBalance(0).TestObject; + private static readonly Account _account1 = Build.An.Account.WithBalance(1).TestObject; + private static readonly Account _account2 = Build.An.Account.WithBalance(2).TestObject; + private static readonly Account _account3 = Build.An.Account.WithBalance(3).TestObject; + private static readonly Account _account4 = Build.An.Account.WithBalance(4).TestObject; + private static readonly Account _account5 = Build.An.Account.WithBalance(5).TestObject; + + public static PathWithAccount[] AccountsWithPaths = new PathWithAccount[] + { + new PathWithAccount(AccountAddress0, _account0), + new PathWithAccount(new Keccak("0000000000000000000000000000000000000000000000000000000001112345"), _account1), + new PathWithAccount(new Keccak("0000000000000000000000000000000000000000000000000000000001113456"), _account2), + new PathWithAccount(new Keccak("0000000000000000000000000000000000000000000000000000000001114567"), _account3), + new PathWithAccount(new Keccak("0000000000000000000000000000000000000000000000000000000001123456"), _account4), + new PathWithAccount(new Keccak("0000000000000000000000000000000000000000000000000000000001123457"), _account5), + }; + + public static PathWithStorageSlot[] SlotsWithPaths = new PathWithStorageSlot[] + { + new PathWithStorageSlot(new Keccak("0000000000000000000000000000000000000000000000000000000001101234"), Rlp.Encode(Bytes.FromHexString("0xab12000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Keccak("0000000000000000000000000000000000000000000000000000000001112345"), Rlp.Encode(Bytes.FromHexString("0xab34000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Keccak("0000000000000000000000000000000000000000000000000000000001113456"), Rlp.Encode(Bytes.FromHexString("0xab56000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Keccak("0000000000000000000000000000000000000000000000000000000001114567"), Rlp.Encode(Bytes.FromHexString("0xab78000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Keccak("0000000000000000000000000000000000000000000000000000000001123456"), Rlp.Encode(Bytes.FromHexString("0xab90000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), + new PathWithStorageSlot(new Keccak("0000000000000000000000000000000000000000000000000000000001123457"), Rlp.Encode(Bytes.FromHexString("0xab9a000000000000000000000000000000000000000000000000000000000000000000000000000000")).Bytes), + }; + + public static StateTree GetStateTree(ITrieStore? store) + { + store ??= new TrieStore(new MemDb(), LimboLogs.Instance); + + var stateTree = new StateTree(store, LimboLogs.Instance); + + FillStateTreeWithTestAccounts(stateTree); + + return stateTree; + } + + public static void FillStateTreeWithTestAccounts(StateTree stateTree) + { + stateTree.Set(AccountsWithPaths[0].Path, AccountsWithPaths[0].Account); + stateTree.Set(AccountsWithPaths[1].Path, AccountsWithPaths[1].Account); + stateTree.Set(AccountsWithPaths[2].Path, AccountsWithPaths[2].Account); + stateTree.Set(AccountsWithPaths[3].Path, AccountsWithPaths[3].Account); + stateTree.Set(AccountsWithPaths[4].Path, AccountsWithPaths[4].Account); + stateTree.Set(AccountsWithPaths[5].Path, AccountsWithPaths[5].Account); + stateTree.Commit(0); + } + + public static (StateTree stateTree, StorageTree storageTree) GetTrees(ITrieStore? store) + { + store ??= new TrieStore(new MemDb(), LimboLogs.Instance); + + var storageTree = new StorageTree(store, LimboLogs.Instance); + + storageTree.Set(SlotsWithPaths[0].Path, SlotsWithPaths[0].SlotRlpValue, false); + storageTree.Set(SlotsWithPaths[1].Path, SlotsWithPaths[1].SlotRlpValue, false); + storageTree.Set(SlotsWithPaths[2].Path, SlotsWithPaths[2].SlotRlpValue, false); + storageTree.Set(SlotsWithPaths[3].Path, SlotsWithPaths[3].SlotRlpValue, false); + storageTree.Set(SlotsWithPaths[4].Path, SlotsWithPaths[4].SlotRlpValue, false); + storageTree.Set(SlotsWithPaths[5].Path, SlotsWithPaths[5].SlotRlpValue, false); + + storageTree.Commit(0); + + var account = Build.An.Account.WithBalance(1).WithStorageRoot(storageTree.RootHash).TestObject; + + var stateTree = new StateTree(store, LimboLogs.Instance); + stateTree.Set(AccountAddress0, account); + stateTree.Commit(0); + + return (stateTree, storageTree); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestObject.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs similarity index 73% rename from src/Nethermind/Nethermind.Core.Test/Builders/TestObject.cs rename to src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs index eec3e5c7cdd..ddbc57fac17 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TestObject.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -19,13 +19,15 @@ using Nethermind.Core.Crypto; using Nethermind.Crypto; using Nethermind.Int256; +using Nethermind.Serialization.Rlp; namespace Nethermind.Core.Test.Builders { - public static class TestItem + public static partial class TestItem { - private static Random _random = new(); - + public static Random Random { get; } = new(); + private static AccountDecoder _accountDecoder = new(); + static TestItem() { NonZeroBloom = new Bloom(); @@ -49,15 +51,15 @@ static TestItem() public static Keccak KeccakFromNumber(int i) { - UInt256 keccakNumber = (UInt256) i; + UInt256 keccakNumber = (UInt256)i; byte[] keccakBytes = new byte[32]; keccakNumber.ToBigEndian(keccakBytes); return new Keccak(keccakBytes); } - public static byte[] RandomDataA = {1, 2, 3}; - public static byte[] RandomDataB = {4, 5, 6, 7}; - public static byte[] RandomDataC = {1, 2, 8, 9, 10}; + public static byte[] RandomDataA = { 1, 2, 3 }; + public static byte[] RandomDataB = { 4, 5, 6, 7 }; + public static byte[] RandomDataC = { 1, 2, 8, 9, 10 }; public static Keccak KeccakA = Keccak.Compute("A"); public static Keccak KeccakB = Keccak.Compute("B"); @@ -105,19 +107,60 @@ public static Keccak KeccakFromNumber(int i) public static IPEndPoint IPEndPointF = IPEndPoint.Parse("10.0.0.6"); public static Bloom NonZeroBloom; - + public static Address GetRandomAddress(Random? random = null) { byte[] bytes = new byte[20]; - (random ?? _random).NextBytes(bytes); + (random ?? Random).NextBytes(bytes); return new Address(bytes); } - + public static Keccak GetRandomKeccak(Random? random = null) { byte[] bytes = new byte[32]; - (random ?? _random).NextBytes(bytes); + (random ?? Random).NextBytes(bytes); return new Keccak(bytes); } + + public static Account GenerateRandomAccount(Random random = null) + { + random ??= Random; + + Account account = new( + (UInt256)random.Next(1000), + (UInt256)random.Next(1000), + Keccak.EmptyTreeHash, + Keccak.OfAnEmptyString); + + return account; + } + + public static byte[] GenerateRandomAccountRlp(AccountDecoder accountDecoder = null) + { + accountDecoder ??= _accountDecoder; + Account account = GenerateRandomAccount(); + byte[] value = accountDecoder.Encode(account).Bytes; + return value; + } + + public static Account GenerateIndexedAccount(int index) + { + Account account = new( + (UInt256)index, + (UInt256)index, + Keccak.EmptyTreeHash, + Keccak.OfAnEmptyString); + + return account; + } + + public static byte[] GenerateIndexedAccountRlp(int index, AccountDecoder accountDecoder = null) + { + accountDecoder ??= _accountDecoder; + + Account account = GenerateIndexedAccount(index); + byte[] value = accountDecoder.Encode(account).Bytes; + return value; + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockInfoDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockInfoDecoderTests.cs index b83ed0c2080..30ff6f41fb3 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockInfoDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockInfoDecoderTests.cs @@ -31,15 +31,20 @@ public void Can_do_roundtrip(bool valueDecode) { Roundtrip(valueDecode); } - - [TestCase(true)] - [TestCase(false)] - public void Can_do_roundtrip_with_finalization(bool valueDecode) + + [TestCase(true, true, true)] + [TestCase(true, true, false)] + [TestCase(true, false, true)] + [TestCase(true, false, false)] + [TestCase(false, true, true)] + [TestCase(false, true, false)] + [TestCase(false, false, true)] + [TestCase(false, false, false)] + public void Is_Backwards_compatible(bool valueDecode, bool chainWithFinalization, bool isFinalized) { - Rlp.Decoders[typeof(BlockInfo)] = new BlockInfoDecoder(true); - Roundtrip(valueDecode); + RoundtripBackwardsCompatible(valueDecode, chainWithFinalization, isFinalized); } - + [Test] public void Can_handle_nulls() { @@ -52,13 +57,52 @@ private static void Roundtrip(bool valueDecode) { BlockInfo blockInfo = new(TestItem.KeccakA, 1); blockInfo.WasProcessed = true; + blockInfo.IsFinalized = true; + blockInfo.Metadata |= BlockMetadata.Invalid; Rlp rlp = Rlp.Encode(blockInfo); BlockInfo decoded = valueDecode ? Rlp.Decode(rlp.Bytes.AsSpan()) : Rlp.Decode(rlp); Assert.True(decoded.WasProcessed, "0 processed"); + Assert.True((decoded.Metadata & BlockMetadata.Finalized) == BlockMetadata.Finalized, "metadata finalized"); + Assert.True((decoded.Metadata & BlockMetadata.Invalid) == BlockMetadata.Invalid, "metadata invalid"); Assert.AreEqual(TestItem.KeccakA, decoded.BlockHash, "block hash"); Assert.AreEqual(UInt256.One, decoded.TotalDifficulty, "difficulty"); } + + private static void RoundtripBackwardsCompatible(bool valueDecode, bool chainWithFinalization, bool isFinalized) + { + BlockInfo blockInfo = new(TestItem.KeccakA, 1); + blockInfo.WasProcessed = true; + blockInfo.IsFinalized = isFinalized; + + Rlp rlp = BlockInfoEncodeDeprecated(blockInfo, chainWithFinalization); + BlockInfo decoded = valueDecode ? Rlp.Decode(rlp.Bytes.AsSpan()) : Rlp.Decode(rlp); + + Assert.True(decoded.WasProcessed, "0 processed"); + Assert.AreEqual(chainWithFinalization && isFinalized, decoded.IsFinalized, "finalized"); + Assert.AreEqual(TestItem.KeccakA, decoded.BlockHash, "block hash"); + Assert.AreEqual(UInt256.One, decoded.TotalDifficulty, "difficulty"); + } + + public static Rlp BlockInfoEncodeDeprecated(BlockInfo? item, bool chainWithFinalization) + { + if (item == null) + { + return Rlp.OfEmptySequence; + } + + Rlp[] elements = new Rlp[chainWithFinalization ? 4 : 3]; + elements[0] = Rlp.Encode(item.BlockHash); + elements[1] = Rlp.Encode(item.WasProcessed); + elements[2] = Rlp.Encode(item.TotalDifficulty); + + if (chainWithFinalization) + { + elements[3] = Rlp.Encode(item.IsFinalized); + } + + return Rlp.Encode(elements); + } } } diff --git a/src/Nethermind/Nethermind.Core/BlockInfo.cs b/src/Nethermind/Nethermind.Core/BlockInfo.cs index 914f139384c..9d716fb2e2f 100644 --- a/src/Nethermind/Nethermind.Core/BlockInfo.cs +++ b/src/Nethermind/Nethermind.Core/BlockInfo.cs @@ -14,11 +14,20 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . +using System; using Nethermind.Core.Crypto; using Nethermind.Int256; namespace Nethermind.Core { + [Flags] + public enum BlockMetadata + { + None = 0x0, + Finalized = 0x1, + Invalid = 0x2, + } + public class BlockInfo { public BlockInfo(Keccak blockHash, in UInt256 totalDifficulty) @@ -30,11 +39,27 @@ public BlockInfo(Keccak blockHash, in UInt256 totalDifficulty) public UInt256 TotalDifficulty { get; set; } public bool WasProcessed { get; set; } - + public Keccak BlockHash { get; } - - public bool IsFinalized { get; set; } - + + public bool IsFinalized + { + get => (Metadata & BlockMetadata.Finalized) == BlockMetadata.Finalized; + set + { + if (value) + { + Metadata |= BlockMetadata.Finalized; + } + else + { + Metadata &= ~BlockMetadata.Finalized; + } + } + } + + public BlockMetadata Metadata { get; set; } + /// /// This property is not serialized /// diff --git a/src/Nethermind/Nethermind.Core/Crypto/Keccak.cs b/src/Nethermind/Nethermind.Core/Crypto/Keccak.cs index d56d103fdd7..8c8e5843820 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Keccak.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Keccak.cs @@ -91,6 +91,11 @@ public class Keccak : IEquatable, IComparable /// public static Keccak Zero { get; } = new(new byte[Size]); + /// + /// 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + /// + public static Keccak MaxValue { get; } = new("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + public byte[] Bytes { get; } public Keccak(string hexString) @@ -206,6 +211,26 @@ public override int GetHashCode() return !(a == b); } + public static bool operator >(Keccak? k1, Keccak? k2) + { + return Extensions.Bytes.Comparer.Compare(k1?.Bytes, k2?.Bytes) > 0; + } + + public static bool operator <(Keccak? k1, Keccak? k2) + { + return Extensions.Bytes.Comparer.Compare(k1?.Bytes, k2?.Bytes) < 0; + } + + public static bool operator >=(Keccak? k1, Keccak? k2) + { + return Extensions.Bytes.Comparer.Compare(k1?.Bytes, k2?.Bytes) >= 0; + } + + public static bool operator <=(Keccak? k1, Keccak? k2) + { + return Extensions.Bytes.Comparer.Compare(k1?.Bytes, k2?.Bytes) <= 0; + } + public KeccakStructRef ToStructRef() => new(Bytes); } diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 15069c1fdaa..75d3b7c610b 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -34,7 +34,7 @@ public static unsafe partial class Bytes { public static readonly IEqualityComparer EqualityComparer = new BytesEqualityComparer(); - public static readonly IComparer Comparer = new BytesComparer(); + public static readonly BytesComparer Comparer = new(); private class BytesEqualityComparer : EqualityComparer { @@ -49,7 +49,7 @@ public override int GetHashCode(byte[] obj) } } - private class BytesComparer : Comparer + public class BytesComparer : Comparer { public override int Compare(byte[]? x, byte[]? y) { @@ -84,6 +84,30 @@ public override int Compare(byte[]? x, byte[]? y) return y.Length > x.Length ? 1 : 0; } + + public int Compare(Span x, Span y) + { + if (x.Length == 0) + { + return y.Length == 0 ? 0 : 1; + } + + for (int i = 0; i < x.Length; i++) + { + if (y.Length <= i) + { + return -1; + } + + int result = x[i].CompareTo(y[i]); + if (result != 0) + { + return result; + } + } + + return y.Length > x.Length ? 1 : 0; + } } public static readonly byte[] Zero32 = new byte[32]; diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs new file mode 100644 index 00000000000..262f7edc422 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs @@ -0,0 +1,128 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Linq; +using BenchmarkDotNet.Attributes; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Db; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; +using Nethermind.State; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Evm.Benchmark; + +public class MultipleUnsignedOperations +{ + private readonly IReleaseSpec _spec = MainnetSpecProvider.Instance.GetSpec(MainnetSpecProvider.IstanbulBlockNumber); + private readonly ITxTracer _txTracer = NullTxTracer.Instance; + private ExecutionEnvironment _environment; + private IVirtualMachine _virtualMachine; + private readonly BlockHeader _header = new(Keccak.Zero, Keccak.Zero, Address.Zero, UInt256.One, MainnetSpecProvider.MuirGlacierBlockNumber, Int64.MaxValue, UInt256.One, Bytes.Empty); + private readonly IBlockhashProvider _blockhashProvider = new TestBlockhashProvider(); + private EvmState _evmState; + private StateProvider _stateProvider; + private StorageProvider _storageProvider; + private WorldState _worldState; + + private readonly byte[] _bytecode = Prepare.EvmCode + .PushData(2) + .PushData(2) + .Op(Instruction.ADD) + + .PushData(2) + .Op(Instruction.MUL) + + .PushData(2) + .Op(Instruction.DIV) + + .PushData(2) + .Op(Instruction.SUB) + + .PushData(2) + .PushData(2) + .Op(Instruction.ADDMOD) + + .PushData(2) + .PushData(2) + .Op(Instruction.MULMOD) + + .PushData(2) + .Op(Instruction.LT) + + .PushData(2) + .Op(Instruction.GT) + .Op(Instruction.POP) + + .Op(Instruction.GAS) + .Op(Instruction.POP) + + .Done; + + [GlobalSetup] + public void GlobalSetup() + { + TrieStore trieStore = new(new MemDb(), new OneLoggerLogManager(NullLogger.Instance)); + IKeyValueStore codeDb = new MemDb(); + + _stateProvider = new StateProvider(trieStore, codeDb, new OneLoggerLogManager(NullLogger.Instance)); + _stateProvider.CreateAccount(Address.Zero, 1000.Ether()); + _stateProvider.Commit(_spec); + + _storageProvider = new StorageProvider(trieStore, _stateProvider, new OneLoggerLogManager(NullLogger.Instance)); + + _worldState = new WorldState(_stateProvider, _storageProvider); + Console.WriteLine(MuirGlacier.Instance); + _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, new OneLoggerLogManager(NullLogger.Instance)); + + _environment = new ExecutionEnvironment + { + ExecutingAccount = Address.Zero, + CodeSource = Address.Zero, + Caller = Address.Zero, + CodeInfo = new CodeInfo(_bytecode.Concat(_bytecode).Concat(_bytecode).Concat(_bytecode).ToArray()), + Value = 0, + TransferValue = 0, + TxExecutionContext = new TxExecutionContext(_header, Address.Zero, 0) + }; + + _evmState = new EvmState(100_000_000L, _environment, ExecutionType.Transaction, true, _worldState.TakeSnapshot(), false); + } + + [Benchmark] + public void ExecuteCode() + { + _virtualMachine.Run(_evmState, _worldState, _txTracer); + _stateProvider.Reset(); + _storageProvider.Reset(); + } + + [Benchmark(Baseline = true)] + public void No_machine_running() + { + _stateProvider.Reset(); + _storageProvider.Reset(); + } +} diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 805c17891ce..f3db41295a2 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -592,6 +592,7 @@ private CallResult ExecutePrecompile(EvmState state, IReleaseSpec spec) } } + [SkipLocalsInit] private CallResult ExecuteCall(EvmState vmState, byte[]? previousCallResult, ZeroPaddedSpan previousCallOutput, in UInt256 previousCallOutputDestination, IReleaseSpec spec) { bool isTrace = _logger.IsTrace; diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs index 7d23d515614..8694f6cc1df 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs @@ -34,6 +34,7 @@ public class LogFinder : ILogFinder private static int ParallelLock = 0; private readonly IReceiptFinder _receiptFinder; + private readonly IReceiptStorage _receiptStorage; private readonly IBloomStorage _bloomStorage; private readonly IReceiptsRecovery _receiptsRecovery; private readonly int _maxBlockDepth; @@ -41,16 +42,17 @@ public class LogFinder : ILogFinder private readonly IBlockFinder _blockFinder; private readonly ILogger _logger; - public LogFinder( - IBlockFinder? blockFinder, - IReceiptFinder? receiptFinder, - IBloomStorage? bloomStorage, - ILogManager? logManager, - IReceiptsRecovery? receiptsRecovery, + public LogFinder(IBlockFinder? blockFinder, + IReceiptFinder? receiptFinder, + IReceiptStorage? receiptStorage, + IBloomStorage? bloomStorage, + ILogManager? logManager, + IReceiptsRecovery? receiptsRecovery, int maxBlockDepth = 1000) { _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); + _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage));; _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); @@ -291,7 +293,10 @@ void RecoverReceiptsData(Keccak hash, TxReceipt[] receipts) var block = _blockFinder.FindBlock(hash, BlockTreeLookupOptions.TotalDifficultyNotNeeded); if (block != null) { - _receiptsRecovery.TryRecover(block, receipts); + if (_receiptsRecovery.TryRecover(block, receipts) == ReceiptsRecoveryResult.Success) + { + _receiptStorage.Insert(block, receipts); + } } } } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs index eb5228da1b7..8e645dadd0b 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs @@ -90,6 +90,7 @@ public Task Execute(CancellationToken cancellationToken) LogFinder logFinder = new( blockTree, receiptFinder, + receiptStorage, bloomStorage, _get.LogManager, new ReceiptsRecovery(_get.EthereumEcdsa, _get.SpecProvider), diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs index cc90070cd97..a864c4a45a7 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs @@ -43,12 +43,14 @@ using Nethermind.Network.Rlpx; using Nethermind.Network.Rlpx.Handshake; using Nethermind.Network.StaticNodes; +using Nethermind.State.Snap; using Nethermind.Stats.Model; using Nethermind.Synchronization; using Nethermind.Synchronization.Blocks; using Nethermind.Synchronization.LesSync; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.SnapSync; namespace Nethermind.Init.Steps { @@ -111,24 +113,27 @@ private async Task Initialize(CancellationToken cancellationToken) _networkConfig.NettyArenaOrder.ToString()); CanonicalHashTrie cht = new CanonicalHashTrie(_api.DbProvider!.ChtDb); - + + int maxPeersCount = _networkConfig.ActivePeersMaxCount; + int maxPriorityPeersCount = _networkConfig.PriorityPeersMaxCount; + _api.SyncPeerPool = new SyncPeerPool(_api.BlockTree!, _api.NodeStatsManager!, _api.BetterPeerStrategy, maxPeersCount, maxPriorityPeersCount, SyncPeerPool.DefaultUpgradeIntervalInMs, _api.LogManager); + _api.DisposeStack.Push(_api.SyncPeerPool); + + ProgressTracker progressTracker = new(_api.BlockTree, _api.DbProvider.StateDb, _api.LogManager); + _api.SnapProvider = new SnapProvider(progressTracker, _api.DbProvider, _api.LogManager); SyncProgressResolver syncProgressResolver = new( _api.BlockTree!, _api.ReceiptStorage!, _api.DbProvider.StateDb, _api.ReadOnlyTrieStore!, + progressTracker, _syncConfig, _api.LogManager); _api.SyncProgressResolver = syncProgressResolver; _api.BetterPeerStrategy = new TotalDifficultyBasedBetterPeerStrategy(_api.SyncProgressResolver, _api.LogManager); - int maxPeersCount = _networkConfig.ActivePeersMaxCount; - _api.SyncPeerPool = - new SyncPeerPool(_api.BlockTree!, _api.NodeStatsManager!, _api.BetterPeerStrategy, maxPeersCount, _api.LogManager); - _api.DisposeStack.Push(_api.SyncPeerPool); - IEnumerable synchronizationPlugins = _api.GetSynchronizationPlugins(); foreach (ISynchronizationPlugin plugin in synchronizationPlugins) { @@ -160,6 +165,7 @@ private async Task Initialize(CancellationToken cancellationToken) _api.NodeStatsManager!, _api.SyncModeSelector, _syncConfig, + _api.SnapProvider, _api.BlockDownloaderFactory, _api.Pivot, _api.LogManager); @@ -199,6 +205,12 @@ await InitPeer().ContinueWith(initPeerTask => } }); + if (_syncConfig.SnapSync) + { + SnapCapabilitySwitcher snapCapabilitySwitcher = new(_api.ProtocolsManager, progressTracker); + snapCapabilitySwitcher.EnableSnapCapabilityUntilSynced(); + } + if (cancellationToken.IsCancellationRequested) { return; @@ -540,7 +552,6 @@ private async Task InitPeer() if (t.IsFaulted) { _logger.Error($"ENR discovery failed: {t.Exception}"); - } }); diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index 5ed2f84b3fa..f68dbdbdbdf 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -207,6 +207,7 @@ public virtual async Task Execute(CancellationToken cancellationToken) _api.BlockTree, _api.TxPool, _api.ReceiptStorage, + _api.ReceiptFinder, _api.FilterStore, _api.EthSyncingInfo!, _api.SpecProvider, diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index d1de1c504dd..c05debef10a 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -112,6 +112,7 @@ TransactionProcessor transactionProcessor LogFinder logFinder = new( blockTree, new InMemoryReceiptStorage(), + new InMemoryReceiptStorage(), bloomStorage, LimboLogs.Instance, new ReceiptsRecovery(ecdsa, specProvider)); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs index 1c04dc1cb6d..768d2644c08 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs @@ -798,10 +798,10 @@ private CallResultWithProof TestCallWithCode(byte[] code, Address from = null) foreach (AccountProof accountProof in callResultWithProof.Accounts) { - ProofVerifier.Verify(accountProof.Proof, block.StateRoot); + ProofVerifier.VerifyOneProof(accountProof.Proof, block.StateRoot); foreach (StorageProof storageProof in accountProof.StorageProofs) { - ProofVerifier.Verify(storageProof.Proof, accountProof.StorageRoot); + ProofVerifier.VerifyOneProof(storageProof.Proof, accountProof.StorageRoot); } } @@ -870,7 +870,7 @@ private void TestCallWithStorageAndCode(byte[] code, UInt256 gasPrice, Address f Account account; try { - account = new AccountDecoder().Decode(new RlpStream(ProofVerifier.Verify(accountProof.Proof, block.StateRoot))); + account = new AccountDecoder().Decode(new RlpStream(ProofVerifier.VerifyOneProof(accountProof.Proof, block.StateRoot))); } catch (Exception) { @@ -880,7 +880,7 @@ private void TestCallWithStorageAndCode(byte[] code, UInt256 gasPrice, Address f foreach (StorageProof storageProof in accountProof.StorageProofs) { // we read the values here just to allow easier debugging so you can confirm that the value is same as the one in the proof and in the trie - byte[] value = ProofVerifier.Verify(storageProof.Proof, accountProof.StorageRoot); + byte[] value = ProofVerifier.VerifyOneProof(storageProof.Proof, accountProof.StorageRoot); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs index ade56d760f0..12acdcb30d8 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs @@ -56,6 +56,7 @@ public class SubscribeModuleTests private IBlockTree _blockTree; private ITxPool _txPool; private IReceiptStorage _receiptStorage; + private IReceiptFinder _receiptFinder; private IFilterStore _filterStore; private ISubscriptionManager _subscriptionManager; private IJsonRpcDuplexClient _jsonRpcDuplexClient; @@ -69,6 +70,7 @@ public void Setup() _blockTree = Substitute.For(); _txPool = Substitute.For(); _receiptStorage = Substitute.For(); + _receiptFinder = Substitute.For(); _specProvider = Substitute.For(); _filterStore = new FilterStore(); _jsonRpcDuplexClient = Substitute.For(); @@ -82,6 +84,7 @@ public void Setup() _blockTree, _txPool, _receiptStorage, + _receiptFinder, _filterStore, new EthSyncingInfo(_blockTree), _specProvider, @@ -123,7 +126,7 @@ private JsonRpcResult GetBlockAddedToMainResult(BlockReplacementEventArgs blockR private List GetLogsSubscriptionResult(Filter filter, BlockEventArgs blockEventArgs, out string subscriptionId) { - LogsSubscription logsSubscription = new(_jsonRpcDuplexClient, _receiptStorage, _filterStore, _blockTree, _logManager, filter); + LogsSubscription logsSubscription = new(_jsonRpcDuplexClient, _receiptStorage, _receiptFinder, _filterStore, _blockTree, _logManager, filter); List jsonRpcResults = new(); @@ -437,7 +440,7 @@ public void LogsSubscription_with_null_arguments_on_NewHeadBlock_event() LogEntry logEntry = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA).WithData(TestItem.RandomDataA).TestObject; TxReceipt[] txReceipts = {Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntry).TestObject}; - _receiptStorage.Get(Arg.Any()).Returns(txReceipts); + _receiptFinder.Get(Arg.Any()).Returns(txReceipts); Block block = Build.A.Block.WithNumber(blockNumber).TestObject; BlockEventArgs blockEventArgs = new(block); @@ -479,7 +482,7 @@ public void LogsSubscription_with_null_arguments_on_NewHeadBlock_event_with_one_ LogEntry logEntryC = Build.A.LogEntry.WithData(TestItem.RandomDataC).TestObject; TxReceipt[] txReceipts = {Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryB, logEntryC).TestObject}; - _receiptStorage.Get(Arg.Any()).Returns(txReceipts); + _receiptFinder.Get(Arg.Any()).Returns(txReceipts); Block block = Build.A.Block.WithNumber(blockNumber).TestObject; BlockEventArgs blockEventArgs = new(block); @@ -517,7 +520,7 @@ public void LogsSubscription_with_null_arguments_on_NewHeadBlock_event_with_few_ Build.A.Receipt.WithBlockNumber(blockNumber).WithIndex(33).WithLogs(logEntryB, logEntryC).TestObject }; - _receiptStorage.Get(Arg.Any()).Returns(txReceipts); + _receiptFinder.Get(Arg.Any()).Returns(txReceipts); Block block = Build.A.Block.WithNumber(blockNumber).TestObject; BlockEventArgs blockEventArgs = new(block); @@ -571,7 +574,7 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryC, logEntryB, logEntryA, logEntryC).TestObject, }; - _receiptStorage.Get(Arg.Any()).Returns(txReceipts); + _receiptFinder.Get(Arg.Any()).Returns(txReceipts); Block block = Build.A.Block.WithNumber(blockNumber).WithBloom(new Bloom(txReceipts.Select(r => r.Bloom).ToArray())).TestObject; BlockEventArgs blockEventArgs = new(block); @@ -618,7 +621,7 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryC, logEntryB, logEntryA, logEntryC).TestObject, }; - _receiptStorage.Get(Arg.Any()).Returns(txReceipts); + _receiptFinder.Get(Arg.Any()).Returns(txReceipts); Block block = Build.A.Block.WithNumber(blockNumber).WithBloom(new Bloom(txReceipts.Select(r => r.Bloom).ToArray())).TestObject; BlockEventArgs blockEventArgs = new(block); @@ -668,7 +671,7 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryC, logEntryB, logEntryE, logEntryA, logEntryB).TestObject, }; - _receiptStorage.Get(Arg.Any()).Returns(txReceipts); + _receiptFinder.Get(Arg.Any()).Returns(txReceipts); Block block = Build.A.Block.WithNumber(blockNumber).WithBloom(new Bloom(txReceipts.Select(r => r.Bloom).ToArray())).TestObject; BlockEventArgs blockEventArgs = new(block); @@ -695,18 +698,18 @@ public void LogsSubscription_should_not_send_logs_of_new_txs_on_ReceiptsInserted int blockNumber = 55555; Filter filter = null; - LogsSubscription logsSubscription = new(_jsonRpcDuplexClient, _receiptStorage, _filterStore, _blockTree, _logManager, filter); + LogsSubscription logsSubscription = new(_jsonRpcDuplexClient, _receiptStorage, _receiptFinder, _filterStore, _blockTree, _logManager, filter); LogEntry logEntry = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA).WithData(TestItem.RandomDataA).TestObject; TxReceipt[] txReceipts = {Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntry).TestObject}; BlockHeader blockHeader = Build.A.BlockHeader.WithNumber(blockNumber).TestObject; Block block = Build.A.Block.WithHeader(blockHeader).TestObject; - _receiptStorage.Get(Arg.Any()).Returns(txReceipts); + _receiptFinder.Get(Arg.Any()).Returns(txReceipts); List jsonRpcResults = new(); ManualResetEvent manualResetEvent = new(false); - ReceiptsEventArgs receiptsEventArgs = new(blockHeader, txReceipts); + ReceiptsEventArgs receiptsEventArgs = new(blockHeader, txReceipts, txReceipts[0].Removed); logsSubscription.JsonRpcDuplexClient.SendJsonRpcResult(Arg.Do(j => { jsonRpcResults.Add(j); @@ -1067,7 +1070,7 @@ public void LogsSubscription_can_send_logs_with_removed_txs_when_inserted() int blockNumber = 55555; Filter filter = null; - LogsSubscription logsSubscription = new(_jsonRpcDuplexClient, _receiptStorage, _filterStore, _blockTree, _logManager, filter); + LogsSubscription logsSubscription = new(_jsonRpcDuplexClient, _receiptStorage, _receiptFinder, _filterStore, _blockTree, _logManager, filter); LogEntry logEntry = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA).WithData(TestItem.RandomDataA).TestObject; TxReceipt[] txReceipts = {Build.A.Receipt.WithLogs(logEntry).WithBlockNumber(blockNumber).TestObject}; @@ -1087,7 +1090,7 @@ public void LogsSubscription_can_send_logs_with_removed_txs_when_inserted() _receiptStorage.Insert(Arg.Any(), Arg.Do(r => { - ReceiptsEventArgs receiptsEventArgs = new(blockHeader, r); + ReceiptsEventArgs receiptsEventArgs = new(blockHeader, r, r[0].Removed); _receiptStorage.ReceiptsInserted += Raise.EventWith(new object(), receiptsEventArgs); })); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index e53585d296e..2c1815682a0 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -126,7 +126,7 @@ protected override async Task Build(ISpecProvider? specProvider IFilterManager filterManager = new FilterManager(filterStore, BlockProcessor, TxPool, LimboLogs.Instance); ReceiptsRecovery receiptsRecovery = new(new EthereumEcdsa(specProvider.ChainId, LimboLogs.Instance), specProvider); - LogFinder = new LogFinder(BlockTree, ReceiptStorage, bloomStorage, LimboLogs.Instance, receiptsRecovery); + LogFinder = new LogFinder(BlockTree, ReceiptStorage, ReceiptStorage, bloomStorage, LimboLogs.Instance, receiptsRecovery); ReadOnlyTxProcessingEnv processingEnv = new( new ReadOnlyDbProvider(DbProvider, false), diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs index 14354403ff6..6d91b1a5668 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs @@ -32,19 +32,22 @@ namespace Nethermind.JsonRpc.Modules.Subscribe public class LogsSubscription : Subscription { private readonly IReceiptStorage _receiptStorage; + private readonly IReceiptFinder _receiptFinder; private readonly IBlockTree _blockTree; private readonly LogFilter _filter; public LogsSubscription( IJsonRpcDuplexClient jsonRpcDuplexClient, - IReceiptStorage? receiptStorage, - IFilterStore? store, - IBlockTree? blockTree, - ILogManager? logManager, - Filter? filter = null) + IReceiptStorage? receiptStorage, + IReceiptFinder? receiptFinder, + IFilterStore? store, + IBlockTree? blockTree, + ILogManager? logManager, + Filter? filter = null) : base(jsonRpcDuplexClient) { _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); + _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); IFilterStore filterStore = store ?? throw new ArgumentNullException(nameof(store)); @@ -75,13 +78,13 @@ private void OnNewHeadBlock(object? sender, BlockEventArgs e) { if (e.Block is not null) { - TryPublishReceiptsInBackground(e.Block.Header, () => _receiptStorage.Get(e.Block), nameof(_blockTree.NewHeadBlock)); + TryPublishReceiptsInBackground(e.Block.Header, () => _receiptFinder.Get(e.Block), nameof(_blockTree.NewHeadBlock)); } } private void OnReceiptsInserted(object? sender, ReceiptsEventArgs e) { - bool isReceiptRemoved = e.TxReceipts.FirstOrDefault()?.Removed == true; + bool isReceiptRemoved = e.WasRemoved; if (isReceiptRemoved) { TryPublishReceiptsInBackground(e.BlockHeader, () => e.TxReceipts, nameof(_receiptStorage.ReceiptsInserted)); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs index 6c413c67449..3d192261e80 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs @@ -50,6 +50,7 @@ public SubscriptionFactory(ILogManager? logManager, IBlockTree? blockTree, ITxPool? txPool, IReceiptStorage? receiptStorage, + IReceiptFinder? receiptFinder, IFilterStore? filterStore, IEthSyncingInfo ethSyncingInfo, ISpecProvider specProvider, @@ -60,6 +61,7 @@ public SubscriptionFactory(ILogManager? logManager, blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); txPool = txPool ?? throw new ArgumentNullException(nameof(txPool)); receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); + receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); filterStore = filterStore ?? throw new ArgumentNullException(nameof(filterStore)); ethSyncingInfo = ethSyncingInfo ?? throw new ArgumentNullException(nameof(ethSyncingInfo)); specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); @@ -71,7 +73,7 @@ public SubscriptionFactory(ILogManager? logManager, new NewHeadSubscription(jsonRpcDuplexClient, blockTree, logManager, specProvider, args)), [SubscriptionType.Logs] = CreateSubscriptionType((jsonRpcDuplexClient, filter) => - new LogsSubscription(jsonRpcDuplexClient, receiptStorage, filterStore, blockTree, logManager, filter)), + new LogsSubscription(jsonRpcDuplexClient, receiptStorage, receiptFinder, filterStore, blockTree, logManager, filter)), [SubscriptionType.NewPendingTransactions] = CreateSubscriptionType((jsonRpcDuplexClient, args) => new NewPendingTransactionsSubscription(jsonRpcDuplexClient, txPool, logManager, args)), diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs index cc842d564ea..f81805b8793 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs @@ -35,6 +35,7 @@ using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; using Nethermind.Synchronization.Reporting; +using Nethermind.Synchronization.SnapSync; using Nethermind.Trie.Pruning; using NSubstitute; using NUnit.Framework; @@ -85,11 +86,14 @@ public Context( _mergeConfig = mergeConfig ?? new MergeConfig(); _metadataDb = metadataDb ?? new MemDb(); + ProgressTracker progressTracker = new(BlockTree, stateDb, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = new( BlockTree, NullReceiptStorage.Instance, stateDb, new TrieStore(stateDb, LimboLogs.Instance), + progressTracker, _syncConfig, LimboLogs.Instance); TotalDifficultyBasedBetterPeerStrategy bestPeerStrategy = new (syncProgressResolver, LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 12957837e6f..53253d54cff 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -238,6 +238,7 @@ public Task InitSynchronization() _api.NodeStatsManager!, _api.SyncModeSelector, _syncConfig, + _api.SnapProvider, _api.BlockDownloaderFactory, _api.Pivot, _beaconSync, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeSynchronizer.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeSynchronizer.cs index 19403d60062..b3384680ae2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeSynchronizer.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeSynchronizer.cs @@ -15,13 +15,11 @@ // along with the Nethermind. If not, see . // -using System; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Validators; -using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Logging; @@ -32,6 +30,7 @@ using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.SnapSync; namespace Nethermind.Merge.Plugin.Synchronization; @@ -53,6 +52,7 @@ public MergeSynchronizer( INodeStatsManager nodeStatsManager, ISyncModeSelector syncModeSelector, ISyncConfig syncConfig, + ISnapProvider snapProvider, IBlockDownloaderFactory blockDownloaderFactory, IPivot pivot, IMergeSyncController mergeSync, @@ -62,7 +62,7 @@ public MergeSynchronizer( IBlockValidator blockValidator, IBlockProcessingQueue blockProcessingQueue, ILogManager logManager) : base(dbProvider, specProvider, blockTree, receiptStorage, peerPool, nodeStatsManager, - syncModeSelector, syncConfig, blockDownloaderFactory, pivot, logManager) + syncModeSelector, syncConfig, snapProvider, blockDownloaderFactory, pivot, logManager) { _mergeSync = mergeSync; _mergeConfig = mergeConfig; diff --git a/src/Nethermind/Nethermind.Network.Stats/INodeStatsManager.cs b/src/Nethermind/Nethermind.Network.Stats/INodeStatsManager.cs index f569ccb5537..197ed4745b1 100644 --- a/src/Nethermind/Nethermind.Network.Stats/INodeStatsManager.cs +++ b/src/Nethermind/Nethermind.Network.Stats/INodeStatsManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -43,7 +43,8 @@ public enum TransferSpeedType NodeData, Headers, Bodies, - Receipts + Receipts, + SnapRanges } public static class NodeStatsManagerExtension diff --git a/src/Nethermind/Nethermind.Network.Stats/NodeStatsLight.cs b/src/Nethermind/Nethermind.Network.Stats/NodeStatsLight.cs index a81c5456d00..b690a393492 100644 --- a/src/Nethermind/Nethermind.Network.Stats/NodeStatsLight.cs +++ b/src/Nethermind/Nethermind.Network.Stats/NodeStatsLight.cs @@ -31,12 +31,14 @@ public class NodeStatsLight : INodeStats private long _bodiesTransferSpeedEventCount; private long _receiptsTransferSpeedEventCount; private long _nodesTransferSpeedEventCount; + private long _snapRangesTransferSpeedEventCount; private long _latencyEventCount; private decimal? _averageNodesTransferSpeed; private decimal? _averageHeadersTransferSpeed; private decimal? _averageBodiesTransferSpeed; private decimal? _averageReceiptsTransferSpeed; + private decimal? _averageSnapRangesTransferSpeed; private decimal? _averageLatency; private int[] _statCountersArray; @@ -163,6 +165,9 @@ public void AddTransferSpeedCaptureEvent(TransferSpeedType transferSpeedType, lo case TransferSpeedType.Receipts: _averageReceiptsTransferSpeed = ((_receiptsTransferSpeedEventCount * (_averageReceiptsTransferSpeed ?? 0)) + bytesPerMillisecond) / (++_receiptsTransferSpeedEventCount); break; + case TransferSpeedType.SnapRanges: + _averageSnapRangesTransferSpeed = ((_snapRangesTransferSpeedEventCount * (_averageSnapRangesTransferSpeed ?? 0)) + bytesPerMillisecond) / (++_snapRangesTransferSpeedEventCount); + break; default: throw new ArgumentOutOfRangeException(nameof(transferSpeedType), transferSpeedType, null); } @@ -178,6 +183,7 @@ public void AddTransferSpeedCaptureEvent(TransferSpeedType transferSpeedType, lo TransferSpeedType.Headers => _averageHeadersTransferSpeed, TransferSpeedType.Bodies => _averageBodiesTransferSpeed, TransferSpeedType.Receipts => _averageReceiptsTransferSpeed, + TransferSpeedType.SnapRanges => _averageSnapRangesTransferSpeed, _ => throw new ArgumentOutOfRangeException() }); } @@ -191,6 +197,7 @@ public string GetPaddedAverageTransferSpeed(TransferSpeedType transferSpeedType) TransferSpeedType.Headers => $"{_averageHeadersTransferSpeed ?? 0,5:0}", TransferSpeedType.Bodies => $"{_averageBodiesTransferSpeed ?? 0,5:0}", TransferSpeedType.Receipts => $"{_averageReceiptsTransferSpeed ?? 0,5:0}", + TransferSpeedType.SnapRanges => $"{_averageSnapRangesTransferSpeed ?? 0,5:0}", _ => throw new ArgumentOutOfRangeException() }); } diff --git a/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj b/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj index 9bc10cf9433..e8692881f05 100644 --- a/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj +++ b/src/Nethermind/Nethermind.Network.Test/Nethermind.Network.Test.csproj @@ -39,6 +39,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/SerializerTester.cs b/src/Nethermind/Nethermind.Network.Test/P2P/SerializerTester.cs similarity index 86% rename from src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/SerializerTester.cs rename to src/Nethermind/Nethermind.Network.Test/P2P/SerializerTester.cs index 4c5df016600..91cdc191053 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/SerializerTester.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/SerializerTester.cs @@ -14,13 +14,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . +using System; using DotNetty.Buffers; using FluentAssertions; +using FluentAssertions.Equivalency; using Nethermind.Core.Extensions; using Nethermind.Network.P2P.Messages; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; using NUnit.Framework; -namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V62 +namespace Nethermind.Network.Test.P2P { public static class SerializerTester { @@ -46,7 +49,9 @@ public static void TestZero(IZeroMessageSerializer serializer, T message, { serializer.Serialize(buffer, message); T deserialized = serializer.Deserialize(buffer); - deserialized.Should().BeEquivalentTo(message); + + // RlpLength is calculated explicitly when serializing an object by Calculate method. It's null after deserialization. + deserialized.Should().BeEquivalentTo(message, options => options.Excluding(c => c.Name == "RlpLength")); Assert.AreEqual(0, buffer.ReadableBytes, "readable bytes"); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializerTests.cs new file mode 100644 index 00000000000..615546be5e0 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializerTests.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using Nethermind.State.Snap; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class AccountRangeMessageSerializerTests + { + public static readonly byte[] Code0 = { 0, 0 }; + public static readonly byte[] Code1 = { 0, 1 }; + + [Test] + public void Roundtrip_NoAccountsNoProofs() + { + AccountRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + PathsWithAccounts = System.Array.Empty(), + Proofs = Array.Empty() + }; + + AccountRangeMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_Many() + { + var acc01 = Build.An.Account + .WithBalance(1) + .WithCode(Code0) + .WithStorageRoot(new Keccak("0x10d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")) + .TestObject; + var acc02 = Build.An.Account + .WithBalance(2) + .WithCode(Code1) + .WithStorageRoot(new Keccak("0x20d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")) + .TestObject; + + AccountRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + PathsWithAccounts = new[] { new PathWithAccount(TestItem.KeccakA, acc01), new PathWithAccount(TestItem.KeccakB, acc02) }, + Proofs = new[] { TestItem.RandomDataA, TestItem.RandomDataB } + }; + + AccountRangeMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_EmptyStorageRoot() + { + var acc01 = Build.An.Account + .WithBalance(1) + .WithCode(Code0) + .WithStorageRoot(Keccak.EmptyTreeHash) + .TestObject; + + AccountRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + PathsWithAccounts = new[] { new PathWithAccount(TestItem.KeccakB, acc01) }, + Proofs = new[] { TestItem.RandomDataA, TestItem.RandomDataB } + }; + + AccountRangeMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_EmptyCode() + { + + var acc01 = Build.An.Account + .WithBalance(1) + .WithStorageRoot(TestItem.KeccakA) + .TestObject; + + AccountRangeMessage msg = new() { + RequestId = MessageConstants.Random.NextLong(), + PathsWithAccounts = new[] { new PathWithAccount(TestItem.KeccakB, acc01) }, + Proofs = new[] {TestItem.RandomDataA, TestItem.RandomDataB} + }; + + AccountRangeMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializerTests.cs new file mode 100644 index 00000000000..c350086b874 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializerTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class ByteCodesMessageSerializerTests + { + [Test] + public void Roundtrip() + { + byte[][] data = {new byte[]{0xde, 0xad, 0xc0, 0xde}, new byte[]{0xfe, 0xed}}; + + ByteCodesMessage message = new (data); + + ByteCodesMessageSerializer serializer = new (); + + SerializerTester.TestZero(serializer, message); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessageSerializerTests.cs new file mode 100644 index 00000000000..d6cf79022f4 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessageSerializerTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using Nethermind.Core.Crypto; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class GetAccountRangeMessageSerializerTests + { + [Test] + public void Roundtrip() + { + GetAccountRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + AccountRange = new( Keccak.OfAnEmptyString, new Keccak("0x15d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), new Keccak("0x20d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")), + ResponseBytes = 10 + }; + GetAccountRangeMessageSerializer serializer = new(); + + var bytes = serializer.Serialize(msg); + var deserializedMsg = serializer.Deserialize(bytes); + + Assert.AreEqual(msg.RequestId, deserializedMsg.RequestId); + Assert.AreEqual(msg.PacketType, deserializedMsg.PacketType); + Assert.AreEqual(msg.AccountRange.RootHash, deserializedMsg.AccountRange.RootHash); + Assert.AreEqual(msg.AccountRange.StartingHash, deserializedMsg.AccountRange.StartingHash); + Assert.AreEqual(msg.AccountRange.LimitHash, deserializedMsg.AccountRange.LimitHash); + Assert.AreEqual(msg.ResponseBytes, deserializedMsg.ResponseBytes); + + SerializerTester.TestZero(serializer, msg); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetByteCodesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetByteCodesMessageSerializerTests.cs new file mode 100644 index 00000000000..9480b8a4092 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetByteCodesMessageSerializerTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class GetByteCodesMessageSerializerTests + { + [Test] + public void Roundtrip_Many() + { + GetByteCodesMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + Hashes = TestItem.Keccaks, + Bytes = 10 + }; + + GetByteCodesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_Empty() + { + GetByteCodesMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + Hashes = Array.Empty(), + Bytes = 10 + }; + + GetByteCodesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializerTests.cs new file mode 100644 index 00000000000..e788b8b0267 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializerTests.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Linq; +using System.Collections.Generic; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using NUnit.Framework; +using Nethermind.State.Snap; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class GetStorageRangesMessageSerializerTests + { + [Test] + public void Roundtrip_Many() + { + GetStorageRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + StoragetRange = new() + { + RootHash = TestItem.KeccakA, + Accounts = TestItem.Keccaks.Select(k => new PathWithAccount(k, null)).ToArray(), + StartingHash = new Keccak("0x15d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), + LimitHash = new Keccak("0x20d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + }, + ResponseBytes = 1000 + }; + + GetStorageRangesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_Empty() + { + GetStorageRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + StoragetRange = new() + { + RootHash = Keccak.OfAnEmptyString, + Accounts = Array.Empty(), + StartingHash = new Keccak("0x15d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), + LimitHash = new Keccak("0x20d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + }, + ResponseBytes = 1000 + }; + GetStorageRangesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializerTests.cs new file mode 100644 index 00000000000..a5c9843c64f --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializerTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using Nethermind.State.Snap; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class GetTrieNodesMessageSerializerTests + { + [Test] + public void Roundtrip_NoPaths() + { + GetTrieNodesMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + RootHash = TestItem.KeccakA, + Paths = Array.Empty(), //new MeasuredArray>(>()) , + Bytes = 10 + }; + GetTrieNodesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_OneAccountPath() + { + GetTrieNodesMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + RootHash = TestItem.KeccakA, + Paths = new PathGroup[] + { + new PathGroup(){Group = new []{TestItem.RandomDataA}} + }, + Bytes = 10 + }; + GetTrieNodesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_MultiplePaths() + { + GetTrieNodesMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + RootHash = TestItem.KeccakA, + Paths = new PathGroup[] + { + new PathGroup(){Group = new []{TestItem.RandomDataA, TestItem.RandomDataB}}, + new PathGroup(){Group = new []{TestItem.RandomDataC}} + }, + Bytes = 10 + }; + GetTrieNodesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializerTests.cs new file mode 100644 index 00000000000..116b60974cc --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializerTests.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Snap; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class StorageRangesMessageSerializerTests + { + + [Test] + public void Roundtrip_NoSlotsNoProofs() + { + StorageRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + Slots = Array.Empty(), + Proofs = Array.Empty() + }; + StorageRangesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_OneProof() + { + StorageRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + Slots = Array.Empty(), + Proofs = new[] { TestItem.RandomDataA } + }; + + StorageRangesMessageSerializer serializer = new(); + + var serialized = serializer.Serialize(msg); + var deserialized = serializer.Deserialize(serialized); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_OneSlot() + { + StorageRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + Slots = new[] { new PathWithStorageSlot[] { new PathWithStorageSlot(new Keccak("0x10d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), TestItem.RandomDataA) } }, + Proofs = Array.Empty() + }; + + StorageRangesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + + [Test] + public void Roundtrip_Many() + { + StorageRangeMessage msg = new() + { + RequestId = MessageConstants.Random.NextLong(), + Slots = new[] { + new PathWithStorageSlot[] { + new PathWithStorageSlot(new Keccak("0x10d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), Rlp.Encode(TestItem.RandomDataA).Bytes) , + new PathWithStorageSlot(new Keccak("0x12d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), Rlp.Encode(TestItem.RandomDataB).Bytes) + }, + new PathWithStorageSlot[] { + new PathWithStorageSlot(new Keccak("0x21d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), Rlp.Encode(TestItem.RandomDataB).Bytes) , + new PathWithStorageSlot(new Keccak("0x22d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"), Rlp.Encode(TestItem.RandomDataC).Bytes) + } + }, + Proofs = new[] { TestItem.RandomDataA, TestItem.RandomDataB } + }; + + StorageRangesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, msg); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializerTests.cs new file mode 100644 index 00000000000..0270125d3c9 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializerTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Snap.Messages +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class TrieNodesMessageSerializerTests + { + [Test] + public void Roundtrip() + { + byte[][] data = {new byte[]{0xde, 0xad, 0xc0, 0xde}, new byte[]{0xfe, 0xed}}; + + TrieNodesMessage message = new (data); + + TrieNodesMessageSerializer serializer = new (); + + SerializerTester.TestZero(serializer, message); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs index 9586c305d50..a8953f48aa0 100644 --- a/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs @@ -30,6 +30,8 @@ using Nethermind.Network.P2P; using Nethermind.Network.P2P.Analyzers; using Nethermind.Network.P2P.Messages; +using Nethermind.Network.P2P.ProtocolHandlers; +using Nethermind.Network.P2P.Subprotocols.Eth; using Nethermind.Network.P2P.Subprotocols.Eth.V62; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V65; diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/NettyHandshakeHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/NettyHandshakeHandlerTests.cs index 44878cf3c08..0a162e1b850 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/NettyHandshakeHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/NettyHandshakeHandlerTests.cs @@ -25,6 +25,7 @@ using Nethermind.Core.Extensions; using Nethermind.Logging; using Nethermind.Network.P2P; +using Nethermind.Network.P2P.ProtocolHandlers; using Nethermind.Network.Rlpx; using Nethermind.Network.Rlpx.Handshake; using NSubstitute; diff --git a/src/Nethermind/Nethermind.Network/Config/INetworkConfig.cs b/src/Nethermind/Nethermind.Network/Config/INetworkConfig.cs index da8d88cec0f..6df470bbf73 100644 --- a/src/Nethermind/Nethermind.Network/Config/INetworkConfig.cs +++ b/src/Nethermind/Nethermind.Network/Config/INetworkConfig.cs @@ -39,6 +39,9 @@ public interface INetworkConfig : IConfig [ConfigItem(Description = "[OBSOLETE](Use MaxActivePeers instead) Max number of connected peers.", DefaultValue = "50")] int ActivePeersMaxCount { get; set; } + + [ConfigItem(Description = "Max number of priority peers. Can be overwritten by value from plugin config.", DefaultValue = "0")] + int PriorityPeersMaxCount { get; set; } [ConfigItem(Description = "Same as ActivePeersMaxCount.", DefaultValue = "50")] int MaxActivePeers => ActivePeersMaxCount; diff --git a/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs b/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs index 257d97e6201..84fad37e758 100644 --- a/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs +++ b/src/Nethermind/Nethermind.Network/Config/NetworkConfig.cs @@ -24,6 +24,7 @@ public class NetworkConfig : INetworkConfig public bool OnlyStaticPeers { get; set; } public bool IsPeersPersistenceOn { get; set; } = true; public int ActivePeersMaxCount { get; set; } = 50; + public int PriorityPeersMaxCount { get; set; } = 0; public int PeersPersistenceInterval { get; set; } = 1000 * 5; public int PeersUpdateInterval { get; set; } = 250; public int P2PPingInterval { get; set; } = 1000 * 10; diff --git a/src/Nethermind/Nethermind.Network/IProtocolsManager.cs b/src/Nethermind/Nethermind.Network/IProtocolsManager.cs index 1d2f5b10fdd..f3ef2b381d4 100644 --- a/src/Nethermind/Nethermind.Network/IProtocolsManager.cs +++ b/src/Nethermind/Nethermind.Network/IProtocolsManager.cs @@ -25,6 +25,7 @@ namespace Nethermind.Network public interface IProtocolsManager { void AddSupportedCapability(Capability capability); + void RemoveSupportedCapability(Capability capability); void SendNewCapability(Capability capability); void AddProtocol(string code, Func factory); event EventHandler P2PProtocolInitialized; diff --git a/src/Nethermind/Nethermind.Network/Metrics.cs b/src/Nethermind/Nethermind.Network/Metrics.cs index c086584b368..a0eb93b4478 100644 --- a/src/Nethermind/Nethermind.Network/Metrics.cs +++ b/src/Nethermind/Nethermind.Network/Metrics.cs @@ -209,6 +209,42 @@ public static class Metrics [Description("Number of eth.66 PooledTransactions messages received")] public static long Eth66PooledTransactionsReceived { get; set; } + [Description("Number of SNAP GetAccountRange messages received")] + public static long SnapGetAccountRangeReceived { get; set; } + + [Description("Number of SNAP GetAccountRange messages sent")] + public static long SnapGetAccountRangeSent { get; set; } + + [Description("Number of SNAP AccountRange messages received")] + public static long SnapAccountRangeReceived { get; set; } + + [Description("Number of SNAP GetStorageRanges messages received")] + public static long SnapGetStorageRangesReceived { get; set; } + + [Description("Number of SNAP GetStorageRanges messages sent")] + public static long SnapGetStorageRangesSent { get; set; } + + [Description("Number of SNAP StorageRanges messages received")] + public static long SnapStorageRangesReceived { get; set; } + + [Description("Number of SNAP GetByteCodes messages received")] + public static long SnapGetByteCodesReceived { get; set; } + + [Description("Number of SNAP GetByteCodes messages sent")] + public static long SnapGetByteCodesSent { get; set; } + + [Description("Number of SNAP ByteCodes messages received")] + public static long SnapByteCodesReceived { get; set; } + + [Description("Number of SNAP GetTrieNodes messages received")] + public static long SnapGetTrieNodesReceived { get; set; } + + [Description("Number of SNAP GetTrieNodes messages sent")] + public static long SnapGetTrieNodesSent { get; set; } + + [Description("Number of SNAP TrieNodes messages received")] + public static long SnapTrieNodesReceived { get; set; } + [Description("Number of bytes sent through P2P (TCP).")] public static long P2PBytesSent; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66MessageConstants.cs b/src/Nethermind/Nethermind.Network/P2P/MessageConstants.cs similarity index 90% rename from src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66MessageConstants.cs rename to src/Nethermind/Nethermind.Network/P2P/MessageConstants.cs index 589234ce851..82ab4b47d44 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66MessageConstants.cs +++ b/src/Nethermind/Nethermind.Network/P2P/MessageConstants.cs @@ -17,9 +17,9 @@ using System; -namespace Nethermind.Network.P2P.Subprotocols.Eth.V66 +namespace Nethermind.Network.P2P { - internal class Eth66MessageConstants + internal class MessageConstants { public static readonly Random Random = new(); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Protocol.cs b/src/Nethermind/Nethermind.Network/P2P/Protocol.cs index 46ddd677515..f4345f9b630 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Protocol.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Protocol.cs @@ -27,6 +27,10 @@ public static class Protocol /// public const string Eth = "eth"; /// + /// Ethereum Snap Sync + /// + public const string Snap = "snap"; + /// /// Whisper /// public const string Shh = "shh"; diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs index 0c133509356..f9e1c077de5 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs @@ -32,6 +32,7 @@ namespace Nethermind.Network.P2P.ProtocolHandlers public abstract class ProtocolHandlerBase : IProtocolHandler { public abstract string Name { get; } + public bool IsPriority { get; set; } protected INodeStatsManager StatsManager { get; } private readonly IMessageSerializationService _serializer; protected ISession Session { get; } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs index 5a0180cb1b3..78485559f48 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs @@ -19,7 +19,6 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using DotNetty.Common.Utilities; using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Core; @@ -41,7 +40,7 @@ namespace Nethermind.Network.P2P.ProtocolHandlers { - public abstract class SyncPeerProtocolHandlerBase : ProtocolHandlerBase, ISyncPeer + public abstract class SyncPeerProtocolHandlerBase : ZeroProtocolHandlerBase, ISyncPeer { public static readonly ulong SoftOutgoingMessageSizeLimit = (ulong) 2.MB(); public Node Node => Session?.Node; @@ -58,7 +57,6 @@ public abstract class SyncPeerProtocolHandlerBase : ProtocolHandlerBase, ISyncPe // this means that we know what the number, hash, and total diff of the head block is public bool IsInitialized { get; set; } - public override string ToString() => $"[Peer|{Name}|{HeadNumber,8}|{Node:s}]"; protected Keccak _remoteHeadBlockHash; @@ -254,22 +252,7 @@ private void SendMessage(IList txsToSend) TransactionsMessage msg = new(txsToSend); Send(msg); } - - public override void HandleMessage(Packet message) - { - ZeroPacket zeroPacket = new(message); - try - { - HandleMessage(zeroPacket); - } - finally - { - zeroPacket.SafeRelease(); - } - } - - public abstract void HandleMessage(ZeroPacket message); - + protected void Handle(GetBlockHeadersMessage getBlockHeadersMessage) { Metrics.Eth62GetBlockHeadersReceived++; diff --git a/src/Nethermind/Nethermind.Network/P2P/ZeroNettyP2PHandler.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs similarity index 99% rename from src/Nethermind/Nethermind.Network/P2P/ZeroNettyP2PHandler.cs rename to src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs index a186c6dc581..43f98fbba74 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ZeroNettyP2PHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs @@ -23,7 +23,7 @@ using Nethermind.Network.Rlpx; using Snappy; -namespace Nethermind.Network.P2P +namespace Nethermind.Network.P2P.ProtocolHandlers { public class ZeroNettyP2PHandler : SimpleChannelInboundHandler { diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs new file mode 100644 index 00000000000..3815574eabe --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Common.Utilities; +using Nethermind.Logging; +using Nethermind.Network.Rlpx; +using Nethermind.Stats; + +namespace Nethermind.Network.P2P.ProtocolHandlers +{ + public abstract class ZeroProtocolHandlerBase : ProtocolHandlerBase + { + protected ZeroProtocolHandlerBase(ISession session, INodeStatsManager nodeStats, IMessageSerializationService serializer, ILogManager logManager) + : base(session, nodeStats, serializer, logManager) + { + } + + public override void HandleMessage(Packet message) + { + ZeroPacket zeroPacket = new(message); + try + { + HandleMessage(zeroPacket); + } + finally + { + zeroPacket.SafeRelease(); + } + } + + public abstract void HandleMessage(ZeroPacket message); + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/RandomExtensions.cs b/src/Nethermind/Nethermind.Network/P2P/RandomExtensions.cs similarity index 94% rename from src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/RandomExtensions.cs rename to src/Nethermind/Nethermind.Network/P2P/RandomExtensions.cs index 1c00c82e3d8..b1d0e9b273f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/RandomExtensions.cs +++ b/src/Nethermind/Nethermind.Network/P2P/RandomExtensions.cs @@ -17,7 +17,7 @@ using System; -namespace Nethermind.Network.P2P.Subprotocols.Eth.V66 +namespace Nethermind.Network.P2P { internal static class RandomExtensions { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/Eth66Message.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/Eth66Message.cs index da53d9146a1..56a8413ca5c 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/Eth66Message.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Messages/Eth66Message.cs @@ -23,7 +23,7 @@ public abstract class Eth66Message : P2PMessage where T : P2PMessage { public override int PacketType => EthMessage.PacketType; public override string Protocol => EthMessage.Protocol; - public long RequestId { get; set; } = Eth66MessageConstants.Random.NextLong(); + public long RequestId { get; set; } = MessageConstants.Random.NextLong(); public T EthMessage { get; set; } protected Eth66Message() diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Les/LesProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Les/LesProtocolHandler.cs index f1d6a3a344b..12354d5b5d7 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Les/LesProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Les/LesProtocolHandler.cs @@ -40,7 +40,7 @@ namespace Nethermind.Network.P2P.Subprotocols.Les { - public class LesProtocolHandler : SyncPeerProtocolHandlerBase, IZeroProtocolHandler, ISyncPeer + public class LesProtocolHandler : SyncPeerProtocolHandlerBase, ISyncPeer { public override string Name => "les3"; public override bool IncludeInTxPool => false; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessage.cs new file mode 100644 index 00000000000..919fb559c2b --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessage.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Collections; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class AccountRangeMessage : SnapMessageBase + { + public override int PacketType => SnapMessageCode.AccountRange; + + /// + /// List of consecutive accounts from the trie + /// + public PathWithAccount[] PathsWithAccounts { get; set; } + + /// + /// List of trie nodes proving the account range + /// + public byte[][] Proofs { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs new file mode 100644 index 00000000000..11f93e5a072 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs @@ -0,0 +1,138 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Buffers; +using Nethermind.Core; +using Nethermind.Serialization.Rlp; +using Nethermind.Core.Extensions; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class AccountRangeMessageSerializer : IZeroMessageSerializer + { + private readonly AccountDecoder _decoder = new (true); + + public void Serialize(IByteBuffer byteBuffer, AccountRangeMessage message) + { + (int contentLength, int pwasLength, int proofsLength) = GetLength(message); + + byteBuffer.EnsureWritable(Rlp.LengthOfSequence(contentLength), true); + + NettyRlpStream stream = new(byteBuffer); + stream.StartSequence(contentLength); + + stream.Encode(message.RequestId); + if (message.PathsWithAccounts == null || message.PathsWithAccounts.Length == 0) + { + stream.EncodeNullObject(); + } + else + { + stream.StartSequence(pwasLength); + for (int i = 0; i < message.PathsWithAccounts.Length; i++) + { + PathWithAccount pwa = message.PathsWithAccounts[i]; + + int accountContentLength = _decoder.GetContentLength(pwa.Account); + int pwaLength = Rlp.LengthOf(pwa.Path) + Rlp.LengthOfSequence(accountContentLength); + + stream.StartSequence(pwaLength); + stream.Encode(pwa.Path); + _decoder.Encode(pwa.Account, stream, accountContentLength); + } + } + + if (message.Proofs == null || message.Proofs.Length == 0) + { + stream.EncodeNullObject(); + } + else + { + stream.StartSequence(proofsLength); + for (int i = 0; i < message.Proofs.Length; i++) + { + stream.Encode(message.Proofs[i]); + } + } + } + + public AccountRangeMessage Deserialize(IByteBuffer byteBuffer) + { + AccountRangeMessage message = new(); + NettyRlpStream rlpStream = new (byteBuffer); + + rlpStream.ReadSequenceLength(); + + message.RequestId = rlpStream.DecodeLong(); + message.PathsWithAccounts = rlpStream.DecodeArray(DecodePathWithRlpData); + message.Proofs = rlpStream.DecodeArray(s => s.DecodeByteArray()); + + return message; + } + + private PathWithAccount DecodePathWithRlpData(RlpStream stream) + { + stream.ReadSequenceLength(); + + PathWithAccount data = new(stream.DecodeKeccak(), _decoder.Decode(stream)); + + return data; + } + + private (int contentLength, int pwasLength, int proofsLength) GetLength(AccountRangeMessage message) + { + int contentLength = Rlp.LengthOf(message.RequestId); + + int pwasLength = 0; + if (message.PathsWithAccounts == null || message.PathsWithAccounts.Length == 0) + { + pwasLength = 1; + } + else + { + for (int i = 0; i < message.PathsWithAccounts.Length; i++) + { + PathWithAccount pwa = message.PathsWithAccounts[i]; + int itemLength = Rlp.LengthOf(pwa.Path); + itemLength += _decoder.GetLength(pwa.Account); + + pwasLength += Rlp.LengthOfSequence(itemLength); + } + } + + contentLength += Rlp.LengthOfSequence(pwasLength); + + int proofsLength = 0; + if (message.Proofs == null || message.Proofs.Length == 0) + { + proofsLength = 1; + } + else + { + for (int i = 0; i < message.Proofs.Length; i++) + { + proofsLength += Rlp.LengthOf(message.Proofs[i]); + } + } + + contentLength += Rlp.LengthOfSequence(proofsLength); + + return (contentLength, pwasLength, proofsLength); + } + } +} diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitRlpAuRa.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessage.cs similarity index 62% rename from src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitRlpAuRa.cs rename to src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessage.cs index 4466904b702..88ae155accd 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitRlpAuRa.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessage.cs @@ -13,23 +13,22 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . +// -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Core; -using Nethermind.Init.Steps; -using Nethermind.Serialization.Rlp; +using System; +using Nethermind.Core.Crypto; -namespace Nethermind.Consensus.AuRa.InitializationSteps +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages { - public class InitRlpAuRa : InitRlp + public class ByteCodesMessage : SnapMessageBase { - public InitRlpAuRa(AuRaNethermindApi context) : base(context) { } - - public override Task Execute(CancellationToken cancellationToken) + public ByteCodesMessage(byte[][]? data) { - Rlp.Decoders[typeof(BlockInfo)] = new BlockInfoDecoder(true); - return base.Execute(cancellationToken); + Codes = data ?? Array.Empty(); } + + public override int PacketType => SnapMessageCode.ByteCodes; + + public byte[][] Codes { get; } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs new file mode 100644 index 00000000000..bdd216c113c --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Buffers; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class ByteCodesMessageSerializer : IZeroMessageSerializer + { + public void Serialize(IByteBuffer byteBuffer, ByteCodesMessage message) + { + (int contentLength, int codesLength) = GetLength(message); + byteBuffer.EnsureWritable(Rlp.LengthOfSequence(contentLength), true); + RlpStream rlpStream = new NettyRlpStream(byteBuffer); + + rlpStream.StartSequence(contentLength); + rlpStream.Encode(message.RequestId); + rlpStream.StartSequence(codesLength); + for (int i = 0; i < message.Codes.Length; i++) + { + rlpStream.Encode(message.Codes[i]); + } + } + + public ByteCodesMessage Deserialize(IByteBuffer byteBuffer) + { + NettyRlpStream rlpStream = new(byteBuffer); + + rlpStream.ReadSequenceLength(); + + long requestId = rlpStream.DecodeLong(); + byte[][] result = rlpStream.DecodeArray(stream => stream.DecodeByteArray()); + + return new ByteCodesMessage(result) { RequestId = requestId }; + } + + public (int contentLength, int codesLength) GetLength(ByteCodesMessage message) + { + int codesLength = 0; + for (int i = 0; i < message.Codes.Length; i++) + { + codesLength += Rlp.LengthOf(message.Codes[i]); + } + + return (codesLength + Rlp.LengthOf(message.RequestId), codesLength); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessage.cs new file mode 100644 index 00000000000..bc29da7a632 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessage.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetAccountRangeMessage : SnapMessageBase + { + public override int PacketType => SnapMessageCode.GetAccountRange; + + public AccountRange AccountRange { get; set; } + + /// + /// Soft limit at which to stop returning data + /// + public long ResponseBytes { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessageSerializer.cs new file mode 100644 index 00000000000..aecb09b11ec --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetAccountRangeMessageSerializer.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Buffers; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetAccountRangeMessageSerializer : SnapSerializerBase + { + protected override GetAccountRangeMessage Deserialize(RlpStream rlpStream) + { + GetAccountRangeMessage message = new (); + rlpStream.ReadSequenceLength(); + + message.RequestId = rlpStream.DecodeLong(); + message.AccountRange = new(rlpStream.DecodeKeccak(), rlpStream.DecodeKeccak(), rlpStream.DecodeKeccak()); + message.ResponseBytes = rlpStream.DecodeLong(); + + return message; + } + + public override void Serialize(IByteBuffer byteBuffer, GetAccountRangeMessage message) + { + NettyRlpStream rlpStream = GetRlpStreamAndStartSequence(byteBuffer, message); + + rlpStream.Encode(message.RequestId); + rlpStream.Encode(message.AccountRange.RootHash); + rlpStream.Encode(message.AccountRange.StartingHash); + + rlpStream.Encode(message.AccountRange.LimitHash ?? Keccak.MaxValue); + rlpStream.Encode(message.ResponseBytes == 0 ? 1000_000 : message.ResponseBytes); + } + + public override int GetLength(GetAccountRangeMessage message, out int contentLength) + { + contentLength = Rlp.LengthOf(message.RequestId); + contentLength += Rlp.LengthOf(message.AccountRange.RootHash); + contentLength += Rlp.LengthOf(message.AccountRange.StartingHash); + contentLength += Rlp.LengthOf(message.AccountRange.LimitHash); + contentLength += Rlp.LengthOf(message.ResponseBytes); + + return Rlp.LengthOfSequence(contentLength); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetByteCodesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetByteCodesMessage.cs new file mode 100644 index 00000000000..6f757be0f41 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetByteCodesMessage.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Collections; +using System.Collections.Generic; +using Nethermind.Core.Crypto; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetByteCodesMessage: SnapMessageBase + { + public override int PacketType => SnapMessageCode.GetByteCodes; + + /// + /// Code hashes to retrieve the code for + /// + public Keccak[] Hashes { get; set; } + + /// + /// Soft limit at which to stop returning data + /// + public long Bytes { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetByteCodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetByteCodesMessageSerializer.cs new file mode 100644 index 00000000000..9e63dbb7c7c --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetByteCodesMessageSerializer.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Buffers; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetByteCodesMessageSerializer : SnapSerializerBase + { + public override void Serialize(IByteBuffer byteBuffer, GetByteCodesMessage message) + { + NettyRlpStream rlpStream = GetRlpStreamAndStartSequence(byteBuffer, message); + + rlpStream.Encode(message.RequestId); + rlpStream.Encode(message.Hashes); + rlpStream.Encode(message.Bytes); + } + + protected override GetByteCodesMessage Deserialize(RlpStream rlpStream) + { + GetByteCodesMessage message = new (); + rlpStream.ReadSequenceLength(); + + message.RequestId = rlpStream.DecodeLong(); + message.Hashes = rlpStream.DecodeArray(_ => rlpStream.DecodeKeccak()); + message.Bytes = rlpStream.DecodeLong(); + + return message; + } + + public override int GetLength(GetByteCodesMessage message, out int contentLength) + { + contentLength = Rlp.LengthOf(message.RequestId); + contentLength += Rlp.LengthOf(message.Hashes, true); + contentLength += Rlp.LengthOf(message.Bytes); + + return Rlp.LengthOfSequence(contentLength); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangeMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangeMessage.cs new file mode 100644 index 00000000000..9a27950ca93 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangeMessage.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Collections; +using System.Collections.Generic; +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetStorageRangeMessage : SnapMessageBase + { + public override int PacketType => SnapMessageCode.GetStorageRanges; + + public StorageRange StoragetRange { get; set; } + + /// + /// Soft limit at which to stop returning data + /// + public long ResponseBytes { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs new file mode 100644 index 00000000000..a73063cca16 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Linq; +using DotNetty.Buffers; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetStorageRangesMessageSerializer : SnapSerializerBase + { + + public override void Serialize(IByteBuffer byteBuffer, GetStorageRangeMessage message) + { + NettyRlpStream rlpStream = GetRlpStreamAndStartSequence(byteBuffer, message); + + rlpStream.Encode(message.RequestId); + rlpStream.Encode(message.StoragetRange.RootHash); + rlpStream.Encode(message.StoragetRange.Accounts.Select(a => a.Path).ToArray()); // TODO: optimize this + rlpStream.Encode(message.StoragetRange.StartingHash); + rlpStream.Encode(message.StoragetRange.LimitHash); + rlpStream.Encode(message.ResponseBytes); + } + + protected override GetStorageRangeMessage Deserialize(RlpStream rlpStream) + { + GetStorageRangeMessage message = new (); + rlpStream.ReadSequenceLength(); + + message.RequestId = rlpStream.DecodeLong(); + + message.StoragetRange = new(); + message.StoragetRange.RootHash = rlpStream.DecodeKeccak(); + message.StoragetRange.Accounts = rlpStream.DecodeArray(DecodePathWithRlpData); + message.StoragetRange.StartingHash = rlpStream.DecodeKeccak(); + message.StoragetRange.LimitHash = rlpStream.DecodeKeccak(); + message.ResponseBytes = rlpStream.DecodeLong(); + + return message; + } + + private PathWithAccount DecodePathWithRlpData(RlpStream stream) + { + return new() { Path = stream.DecodeKeccak() }; + } + + public override int GetLength(GetStorageRangeMessage message, out int contentLength) + { + contentLength = Rlp.LengthOf(message.RequestId); + contentLength += Rlp.LengthOf(message.StoragetRange.RootHash); + contentLength += Rlp.LengthOf(message.StoragetRange.Accounts.Select(a => a.Path).ToArray(), true); // TODO: optimize this + contentLength += Rlp.LengthOf(message.StoragetRange.StartingHash); + contentLength += Rlp.LengthOf(message.StoragetRange.LimitHash); + contentLength += Rlp.LengthOf(message.ResponseBytes); + + return Rlp.LengthOfSequence(contentLength); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessage.cs new file mode 100644 index 00000000000..f7ad8ac2616 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessage.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetTrieNodesMessage : SnapMessageBase + { + public override int PacketType => SnapMessageCode.GetTrieNodes; + + /// + /// Root hash of the account trie to serve + /// + public Keccak RootHash { get; set; } + + /// + /// Trie paths to retrieve the nodes for, grouped by account + /// + public PathGroup[] Paths { get; set; } + + /// + /// Soft limit at which to stop returning data + /// + public long Bytes { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializer.cs new file mode 100644 index 00000000000..ea335598914 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetTrieNodesMessageSerializer.cs @@ -0,0 +1,122 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Buffers; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class GetTrieNodesMessageSerializer : IZeroMessageSerializer + { + public void Serialize(IByteBuffer byteBuffer, GetTrieNodesMessage message) + { + (int contentLength, int allPathsLength, int[] pathsLengths) = CalculateLengths(message); + + byteBuffer.EnsureWritable(Rlp.LengthOfSequence(contentLength), true); + NettyRlpStream stream = new (byteBuffer); + + stream.StartSequence(contentLength); + + stream.Encode(message.RequestId); + stream.Encode(message.RootHash); + + if (message.Paths == null || message.Paths.Length == 0) + { + stream.EncodeNullObject(); + } + else + { + stream.StartSequence(allPathsLength); + + for (int i = 0; i < message.Paths.Length; i++) + { + PathGroup group = message.Paths[i]; + + stream.StartSequence(pathsLengths[i]); + + for (int j = 0; j < group.Group.Length; j++) + { + stream.Encode(group.Group[j]); + } + } + } + + stream.Encode(message.Bytes); + } + + public GetTrieNodesMessage Deserialize(IByteBuffer byteBuffer) + { + GetTrieNodesMessage message = new(); + NettyRlpStream stream = new (byteBuffer); + + stream.ReadSequenceLength(); + + message.RequestId = stream.DecodeLong(); + message.RootHash = stream.DecodeKeccak(); + message.Paths = stream.DecodeArray(DecodeGroup); + + message.Bytes = stream.DecodeLong(); + + return message; + } + + private PathGroup DecodeGroup(RlpStream stream) + { + PathGroup group = new PathGroup(); + group.Group = stream.DecodeArray(s => stream.DecodeByteArray()); + + return group; + } + + private (int contentLength, int allPathsLength, int[] pathsLengths) CalculateLengths(GetTrieNodesMessage message) + { + int contentLength = Rlp.LengthOf(message.RequestId); + contentLength += Rlp.LengthOf(message.RootHash); + + int allPathsLength = 0; + int[] pathsLengths = new int[message.Paths.Length]; + + if (message.Paths == null || message.Paths.Length == 0) + { + allPathsLength = 1; + } + else + { + for (var i = 0; i < message.Paths.Length; i++) + { + PathGroup pathGroup = message.Paths[i]; + int groupLength = 0; + + foreach (byte[] path in pathGroup.Group) + { + groupLength += Rlp.LengthOf(path); + } + + pathsLengths[i] = groupLength; + allPathsLength += Rlp.LengthOfSequence(groupLength); + } + } + + contentLength += Rlp.LengthOfSequence(allPathsLength); + + contentLength += Rlp.LengthOf(message.Bytes); + + return (contentLength, allPathsLength, pathsLengths); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/SnapMessageBase.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/SnapMessageBase.cs new file mode 100644 index 00000000000..25fa0d79c18 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/SnapMessageBase.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Common.Utilities; +using Nethermind.Network.P2P.Messages; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public abstract class SnapMessageBase : P2PMessage + { + public override string Protocol => Nethermind.Network.P2P.Protocol.Snap; + + /// + /// Request ID to match up responses with + /// + public long RequestId { get; set; } + + protected SnapMessageBase(bool generateRandomRequestId = true) + { + if (generateRandomRequestId) + { + RequestId = MessageConstants.Random.NextLong(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/SnapSerializerBase.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/SnapSerializerBase.cs new file mode 100644 index 00000000000..bec66f8cf82 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/SnapSerializerBase.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Buffers; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public abstract class SnapSerializerBase : IZeroInnerMessageSerializer where T : MessageBase + { + public abstract void Serialize(IByteBuffer byteBuffer, T message); + protected abstract T Deserialize(RlpStream rlpStream); + public abstract int GetLength(T message, out int contentLength); + + protected NettyRlpStream GetRlpStreamAndStartSequence(IByteBuffer byteBuffer, T msg) + { + int totalLength = GetLength(msg, out int contentLength); + byteBuffer.EnsureWritable(totalLength, true); + NettyRlpStream stream = new (byteBuffer); + stream.StartSequence(contentLength); + + return stream; + } + + public T Deserialize(IByteBuffer byteBuffer) + { + NettyRlpStream rlpStream = new (byteBuffer); + return Deserialize(rlpStream); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangeMessage.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangeMessage.cs new file mode 100644 index 00000000000..94bdd090697 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangeMessage.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Collections; +using System.Collections.Generic; +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class StorageRangeMessage : SnapMessageBase + { + public override int PacketType => SnapMessageCode.StorageRanges; + + /// + /// List of list of consecutive slots from the trie (one list per account) + /// + public PathWithStorageSlot[][] Slots { get; set; } + + /// + /// List of trie nodes proving the slot range + /// + public byte[][] Proofs { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs new file mode 100644 index 00000000000..10dc692db2c --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/StorageRangesMessageSerializer.cs @@ -0,0 +1,159 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Collections.Generic; +using DotNetty.Buffers; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Snap; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class StorageRangesMessageSerializer : IZeroMessageSerializer + { + public void Serialize(IByteBuffer byteBuffer, StorageRangeMessage message) + { + (int contentLength, int allSlotsLength, int[] accountSlotsLengths, int proofsLength) = CalculateLengths(message); + + byteBuffer.EnsureWritable(Rlp.LengthOfSequence(contentLength), true); + NettyRlpStream stream = new(byteBuffer); + + stream.StartSequence(contentLength); + + stream.Encode(message.RequestId); + + if (message.Slots == null || message.Slots.Length == 0) + { + stream.EncodeNullObject(); + } + else + { + stream.StartSequence(allSlotsLength); + + for (int i = 0; i < message.Slots.Length; i++) + { + stream.StartSequence(accountSlotsLengths[i]); + + PathWithStorageSlot[] accountSlots = message.Slots[i]; + + for (int j = 0; j < accountSlots.Length; j++) + { + var slot = accountSlots[j]; + + int itemLength = Rlp.LengthOf(slot.Path) + Rlp.LengthOf(slot.SlotRlpValue); + + stream.StartSequence(itemLength); + stream.Encode(slot.Path); + stream.Encode(slot.SlotRlpValue); + } + + } + } + + if (message.Proofs == null || message.Proofs.Length == 0) + { + stream.EncodeNullObject(); + } + else + { + stream.StartSequence(proofsLength); + for (int i = 0; i < message.Proofs.Length; i++) + { + stream.Encode(message.Proofs[i]); + } + } + } + + public StorageRangeMessage Deserialize(IByteBuffer byteBuffer) + { + StorageRangeMessage message = new(); + NettyRlpStream stream = new (byteBuffer); + + stream.ReadSequenceLength(); + + message.RequestId = stream.DecodeLong(); + message.Slots = stream.DecodeArray(s => s.DecodeArray(DecodeSlot)); + message.Proofs = stream.DecodeArray(s => s.DecodeByteArray()); + + return message; + } + + private PathWithStorageSlot DecodeSlot(RlpStream stream) + { + stream.ReadSequenceLength(); + Keccak path = stream.DecodeKeccak(); + byte[] value = stream.DecodeByteArray(); + + PathWithStorageSlot data = new(path, value); + + return data; + } + + private (int contentLength, int allSlotsLength, int[] accountSlotsLengths, int proofsLength) CalculateLengths(StorageRangeMessage message) + { + int contentLength = Rlp.LengthOf(message.RequestId); + + int allSlotsLength = 0; + int[] accountSlotsLengths = new int[message.Slots.Length]; + + if (message.Slots == null || message.Slots.Length == 0) + { + allSlotsLength = 1; + } + else + { + for (var i = 0; i < message.Slots.Length; i++) + { + int accountSlotsLength = 0; + + var accountSlots = message.Slots[i]; + foreach (PathWithStorageSlot slot in accountSlots) + { + int slotLength = Rlp.LengthOf(slot.Path) + Rlp.LengthOf(slot.SlotRlpValue); + accountSlotsLength += Rlp.LengthOfSequence(slotLength); + } + + accountSlotsLengths[i] = accountSlotsLength; + allSlotsLength += Rlp.LengthOfSequence(accountSlotsLength); + } + } + + contentLength += Rlp.LengthOfSequence(allSlotsLength); + + int proofsLength = 0; + if (message.Proofs == null || message.Proofs.Length == 0) + { + proofsLength = 1; + contentLength++; + } + else + { + for (int i = 0; i < message.Proofs.Length; i++) + { + proofsLength += Rlp.LengthOf(message.Proofs[i]); + } + + contentLength += Rlp.LengthOfSequence(proofsLength); + } + + + + return (contentLength, allSlotsLength, accountSlotsLengths, proofsLength); + } + } +} diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/EntryPointsParam.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessage.cs similarity index 60% rename from src/Nethermind/Nethermind.AccountAbstraction/Subscribe/EntryPointsParam.cs rename to src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessage.cs index 19f359f492e..c2ee80a8790 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/EntryPointsParam.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessage.cs @@ -16,21 +16,18 @@ // using System; -using Nethermind.Core; -using Nethermind.JsonRpc; -using Newtonsoft.Json; -namespace Nethermind.AccountAbstraction.Subscribe; - -public class EntryPointsParam : IJsonRpcParam +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages { - public Address[] EntryPoints { get; set; } = null!; - - public void FromJson(JsonSerializer serializer, string jsonValue) + public class TrieNodesMessage : SnapMessageBase { - EntryPointsParam ep = serializer.Deserialize(jsonValue.ToJsonTextReader()) - ?? throw new ArgumentException($"Invalid 'entryPoints' filter: {jsonValue}"); - EntryPoints = ep.EntryPoints; + public TrieNodesMessage(byte[][]? data) + { + Nodes = data ?? Array.Empty(); + } + + public override int PacketType => SnapMessageCode.TrieNodes; + public byte[][] Nodes { get; set; } } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializer.cs new file mode 100644 index 00000000000..bedd8e3e4e8 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/TrieNodesMessageSerializer.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using DotNetty.Buffers; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Network.P2P.Subprotocols.Snap.Messages +{ + public class TrieNodesMessageSerializer : IZeroMessageSerializer + { + public void Serialize(IByteBuffer byteBuffer, TrieNodesMessage message) + { + (int contentLength, int nodesLength) = GetLength(message); + + byteBuffer.EnsureWritable(Rlp.LengthOfSequence(contentLength), true); + + NettyRlpStream rlpStream = new(byteBuffer); + + rlpStream.StartSequence(contentLength); + rlpStream.Encode(message.RequestId); + rlpStream.StartSequence(nodesLength); + for (int i = 0; i < message.Nodes.Length; i++) + { + rlpStream.Encode(message.Nodes[i]); + } + } + + public TrieNodesMessage Deserialize(IByteBuffer byteBuffer) + { + NettyRlpStream rlpStream = new(byteBuffer); + + rlpStream.ReadSequenceLength(); + + long requestId = rlpStream.DecodeLong(); + byte[][] result = rlpStream.DecodeArray(stream => stream.DecodeByteArray()); + return new TrieNodesMessage(result) { RequestId = requestId }; + } + + public (int contentLength, int nodesLength) GetLength(TrieNodesMessage message) + { + int nodesLength = 0; + for (int i = 0; i < message.Nodes.Length; i++) + { + nodesLength += Rlp.LengthOf(message.Nodes[i]); + } + + return (nodesLength + Rlp.LengthOf(message.RequestId), nodesLength); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapMessageCode.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapMessageCode.cs new file mode 100644 index 00000000000..a0527aa60ff --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapMessageCode.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +namespace Nethermind.Network.P2P.Subprotocols.Snap +{ + public static class SnapMessageCode + { + public const int GetAccountRange = 0x00; + public const int AccountRange = 0x01; + public const int GetStorageRanges = 0x02; + public const int StorageRanges = 0x03; + public const int GetByteCodes = 0x04; + public const int ByteCodes = 0x05; + public const int GetTrieNodes = 0x06; + public const int TrieNodes = 0x07; + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapProtocolHandler.cs new file mode 100644 index 00000000000..319c9f878ee --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/SnapProtocolHandler.cs @@ -0,0 +1,294 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using DotNetty.Buffers; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Logging; +using Nethermind.Network.P2P.EventArg; +using Nethermind.Network.P2P.ProtocolHandlers; +using Nethermind.Network.P2P.Subprotocols.Snap.Messages; +using Nethermind.Network.Rlpx; +using Nethermind.State.Snap; +using Nethermind.Stats; +using Nethermind.Stats.Model; + +namespace Nethermind.Network.P2P.Subprotocols.Snap +{ + public class SnapProtocolHandler : ZeroProtocolHandlerBase, ISnapSyncPeer + { + private const int BYTES_LIMIT = 2_000_000; + + public override string Name => "snap1"; + protected override TimeSpan InitTimeout => Timeouts.Eth; + + public override byte ProtocolVersion => 1; + public override string ProtocolCode => Protocol.Snap; + public override int MessageIdSpaceSize => 8; + + private readonly MessageQueue _getAccountRangeRequests; + private readonly MessageQueue _getStorageRangeRequests; + private readonly MessageQueue _getByteCodesRequests; + private readonly MessageQueue _getTrieNodesRequests; + private static readonly byte[] _emptyBytes = { 0 }; + + public SnapProtocolHandler(ISession session, + INodeStatsManager nodeStats, + IMessageSerializationService serializer, + ILogManager logManager) + : base(session, nodeStats, serializer, logManager) + { + _getAccountRangeRequests = new(Send); + _getStorageRangeRequests = new(Send); + _getByteCodesRequests = new(Send); + _getTrieNodesRequests = new(Send); + } + + public override event EventHandler ProtocolInitialized; + public override event EventHandler? SubprotocolRequested + { + add { } + remove { } + } + + public override void Init() + { + ProtocolInitialized?.Invoke(this, new ProtocolInitializedEventArgs(this)); + } + + public override void Dispose() + { + } + + public override void HandleMessage(ZeroPacket message) + { + int size = message.Content.ReadableBytes; + + switch (message.PacketType) + { + case SnapMessageCode.GetAccountRange: + GetAccountRangeMessage getAccountRangeMessage = Deserialize(message.Content); + ReportIn(getAccountRangeMessage); + Handle(getAccountRangeMessage); + break; + case SnapMessageCode.AccountRange: + AccountRangeMessage accountRangeMessage = Deserialize(message.Content); + ReportIn(accountRangeMessage); + Handle(accountRangeMessage, size); + break; + case SnapMessageCode.GetStorageRanges: + GetStorageRangeMessage getStorageRangesMessage = Deserialize(message.Content); + ReportIn(getStorageRangesMessage); + Handle(getStorageRangesMessage); + break; + case SnapMessageCode.StorageRanges: + StorageRangeMessage storageRangesMessage = Deserialize(message.Content); + ReportIn(storageRangesMessage); + Handle(storageRangesMessage, size); + break; + case SnapMessageCode.GetByteCodes: + GetByteCodesMessage getByteCodesMessage = Deserialize(message.Content); + ReportIn(getByteCodesMessage); + Handle(getByteCodesMessage); + break; + case SnapMessageCode.ByteCodes: + ByteCodesMessage byteCodesMessage = Deserialize(message.Content); + ReportIn(byteCodesMessage); + Handle(byteCodesMessage, size); + break; + case SnapMessageCode.GetTrieNodes: + GetTrieNodesMessage getTrieNodesMessage = Deserialize(message.Content); + ReportIn(getTrieNodesMessage); + Handle(getTrieNodesMessage); + break; + case SnapMessageCode.TrieNodes: + TrieNodesMessage trieNodesMessage = Deserialize(message.Content); + ReportIn(trieNodesMessage); + Handle(trieNodesMessage, size); + break; + } + } + + private void Handle(AccountRangeMessage msg, long size) + { + Metrics.SnapAccountRangeReceived++; + _getAccountRangeRequests.Handle(msg, size); + } + + private void Handle(StorageRangeMessage msg, long size) + { + Metrics.SnapStorageRangesReceived++; + _getStorageRangeRequests.Handle(msg, size); + } + + private void Handle(ByteCodesMessage msg, long size) + { + Metrics.SnapByteCodesReceived++; + _getByteCodesRequests.Handle(msg, size); + } + + private void Handle(TrieNodesMessage msg, long size) + { + Metrics.SnapTrieNodesReceived++; + _getTrieNodesRequests.Handle(msg, size); + } + + private void Handle(GetAccountRangeMessage msg) + { + Metrics.SnapGetAccountRangeReceived++; + //throw new NotImplementedException(); + } + + private void Handle(GetStorageRangeMessage getStorageRangesMessage) + { + Metrics.SnapGetStorageRangesReceived++; + //throw new NotImplementedException(); + } + + private void Handle(GetByteCodesMessage getByteCodesMessage) + { + Metrics.SnapGetByteCodesReceived++; + //throw new NotImplementedException(); + } + + private void Handle(GetTrieNodesMessage getTrieNodesMessage) + { + Metrics.SnapGetTrieNodesReceived++; + //throw new NotImplementedException(); + } + + public override void DisconnectProtocol(DisconnectReason disconnectReason, string details) + { + Dispose(); + } + + public async Task GetAccountRange(AccountRange range, CancellationToken token) + { + var request = new GetAccountRangeMessage() + { + AccountRange = range, + ResponseBytes = BYTES_LIMIT + }; + + AccountRangeMessage response = await SendRequest(request, _getAccountRangeRequests, token); + + Metrics.SnapGetAccountRangeSent++; + + return new AccountsAndProofs() { PathAndAccounts = response.PathsWithAccounts, Proofs = response.Proofs }; + } + + public async Task GetStorageRange(StorageRange range, CancellationToken token) + { + var request = new GetStorageRangeMessage() + { + StoragetRange = range, + ResponseBytes = BYTES_LIMIT + }; + + StorageRangeMessage response = await SendRequest(request, _getStorageRangeRequests, token); + + Metrics.SnapGetStorageRangesSent++; + + return new SlotsAndProofs() { PathsAndSlots = response.Slots, Proofs = response.Proofs }; + } + + public async Task GetByteCodes(Keccak[] codeHashes, CancellationToken token) + { + var request = new GetByteCodesMessage() + { + Hashes = codeHashes, + Bytes = BYTES_LIMIT + }; + + ByteCodesMessage response = await SendRequest(request, _getByteCodesRequests, token); + + Metrics.SnapGetByteCodesSent++; + + return response.Codes; + } + + public async Task GetTrieNodes(AccountsToRefreshRequest request, CancellationToken token) + { + PathGroup[] groups = GetPathGroups(request); + + GetTrieNodesMessage reqMsg = new () + { + RootHash = request.RootHash, + Paths = groups, + Bytes = BYTES_LIMIT + }; + + TrieNodesMessage response = await SendRequest(reqMsg, _getTrieNodesRequests, token); + + Metrics.SnapGetTrieNodesSent++; + + return response.Nodes; + } + + private PathGroup[] GetPathGroups(AccountsToRefreshRequest request) + { + PathGroup[] groups = new PathGroup[request.Paths.Length]; + + for (int i = 0; i < request.Paths.Length; i++) + { + AccountWithStorageStartingHash path = request.Paths[i]; + groups[i] = new PathGroup() { Group = new[] { path.PathAndAccount.Path.Bytes, _emptyBytes } }; + } + + return groups; + } + + private async Task SendRequest(TIn msg, MessageQueue requestQueue, CancellationToken token) + where TIn : SnapMessageBase + where TOut : SnapMessageBase + { + Request batch = new(msg); + + requestQueue.Send(batch); + + Task task = batch.CompletionSource.Task; + + using CancellationTokenSource delayCancellation = new(); + using CancellationTokenSource compositeCancellation + = CancellationTokenSource.CreateLinkedTokenSource(token, delayCancellation.Token); + Task firstTask = await Task.WhenAny(task, Task.Delay(Timeouts.Eth, compositeCancellation.Token)); + if (firstTask.IsCanceled) + { + token.ThrowIfCancellationRequested(); + } + + if (firstTask == task) + { + delayCancellation.Cancel(); + long elapsed = batch.FinishMeasuringTime(); + long bytesPerMillisecond = (long)((decimal)batch.ResponseSize / Math.Max(1, elapsed)); + if (Logger.IsTrace) + Logger.Trace($"{this} speed is {batch.ResponseSize}/{elapsed} = {bytesPerMillisecond}"); + StatsManager.ReportTransferSpeedEvent(Session.Node, TransferSpeedType.SnapRanges, bytesPerMillisecond); + + return task.Result; + } + + StatsManager.ReportTransferSpeedEvent(Session.Node, TransferSpeedType.SnapRanges, 0L); + throw new TimeoutException($"{Session} Request timeout in {nameof(TIn)}"); + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Wit/WitProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Wit/WitProtocolHandler.cs index a4fcf0fd82c..8c78ad8452b 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Wit/WitProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Wit/WitProtocolHandler.cs @@ -31,7 +31,7 @@ namespace Nethermind.Network.P2P.Subprotocols.Wit { - public class WitProtocolHandler : ProtocolHandlerBase, IZeroProtocolHandler, IWitnessPeer + public class WitProtocolHandler : ZeroProtocolHandlerBase, IWitnessPeer { private readonly ISyncServer _syncServer; @@ -71,20 +71,7 @@ public override void Init() // GetBlockWitnessHashes(Keccak.Zero, CancellationToken.None); } - public override void HandleMessage(Packet message) - { - ZeroPacket zeroPacket = new(message); - try - { - HandleMessage(zeroPacket); - } - finally - { - zeroPacket.SafeRelease(); - } - } - - public void HandleMessage(ZeroPacket message) + public override void HandleMessage(ZeroPacket message) { int size = message.Content.ReadableBytes; int packetType = message.PacketType; diff --git a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs b/src/Nethermind/Nethermind.Network/ProtocolsManager.cs index f883c5eae60..ea49701d99a 100644 --- a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs +++ b/src/Nethermind/Nethermind.Network/ProtocolsManager.cs @@ -32,6 +32,7 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V65; using Nethermind.Network.P2P.Subprotocols.Eth.V66; using Nethermind.Network.P2P.Subprotocols.Les; +using Nethermind.Network.P2P.Subprotocols.Snap; using Nethermind.Network.P2P.Subprotocols.Wit; using Nethermind.Network.Rlpx; using Nethermind.Stats; @@ -65,7 +66,7 @@ public class ProtocolsManager : IProtocolsManager private readonly ILogManager _logManager; private readonly ILogger _logger; private readonly IDictionary> _protocolFactories; - private readonly IList _capabilities = new List(); + private readonly HashSet _capabilities = new(); public event EventHandler P2PProtocolInitialized; public ProtocolsManager( @@ -207,6 +208,17 @@ private IDictionary> GetProtocolFa InitSyncPeerProtocol(session, ethHandler); return ethHandler; }, + [Protocol.Snap] = (session, version) => + { + var handler = version switch + { + 1 => new SnapProtocolHandler(session, _stats, _serializer, _logManager), + _ => throw new NotSupportedException($"{Protocol.Snap}.{version} is not supported.") + }; + InitSatelliteProtocol(session, handler); + + return handler; + }, [Protocol.Wit] = (session, version) => { var handler = version switch @@ -249,6 +261,7 @@ private void InitSatelliteProtocol(ISession session, ProtocolHandlerBase handler if (peer != null) { peer.SyncPeer.RegisterSatelliteProtocol(handler.ProtocolCode, handler); + if (handler.IsPriority) _syncPool.SetPeerPriority(session.Node.Id); if (_logger.IsDebug) _logger.Debug($"{handler.ProtocolCode} satellite protocol registered for sync peer {session}."); } else @@ -332,7 +345,8 @@ private void InitSyncPeerProtocol(ISession session, SyncPeerProtocolHandlerBase foreach (KeyValuePair registration in handlerDictionary) { handler.RegisterSatelliteProtocol(registration.Value); - if (_logger.IsDebug) _logger.Debug($"{handler.ProtocolCode} satellite protocol registered for sync peer {session}."); + if (registration.Value.IsPriority) handler.IsPriority = true; + if (_logger.IsDebug) _logger.Debug($"{handler.ProtocolCode} satellite protocol registered for sync peer {session}. Sync peer has priority: {handler.IsPriority}"); } } @@ -393,12 +407,15 @@ private void AddNodeToDiscovery(ISession session, P2PProtocolInitializedEventArg public void AddSupportedCapability(Capability capability) { - if (_capabilities.Contains(capability)) + _capabilities.Add(capability); + } + + public void RemoveSupportedCapability(Capability capability) + { + if (_capabilities.Remove(capability)) { - return; + if (_logger.IsDebug) _logger.Debug($"Removed supported capability: {capability}"); } - - _capabilities.Add(capability); } public void SendNewCapability(Capability capability) diff --git a/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs b/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs index 5316115f8e1..868eac596bc 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/NettyHandshakeHandler.cs @@ -28,6 +28,7 @@ using Nethermind.Core.Crypto; using Nethermind.Logging; using Nethermind.Network.P2P; +using Nethermind.Network.P2P.ProtocolHandlers; using Nethermind.Network.Rlpx.Handshake; namespace Nethermind.Network.Rlpx diff --git a/src/Nethermind/Nethermind.Network/SnapCapabilitySwitcher.cs b/src/Nethermind/Nethermind.Network/SnapCapabilitySwitcher.cs new file mode 100644 index 00000000000..77c50905763 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/SnapCapabilitySwitcher.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2022 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using Nethermind.Network.P2P; +using Nethermind.Stats.Model; +using Nethermind.Synchronization.SnapSync; + +namespace Nethermind.Network; + +// Temporary class used for removing snap capability after SnapSync finish. +// Will be removed after implementing missing functionality - serving data via snap protocol. +public class SnapCapabilitySwitcher +{ + private readonly IProtocolsManager _protocolsManager; + private readonly ProgressTracker _progressTracker; + + public SnapCapabilitySwitcher(IProtocolsManager? protocolsManager, ProgressTracker? progressTracker) + { + _protocolsManager = protocolsManager ?? throw new ArgumentNullException(nameof(protocolsManager)); + _progressTracker = progressTracker ?? throw new ArgumentNullException(nameof(progressTracker)); + } + + /// + /// Add Snap capability if SnapSync is not finished and remove after finished. + /// + public void EnableSnapCapabilityUntilSynced() + { + if (!_progressTracker.IsSnapGetRangesFinished()) + { + _protocolsManager.AddSupportedCapability(new Capability(Protocol.Snap, 1)); + _progressTracker.SnapSyncFinished += OnSnapSyncFinished; + } + } + + private void OnSnapSyncFinished(object? sender, EventArgs e) + { + _progressTracker.SnapSyncFinished -= OnSnapSyncFinished; + _protocolsManager.RemoveSupportedCapability(new Capability(Protocol.Snap, 1)); + } +} diff --git a/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg b/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg index 7f7f3ac01b6..c4bf75dbd4e 100644 --- a/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg @@ -8,9 +8,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 17270000, - "PivotHash": "0xbff13fdeecb1ee3869e61e0005b03fef788e718ea326ef2007ee307a80b0b47f", - "PivotTotalDifficulty": "5876676476724607264012479470346637011498116751", + "PivotNumber": 17860000, + "PivotHash": "0xb2071af5e25e334fab2c78fdf19fc6eb0c7e9271f0621a3412148d052931d96b", + "PivotTotalDifficulty": "6077443073207960957455870488731380256255968444", "FastBlocks": true, "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/energyweb_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/energyweb_pruned.cfg deleted file mode 100644 index a6cc3e57590..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/energyweb_pruned.cfg +++ /dev/null @@ -1,58 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/energyweb.json", - "GenesisHash": "0x0b6d3e680af2fc525392c720666cce58e3d8e6fe75ba4b48cb36bcc69039229b", - "BaseDbPath": "nethermind_db/energyweb", - "LogFileName": "energyweb.logs.txt", - "MemoryHint": 768000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "TxPool": { - "Size": 2048 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 16270000, - "PivotHash": "0x6ec87c58956d620567b6fe173d73d20463ca0a707b320883d60d54dfdd394125", - "PivotTotalDifficulty": "5536394109803668800549104862914868800044122664", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind Energy Web", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Energy_Web", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Mining": { - "MinGasPrice": 1 - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/goerli.cfg b/src/Nethermind/Nethermind.Runner/configs/goerli.cfg index 01d39d7e1dc..4f124c9cbe7 100644 --- a/src/Nethermind/Nethermind.Runner/configs/goerli.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/goerli.cfg @@ -14,9 +14,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 6660000, - "PivotHash": "0xa95a98bbceaac49934a61135e8a927a09869108d539fc3653fe5ca38e41a52dc", - "PivotTotalDifficulty": "9778028", + "PivotNumber": 6840000, + "PivotHash": "0x8365d7d503d2df3459e44fb8cca4eb820b6674bf0c7d2931595ce7b6b805a966", + "PivotTotalDifficulty": "10035383", "FastBlocks": true, "UseGethLimitsInFastBlocks": true, "WitnessProtocolEnabled": true diff --git a/src/Nethermind/Nethermind.Runner/configs/goerli_mev.cfg b/src/Nethermind/Nethermind.Runner/configs/goerli_mev.cfg index d8739db9cda..6761ecf5ee9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/goerli_mev.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/goerli_mev.cfg @@ -14,9 +14,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 6660000, - "PivotHash": "0xa95a98bbceaac49934a61135e8a927a09869108d539fc3653fe5ca38e41a52dc", - "PivotTotalDifficulty": "9778028", + "PivotNumber": 6840000, + "PivotHash": "0x8365d7d503d2df3459e44fb8cca4eb820b6674bf0c7d2931595ce7b6b805a966", + "PivotTotalDifficulty": "10035383", "FastBlocks": true, "WitnessProtocolEnabled": true }, diff --git a/src/Nethermind/Nethermind.Runner/configs/goerli_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/goerli_pruned.cfg deleted file mode 100644 index 44a6016f449..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/goerli_pruned.cfg +++ /dev/null @@ -1,67 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "EnableUnsecuredDevWallet": false, - "IsMining": false, - "ChainSpecPath": "chainspec/goerli.json", - "GenesisHash": "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a", - "BaseDbPath": "nethermind_db/goerli", - "LogFileName": "goerli.logs.txt", - "MemoryHint": 384000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - "DiagTracerEnabled": false - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "TxPool": { - "Size": 1024 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 6330000, - "PivotHash": "0x3aa7fd450f0e2dae207bf97abb47c7af0112234d0a136d9867451331a6909f2d", - "PivotTotalDifficulty": "9303360", - "FastBlocks": true, - "DownloadBodiesInFastSync": true, - "DownloadReceiptsInFastSync": true, - "UseGethLimitsInFastBlocks": true, - "WitnessProtocolEnabled": true - }, - "EthStats": { - "Enabled": false, - "Server": "wss://stats.goerli.net/api", - "Name": "Nethermind", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Goerli", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16, - 16 - ] - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/goerli_pruned_mev.cfg b/src/Nethermind/Nethermind.Runner/configs/goerli_pruned_mev.cfg deleted file mode 100644 index 88784312514..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/goerli_pruned_mev.cfg +++ /dev/null @@ -1,71 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "EnableUnsecuredDevWallet": false, - "IsMining": false, - "ChainSpecPath": "chainspec/goerli.json", - "GenesisHash": "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a", - "BaseDbPath": "nethermind_db/goerli", - "LogFileName": "goerli.logs.txt", - "MemoryHint": 384000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - "DiagTracerEnabled": false - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "TxPool": { - "Size": 1024 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 6330000, - "PivotHash": "0x3aa7fd450f0e2dae207bf97abb47c7af0112234d0a136d9867451331a6909f2d", - "PivotTotalDifficulty": "9303360", - "FastBlocks": true, - "DownloadBodiesInFastSync": true, - "DownloadReceiptsInFastSync": true, - "UseGethLimitsInFastBlocks": true, - "WitnessProtocolEnabled": true - }, - "EthStats": { - "Enabled": false, - "Server": "wss://stats.goerli.net/api", - "Name": "Nethermind", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Goerli", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16, - 16 - ] - }, - "Mev": { - "Enabled": true, - "MaxMergedBundles": 3 - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/kovan.cfg b/src/Nethermind/Nethermind.Runner/configs/kovan.cfg index b258856c736..aab9a64aa0d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/kovan.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/kovan.cfg @@ -11,9 +11,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 30880000, - "PivotHash": "0x8747c92da4b1a2899d86cccf8f4990f7b9972591ccdbb4b28d27593e750cced7", - "PivotTotalDifficulty": "10457476372728586754863660689061924501222854642", + "PivotNumber": 31560000, + "PivotHash": "0xf01aa9e44044e74d795706485554ab45e7e8efd5026c1bfe4cdf64fba8f6448b", + "PivotTotalDifficulty": "10688868382234824910018755422115526885011502311", "FastBlocks": true, "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg b/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg index 7d2abf08916..ad53793380b 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg @@ -11,9 +11,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 14540000, - "PivotHash": "0x1419a213994032969c61774966c2ce4ef2bab60a4db9953125b1e99f26cf6c75", - "PivotTotalDifficulty": "45768928877681841354783", + "PivotNumber": 14763000, + "PivotHash": "0xfa104eff3ea559493e9bdb4933a5a6329e5f96e2bfdda732272111a876f7ec56", + "PivotTotalDifficulty": "48793546583928107172186", "FastBlocks": true, "AncientBodiesBarrier": 11052984, "AncientReceiptsBarrier": 11052984, diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet_mev.cfg b/src/Nethermind/Nethermind.Runner/configs/mainnet_mev.cfg index ac69969e468..b76e01217f7 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet_mev.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet_mev.cfg @@ -8,9 +8,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 14540000, - "PivotHash": "0x1419a213994032969c61774966c2ce4ef2bab60a4db9953125b1e99f26cf6c75", - "PivotTotalDifficulty": "45768928877681841354783", + "PivotNumber": 14763000, + "PivotHash": "0xfa104eff3ea559493e9bdb4933a5a6329e5f96e2bfdda732272111a876f7ec56", + "PivotTotalDifficulty": "48793546583928107172186", "FastBlocks": true, "AncientBodiesBarrier": 11052984, "AncientReceiptsBarrier": 11052984, diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/mainnet_pruned.cfg deleted file mode 100644 index b0f54ccad11..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet_pruned.cfg +++ /dev/null @@ -1,65 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/foundation.json", - "GenesisHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", - "BaseDbPath": "nethermind_db/mainnet", - "LogFileName": "mainnet.logs.txt", - "DiagnosticMode": "None", - "MemoryHint": 2048000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - "ActivePeersMaxCount": 100 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "TxPool": { - "Size": 2048 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 1024, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 14166000, - "PivotHash": "0x24a8c4f0ebf7913d621b6088978e1a4e8ef7ea25078c7919abdc01640df0e791", - "PivotTotalDifficulty": "40971169532254218136664", - "FastBlocks": true, - "DownloadBodiesInFastSync": true, - "DownloadReceiptsInFastSync": true, - "AncientBodiesBarrier": 11052984, - "AncientReceiptsBarrier": 11052984, - "UseGethLimitsInFastBlocks": true, - "WitnessProtocolEnabled": true - }, - "EthStats": { - "Enabled": false, - "Server": "wss://ethstats.net/api", - "Name": "Nethermind", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Mainnet", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Analytics": { - "LogPublishedData": false, - "PluginsEnabled": false - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet_pruned_mev.cfg b/src/Nethermind/Nethermind.Runner/configs/mainnet_pruned_mev.cfg deleted file mode 100644 index 6b89d5e3f61..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet_pruned_mev.cfg +++ /dev/null @@ -1,69 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/foundation.json", - "GenesisHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", - "BaseDbPath": "nethermind_db/mainnet", - "LogFileName": "mainnet.logs.txt", - "DiagnosticMode": "None", - "MemoryHint": 2048000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - "ActivePeersMaxCount": 100 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "TxPool": { - "Size": 2048 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 1024, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 14166000, - "PivotHash": "0x24a8c4f0ebf7913d621b6088978e1a4e8ef7ea25078c7919abdc01640df0e791", - "PivotTotalDifficulty": "40971169532254218136664", - "FastBlocks": true, - "DownloadBodiesInFastSync": true, - "DownloadReceiptsInFastSync": true, - "AncientBodiesBarrier": 11052984, - "AncientReceiptsBarrier": 11052984, - "UseGethLimitsInFastBlocks": true, - "WitnessProtocolEnabled": true - }, - "EthStats": { - "Enabled": false, - "Server": "wss://ethstats.net/api", - "Name": "Nethermind", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Mainnet", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Analytics": { - "LogPublishedData": false, - "PluginsEnabled": false - }, - "Mev": { - "Enabled": true, - "MaxMergedBundles": 3 - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/poacore.cfg b/src/Nethermind/Nethermind.Runner/configs/poacore.cfg index b1f7e9659fa..fee279cf2a2 100644 --- a/src/Nethermind/Nethermind.Runner/configs/poacore.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/poacore.cfg @@ -8,9 +8,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 26750000, - "PivotHash": "0x0f5ae3e5ce9bee52afdd52252325641d7d6f065f7182c638eb3c87c6dbf1d7e6", - "PivotTotalDifficulty": "9102553315135103897645270748799799656091515228", + "PivotNumber": 27340000, + "PivotHash": "0xc3b3ebe9386330493c6501da24abf1d39bf8018a5a0c1bc1c0fc1f3fa1199606", + "PivotTotalDifficulty": "9303319911618457591088661767184542900849367940", "FastBlocks": true, "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/poacore_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/poacore_pruned.cfg deleted file mode 100644 index 4468680fbea..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/poacore_pruned.cfg +++ /dev/null @@ -1,67 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/poacore.json", - "GenesisHash": "0x39f02c003dde5b073b3f6e1700fc0b84b4877f6839bb23edadd3d2d82a488634", - "BaseDbPath": "nethermind_db/poacore", - "LogFileName": "poacore.logs.txt", - "MemoryHint": 768000000 - }, - "TxPool": { - "Size": 512 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "WebSocketsPort": 8546 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 25760000, - "PivotHash": "0xbf5dc24ea9d21ac4332c202533403aa57b7f9e6fa427161720cc442c69425b6f", - "PivotTotalDifficulty": "8765673771883374818816529887442349126752072048", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind POA Core", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "POA Core", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Aura": { - "ForceSealing": true - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16, - 16 - ] - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/rinkeby.cfg b/src/Nethermind/Nethermind.Runner/configs/rinkeby.cfg index 0764d7072d6..03521ff20b9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/rinkeby.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/rinkeby.cfg @@ -11,9 +11,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 10440000, - "PivotHash": "0xc3c972cc303018a9455fc7331ff589febc2e0a9bb88e4b294e59ce7758c4f8db", - "PivotTotalDifficulty": "17330645", + "PivotNumber": 10650000, + "PivotHash": "0x4d51866b92a87a7811e231a1d78e6a6f7bb704b23902547aeac9cc3781c34354", + "PivotTotalDifficulty": "17593781", "FastBlocks": true }, "Metrics": { diff --git a/src/Nethermind/Nethermind.Runner/configs/rinkeby_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/rinkeby_pruned.cfg deleted file mode 100644 index ffd149bb3c6..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/rinkeby_pruned.cfg +++ /dev/null @@ -1,56 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/rinkeby.json", - "GenesisHash": "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", - "BaseDbPath": "nethermind_db/rinkeby", - "LogFileName": "rinkeby.logs.txt", - "MemoryHint": 1024000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "TxPool": { - "Size": 1024 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 10110000, - "PivotHash": "0x26a0c55a9b94f38797eac58c43978f294d2e012f5161bb776782c5c7f06fbcb0", - "PivotTotalDifficulty": "16870395", - "FastBlocks": true, - "DownloadBodiesInFastSync": true, - "DownloadReceiptsInFastSync": true, - "UseGethLimitsInFastBlocks": true - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Rinkeby", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/ropsten.cfg b/src/Nethermind/Nethermind.Runner/configs/ropsten.cfg index 6181c44d9f4..76553ad6126 100644 --- a/src/Nethermind/Nethermind.Runner/configs/ropsten.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/ropsten.cfg @@ -13,9 +13,9 @@ "FastSync": true, "FastBlocks": true, "UseGethLimitsInFastBlocks": true, - "PivotNumber": 12160000, - "PivotHash": "0x415457b6cf2337942c98099c87624e2854d176763989b74349d478fcc85280fb", - "PivotTotalDifficulty": "41030321718293399" + "PivotNumber": 12250000, + "PivotHash": "0x6fddcf958a16881957b30294a795c9be6745ca03879076bd05e31e70125c5560", + "PivotTotalDifficulty": "41619747140010111" }, "EthStats": { "Server": "ws://ropsten-stats.parity.io/api" diff --git a/src/Nethermind/Nethermind.Runner/configs/ropsten_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/ropsten_pruned.cfg deleted file mode 100644 index e76c0cd1ec1..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/ropsten_pruned.cfg +++ /dev/null @@ -1,56 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/ropsten.json", - "GenesisHash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", - "BaseDbPath": "nethermind_db/ropsten", - "LogFileName": "ropsten.logs.txt", - "MemoryHint": 1024000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "TxPool": { - "Size": 1024 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "FastBlocks": true, - "UseGethLimitsInFastBlocks": true, - "PivotNumber": 11920000, - "DownloadBodiesInFastSync": true, - "DownloadReceiptsInFastSync": true, - "PivotHash": "0xaeab94b61026e16232dc7e104de754e3f8c45f280d797326b2e27d6604943e30", - "PivotTotalDifficulty": "39411106883089838" - }, - "EthStats": { - "Enabled": false, - "Server": "ws://ropsten-stats.parity.io/api", - "Name": "Nethermind", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Ropsten", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/sokol.cfg b/src/Nethermind/Nethermind.Runner/configs/sokol.cfg index 5b94b6734a3..d42e6e8cda1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sokol.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/sokol.cfg @@ -11,9 +11,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 26050000, - "PivotHash": "0xaa1cff725435e9b879d2e3e7485e8d43a071d73fd08cd60721c86794b3720dcc", - "PivotTotalDifficulty": "8864355658290446973220908523597561908073019352", + "PivotNumber": 26590000, + "PivotHash": "0x5251209eed52f6bc18f3ffd473d6d7abd66bdb6160337697e52da12860904b1c", + "PivotTotalDifficulty": "9048108136427753743491130811610716742258116092", "FastBlocks": true, "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/sokol_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/sokol_pruned.cfg deleted file mode 100644 index fea6b22674d..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/sokol_pruned.cfg +++ /dev/null @@ -1,67 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/sokol.json", - "GenesisHash": "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", - "BaseDbPath": "nethermind_db/sokol", - "LogFileName": "sokol.logs.txt", - "MemoryHint": 512000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "TxPool": { - "Size": 512 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "WebSocketsPort": 8546 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 25070000, - "PivotHash": "0x7c696be15c5f17819648846744a0761981ed0f4a681bbb272e3b1967d580d668", - "PivotTotalDifficulty": "8530878938707927279026801408314429060848120360", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind Sokol", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Sokol", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Aura": { - "ForceSealing": true - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16, - 16 - ] - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/volta.cfg b/src/Nethermind/Nethermind.Runner/configs/volta.cfg index 8727163cb40..7b5d84d5bdd 100644 --- a/src/Nethermind/Nethermind.Runner/configs/volta.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/volta.cfg @@ -12,9 +12,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 16830000, - "PivotHash": "0x43013b47043fa3609df712f8f668df0144e97cc77788e7ae708ac15848cc52a0", - "PivotTotalDifficulty": "5726952235279394340088594643076658998457917898", + "PivotNumber": 17370000, + "PivotHash": "0x8488346d9407f4e99f2350ef65010e30c82cfcfafee1d6675e7d49d17eea2eb5", + "PivotTotalDifficulty": "5910704713416701110358816931089813832643017046", "FastBlocks": true, "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/volta_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/volta_pruned.cfg deleted file mode 100644 index 1440dd35fb3..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/volta_pruned.cfg +++ /dev/null @@ -1,59 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/volta.json", - "GenesisHash": "0xebd8b413ca7b7f84a8dd20d17519ce2b01954c74d94a0a739a3e416abe0e43e5", - "BaseDbPath": "nethermind_db/volta", - "LogFileName": "volta.logs.txt", - "MemoryHint": 768000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303, - "ActivePeersMaxCount": 25 - }, - "TxPool": { - "Size": 2048 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 15890000, - "PivotHash": "0xdb195df9a3b8003bc65f7bfb43207571bcad9d4da088d32837ff334a943754a2", - "PivotTotalDifficulty": "5407086810373712184433022512090796879691219639", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind Volta", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "Volta", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Mining": { - "MinGasPrice": 1 - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/xdai.cfg b/src/Nethermind/Nethermind.Runner/configs/xdai.cfg index 0718524afe7..0a29f646fd3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/xdai.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/xdai.cfg @@ -8,9 +8,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 21510000, - "PivotHash": "0x88f99c6ac9fc3409adf61bf6b04fd0b5c72ef04c112fc3b89340bf9cb53e6ffc", - "PivotTotalDifficulty": "7319473712469386349097187805857334228067318917", + "PivotNumber": 22100000, + "PivotHash": "0x158a5c25e590958f74d660824b3835243ebf183aafb6093887dccba56481b019", + "PivotTotalDifficulty": "7520240308952740042540578824242077472825165940", "FastBlocks": true, "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/xdai_full_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/xdai_full_pruned.cfg deleted file mode 100644 index 22016513a19..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/xdai_full_pruned.cfg +++ /dev/null @@ -1,65 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/xdai.json", - "GenesisHash": "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756", - "BaseDbPath": "/nethermind_db/xdai", - "LogFileName": "xdai.logs.txt", - "MemoryHint": 1024000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "TxPool": { - "Size": 8192 - }, - "JsonRpc": { - "Enabled": true, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "WebSocketsPort": 8546 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Mode":"Full", - "FullPruningMaxDegreeOfParallelism": 2 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 18400000, - "PivotHash": "0x216e2bd3ad19acf0775563888f66f2e3a15473db46ca812dbe6b03e4944709bc", - "PivotTotalDifficulty": "6261195551345267727726092776744535090445471414", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind xDai", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "xDai", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Aura": { - "ForceSealing": true - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16 - ] - } -} diff --git a/src/Nethermind/Nethermind.Runner/configs/xdai_mev.cfg b/src/Nethermind/Nethermind.Runner/configs/xdai_mev.cfg index 593644d42d0..736c49c3996 100644 --- a/src/Nethermind/Nethermind.Runner/configs/xdai_mev.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/xdai_mev.cfg @@ -8,9 +8,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 21510000, - "PivotHash": "0x88f99c6ac9fc3409adf61bf6b04fd0b5c72ef04c112fc3b89340bf9cb53e6ffc", - "PivotTotalDifficulty": "7319473712469386349097187805857334228067318917", + "PivotNumber": 22100000, + "PivotHash": "0x158a5c25e590958f74d660824b3835243ebf183aafb6093887dccba56481b019", + "PivotTotalDifficulty": "7520240308952740042540578824242077472825165940", "FastBlocks": true, "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/xdai_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/xdai_pruned.cfg deleted file mode 100644 index ab4bcbaef67..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/xdai_pruned.cfg +++ /dev/null @@ -1,66 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/xdai.json", - "GenesisHash": "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756", - "BaseDbPath": "nethermind_db/xdai", - "LogFileName": "xdai.logs.txt", - "MemoryHint": 768000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "TxPool": { - "Size": 2048 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "WebSocketsPort": 8546 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 20530000, - "PivotHash": "0xb271ce9a617b3876b3cd9a39a6b40932f355f8dab71a1785c7196dbc8beae1ed", - "PivotTotalDifficulty": "6985996992886866654903080690574201380842418433", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind xDai", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "xDai", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Aura": { - "ForceSealing": true - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16 - ] - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/xdai_pruned_mev.cfg b/src/Nethermind/Nethermind.Runner/configs/xdai_pruned_mev.cfg deleted file mode 100644 index c3844999a21..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/xdai_pruned_mev.cfg +++ /dev/null @@ -1,70 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": false, - "ChainSpecPath": "chainspec/xdai.json", - "GenesisHash": "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756", - "BaseDbPath": "nethermind_db/xdai", - "LogFileName": "xdai.logs.txt", - "MemoryHint": 768000000 - }, - "Network": { - "DiscoveryPort": 30303, - "P2PPort": 30303 - }, - "TxPool": { - "Size": 2048 - }, - "JsonRpc": { - "Enabled": false, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "WebSocketsPort": 8546 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Enabled": true, - "CacheMb": 256, - "PersistenceInterval": 16384 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 20530000, - "PivotHash": "0xb271ce9a617b3876b3cd9a39a6b40932f355f8dab71a1785c7196dbc8beae1ed", - "PivotTotalDifficulty": "6985996992886866654903080690574201380842418433", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind xDai", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "xDai", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Aura": { - "ForceSealing": true - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16 - ] - }, - "Mev": { - "Enabled": true, - "MaxMergedBundles": 3 - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/xdai_testnet_full_pruned.cfg b/src/Nethermind/Nethermind.Runner/configs/xdai_testnet_full_pruned.cfg deleted file mode 100644 index b41759dff03..00000000000 --- a/src/Nethermind/Nethermind.Runner/configs/xdai_testnet_full_pruned.cfg +++ /dev/null @@ -1,68 +0,0 @@ -{ - "Init": { - "WebSocketsEnabled": false, - "StoreReceipts": true, - "IsMining": true, - "ChainSpecPath": "chainspec/xdai_testnet.json", - "GenesisHash": "0x0ae5a04b393944ed5c796c8a8c7bf49e163b4073c52e53d7aed7390842d0077c", - "BaseDbPath": "/nethermind_db/xdai_testnet", - "LogFileName": "xdai.logs.txt", - "MemoryHint": 768000000 - }, - "Network": { - "DiscoveryPort": 30304, - "P2PPort": 30304, - "StaticPeers": "enode://58b59795aebf9473c902f0c48168a151df4e17254dce64010444c4a041ff117d594969fb5c2799b7d8e06a222b7a4139468ddcc93a3386c4ee6e41b6948ace57@88.156.138.170:30303", - "OnlyStaticPeers": false, - "DiagTracerEnabled": true - }, - "TxPool": { - "Size": 1024 - }, - "JsonRpc": { - "Enabled": true, - "Timeout": 20000, - "Host": "127.0.0.1", - "Port": 8545, - "WebSocketsPort": 8547 - }, - "Db": { - "CacheIndexAndFilterBlocks": false - }, - "Pruning": { - "Mode":"Full", - "FullPruningMaxDegreeOfParallelism": 2 - }, - "Sync": { - "FastSync": true, - "PivotNumber": 17530000, - "PivotHash": "0xa7219cb1f5aa614455d0e42018b45f56f6e28daa948504b55b5d7da112e42fbf", - "PivotTotalDifficulty": "5965149892124051264512956868278896746480547121", - "FastBlocks": true, - "UseGethLimitsInFastBlocks": false, - "FastSyncCatchUpHeightDelta": 10000000000 - }, - "EthStats": { - "Enabled": false, - "Server": "ws://localhost:3000/api", - "Name": "Nethermind xDai", - "Secret": "secret", - "Contact": "hello@nethermind.io" - }, - "Metrics": { - "NodeName": "xDai", - "Enabled": false, - "PushGatewayUrl": "http://localhost:9091/metrics", - "IntervalSeconds": 5 - }, - "Aura": { - "ForceSealing": true - }, - "Bloom": { - "IndexLevelBucketSizes": [ - 16, - 16, - 16 - ] - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs index 4f5c7dfc608..e2b81fd0786 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/AccountDecoder.cs @@ -22,24 +22,35 @@ namespace Nethermind.Serialization.Rlp { - public class AccountDecoder : IRlpObjectDecoder + public class AccountDecoder : IRlpObjectDecoder, IRlpStreamDecoder { + private readonly bool _slimFormat; + + public AccountDecoder() { } + + public AccountDecoder(bool slimFormat = false) + { + _slimFormat = slimFormat; + } + public (Keccak CodeHash, Keccak StorageRoot) DecodeHashesOnly(RlpStream rlpStream) { rlpStream.SkipLength(); rlpStream.SkipItem(); rlpStream.SkipItem(); - Keccak storageRoot = rlpStream.DecodeKeccak(); - Keccak codeHash = rlpStream.DecodeKeccak(); + + Keccak storageRoot = DecodeStorageRoot(rlpStream); + Keccak codeHash = DecodeCodeHash(rlpStream); + return (codeHash, storageRoot); } - + public Keccak DecodeStorageRootOnly(RlpStream rlpStream) { rlpStream.SkipLength(); rlpStream.SkipItem(); rlpStream.SkipItem(); - Keccak storageRoot = rlpStream.DecodeKeccak(); + Keccak storageRoot = DecodeStorageRoot(rlpStream); return storageRoot; } @@ -53,29 +64,85 @@ public Keccak DecodeStorageRootOnly(RlpStream rlpStream) UInt256 nonce = rlpStream.DecodeUInt256(); UInt256 balance = rlpStream.DecodeUInt256(); - Keccak storageRoot = rlpStream.DecodeKeccak(); - Keccak codeHash = rlpStream.DecodeKeccak(); + Keccak storageRoot = DecodeStorageRoot(rlpStream); + Keccak codeHash = DecodeCodeHash(rlpStream); Account account = new(nonce, balance, storageRoot, codeHash); return account; } + public void Encode(RlpStream stream, Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item == null) + { + stream.EncodeNullObject(); + return; + } + + Encode(item, stream); + } + public Rlp Encode(Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) { return Rlp.OfEmptySequence; } - + int contentLength = GetContentLength(item); RlpStream rlpStream = new(Rlp.LengthOfSequence(contentLength)); - rlpStream.StartSequence(contentLength); - rlpStream.Encode(item.Nonce); - rlpStream.Encode(item.Balance); - rlpStream.Encode(item.StorageRoot); - rlpStream.Encode(item.CodeHash); + + Encode(item, rlpStream, contentLength); + return new Rlp(rlpStream.Data); } + public void Encode(Account account, RlpStream rlpStream, int? contentLength = null) + { + if (contentLength == null) + { + contentLength = GetContentLength(account); + } + + rlpStream.StartSequence(contentLength.Value); + rlpStream.Encode(account.Nonce); + rlpStream.Encode(account.Balance); + + if (_slimFormat && !account.HasStorage) + { + rlpStream.EncodeEmptyByteArray(); + } + else + { + rlpStream.Encode(account.StorageRoot); + } + + if (_slimFormat && !account.HasCode) + { + rlpStream.EncodeEmptyByteArray(); + } + else + { + rlpStream.Encode(account.CodeHash); + } + } + + public int GetLength(Account[] accounts) + { + int length = 0; + + if (accounts == null || accounts.Length == 0) + { + return 1; + } + + for (int i = 0; i < accounts.Length; i++) + { + length += GetLength(accounts[i]); + } + + return length; + } + public int GetLength(Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (item is null) @@ -86,17 +153,67 @@ public int GetLength(Account? item, RlpBehaviors rlpBehaviors = RlpBehaviors.Non return Rlp.LengthOfSequence(GetContentLength(item)); } - private int GetContentLength(Account? item) + public int GetContentLength(Account? item) { if (item is null) { return 0; } - var contentLength = 2 * Rlp.LengthOfKeccakRlp; - contentLength += Rlp.LengthOf(item.Nonce); + var contentLength = Rlp.LengthOf(item.Nonce); contentLength += Rlp.LengthOf(item.Balance); + + if (_slimFormat && !item.HasStorage) + { + contentLength++; + } + else + { + contentLength += Rlp.LengthOfKeccakRlp; + } + + if (_slimFormat && !item.HasCode) + { + contentLength++; + } + else + { + contentLength += Rlp.LengthOfKeccakRlp; + } + return contentLength; } + + private Keccak DecodeStorageRoot(RlpStream rlpStream) + { + Keccak storageRoot = null; + if (_slimFormat && rlpStream.IsNextItemEmptyArray()) + { + rlpStream.ReadByte(); + storageRoot = Keccak.EmptyTreeHash; + } + else + { + storageRoot = rlpStream.DecodeKeccak(); + } + + return storageRoot; + } + + private Keccak DecodeCodeHash(RlpStream rlpStream) + { + Keccak codeHash = null; + if (_slimFormat && rlpStream.IsNextItemEmptyArray()) + { + rlpStream.ReadByte(); + codeHash = Keccak.OfAnEmptyString; + } + else + { + codeHash = rlpStream.DecodeKeccak(); + } + + return codeHash; + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs index 3219978ee93..85317352cfb 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockInfoDecoder.cs @@ -23,16 +23,6 @@ namespace Nethermind.Serialization.Rlp { public class BlockInfoDecoder : IRlpStreamDecoder, IRlpValueDecoder { - private readonly bool _chainWithFinalization; - - public BlockInfoDecoder(bool chainWithFinalization) - { - _chainWithFinalization = chainWithFinalization; - } - - // ReSharper disable once UnusedMember.Global this is needed for auto-registration - public BlockInfoDecoder() : this(false) { } - public BlockInfo? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (rlpStream.IsNextItemNull()) @@ -48,10 +38,11 @@ public BlockInfoDecoder() : this(false) { } bool wasProcessed = rlpStream.DecodeBool(); UInt256 totalDifficulty = rlpStream.DecodeUInt256(); - bool isFinalized = false; - if (_chainWithFinalization) + BlockMetadata metadata = BlockMetadata.None; + // if we hadn't reached the end of the stream, assume we have metadata to decode + if (rlpStream.Position != lastCheck) { - isFinalized = rlpStream.DecodeBool(); + metadata = (BlockMetadata)rlpStream.DecodeInt(); } if ((rlpBehaviors & RlpBehaviors.AllowExtraData) != RlpBehaviors.AllowExtraData) @@ -67,7 +58,7 @@ public BlockInfoDecoder() : this(false) { } BlockInfo blockInfo = new(blockHash, totalDifficulty) { WasProcessed = wasProcessed, - IsFinalized = isFinalized + Metadata = metadata, }; return blockInfo; @@ -85,14 +76,15 @@ public Rlp Encode(BlockInfo? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None return Rlp.OfEmptySequence; } - Rlp[] elements = new Rlp[_chainWithFinalization ? 4 : 3]; + bool hasMetadata = item.Metadata != BlockMetadata.None; + + Rlp[] elements = new Rlp[hasMetadata ? 4 : 3]; elements[0] = Rlp.Encode(item.BlockHash); elements[1] = Rlp.Encode(item.WasProcessed); elements[2] = Rlp.Encode(item.TotalDifficulty); - - if (_chainWithFinalization) + if (hasMetadata) { - elements[3] = Rlp.Encode(item.IsFinalized); + elements[3] = Rlp.Encode((int)item.Metadata); } return Rlp.Encode(elements); @@ -116,11 +108,12 @@ public int GetLength(BlockInfo item, RlpBehaviors rlpBehaviors) Keccak? blockHash = decoderContext.DecodeKeccak(); bool wasProcessed = decoderContext.DecodeBool(); UInt256 totalDifficulty = decoderContext.DecodeUInt256(); - bool isFinalized = false; - if (_chainWithFinalization) + BlockMetadata metadata = BlockMetadata.None; + // if we hadn't reached the end of the stream, assume we have metadata to decode + if (decoderContext.Position != lastCheck) { - isFinalized = decoderContext.DecodeBool(); + metadata = (BlockMetadata)decoderContext.DecodeInt(); } if ((rlpBehaviors & RlpBehaviors.AllowExtraData) != RlpBehaviors.AllowExtraData) @@ -136,7 +129,7 @@ public int GetLength(BlockInfo item, RlpBehaviors rlpBehaviors) BlockInfo blockInfo = new(blockHash, totalDifficulty) { WasProcessed = wasProcessed, - IsFinalized = isFinalized + Metadata = metadata }; return blockInfo; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index a2180926ef0..665bffdff0e 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -140,19 +140,24 @@ public static T[] DecodeArray(RlpStream rlpStream, RlpBehaviors rlpBehaviors var rlpDecoder = GetStreamDecoder(); if (rlpDecoder != null) { - int checkPosition = rlpStream.ReadSequenceLength() + rlpStream.Position; - T[] result = new T[rlpStream.ReadNumberOfItemsRemaining(checkPosition)]; - for (int i = 0; i < result.Length; i++) - { - result[i] = rlpDecoder.Decode(rlpStream, rlpBehaviors); - } - - return result; + return DecodeArray(rlpStream, rlpDecoder, rlpBehaviors); } throw new RlpException($"{nameof(Rlp)} does not support decoding {typeof(T).Name}"); } + public static T[] DecodeArray(RlpStream rlpStream, IRlpStreamDecoder? rlpDecoder, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int checkPosition = rlpStream.ReadSequenceLength() + rlpStream.Position; + T[] result = new T[rlpStream.ReadNumberOfItemsRemaining(checkPosition)]; + for (int i = 0; i < result.Length; i++) + { + result[i] = rlpDecoder.Decode(rlpStream, rlpBehaviors); + } + + return result; + } + public static IRlpValueDecoder? GetValueDecoder() => Decoders.ContainsKey(typeof(T)) ? Decoders[typeof(T)] as IRlpValueDecoder : null; public static IRlpStreamDecoder? GetStreamDecoder() => Decoders.ContainsKey(typeof(T)) ? Decoders[typeof(T)] as IRlpStreamDecoder: null; public static IRlpObjectDecoder? GetObjectDecoder() => Decoders.ContainsKey(typeof(T)) ? Decoders[typeof(T)] as IRlpObjectDecoder: null; @@ -573,7 +578,7 @@ public static int SerializeLength(Span buffer, int position, int value) return position + 4; } - internal static int LengthOfLength(int value) + public static int LengthOfLength(int value) { if (value < 1 << 8) { @@ -1555,6 +1560,18 @@ public static int LengthOf(Keccak? item) return item is null ? 1 : 33; } + public static int LengthOf(Keccak[] keccaks, bool includeLengthOfSequenceStart = false) + { + int value = keccaks?.Length * LengthOfKeccakRlp ?? 0; + + if (includeLengthOfSequenceStart) + { + value = LengthOfSequence(value); + } + + return value; + } + public static int LengthOf(Address? item) { return item is null ? 1 : 21; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index 6cff02afe2d..917c0285f3a 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -15,6 +15,7 @@ // along with the Nethermind. If not, see . using System; +using System.Collections.Generic; using System.Numerics; using System.Text; using Nethermind.Core; @@ -191,6 +192,23 @@ public void Encode(Keccak? keccak) } } + public void Encode(Keccak[] keccaks) + { + if (keccaks == null) + { + EncodeNullObject(); + } + else + { + var length = Rlp.LengthOf(keccaks); + StartSequence(length); + for (int i = 0; i < keccaks.Length; i++) + { + Encode(keccaks[i]); + } + } + } + public void Encode(Address? address) { if (address == null) @@ -282,7 +300,7 @@ public void Encode(long value) { if (value == 0L) { - EncodeEmptyArray(); + EncodeEmptyByteArray(); return; } @@ -786,6 +804,11 @@ public Span Peek(int length) return item; } + public bool IsNextItemEmptyArray() + { + return PeekByte() == Rlp.EmptyArrayByte; + } + public bool IsNextItemNull() { return PeekByte() == Rlp.NullObjectByte; @@ -1067,7 +1090,7 @@ public void EncodeNullObject() WriteByte(EmptySequenceByte); } - public void EncodeEmptyArray() + public void EncodeEmptyByteArray() { WriteByte(EmptyArrayByte); } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index 5a37f35d0fa..71a4462427f 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -339,11 +339,11 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec return; } - UInt256 nonce = chainSpecJson.Genesis.Seal.Ethereum?.Nonce ?? 0; - Keccak mixHash = chainSpecJson.Genesis.Seal.Ethereum?.MixHash ?? Keccak.Zero; + UInt256 nonce = chainSpecJson.Genesis.Seal?.Ethereum?.Nonce ?? 0; + Keccak mixHash = chainSpecJson.Genesis.Seal?.Ethereum?.MixHash ?? Keccak.Zero; - byte[] auRaSignature = chainSpecJson.Genesis.Seal.AuthorityRound?.Signature; - long? step = chainSpecJson.Genesis.Seal.AuthorityRound?.Step; + byte[] auRaSignature = chainSpecJson.Genesis.Seal?.AuthorityRound?.Signature; + long? step = chainSpecJson.Genesis.Seal?.AuthorityRound?.Step; Keccak parentHash = chainSpecJson.Genesis.ParentHash ?? Keccak.Zero; UInt256 timestamp = chainSpecJson.Genesis.Timestamp; diff --git a/src/Nethermind/Nethermind.State.Test/SnapSync/RecreateStateFromAccountRangesTests.cs b/src/Nethermind/Nethermind.State.Test/SnapSync/RecreateStateFromAccountRangesTests.cs new file mode 100644 index 00000000000..17087fe1ea5 --- /dev/null +++ b/src/Nethermind/Nethermind.State.Test/SnapSync/RecreateStateFromAccountRangesTests.cs @@ -0,0 +1,282 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.State.Snap; +using Nethermind.Synchronization.SnapSync; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; +using NUnit.Framework; + +namespace Nethermind.Store.Test +{ + [TestFixture] + public class RecreateStateFromAccountRangesTests + { + private StateTree _inputTree; + + [OneTimeSetUp] + public void Setup() + { + _inputTree = TestItem.Tree.GetStateTree(null); + } + + //[Test] + public void Test01() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountsWithPaths[0].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + MemDb db = new (); + TrieStore store = new (db, LimboLogs.Instance); + StateTree tree = new (store, LimboLogs.Instance); + + IList nodes = new List(); + + for (int i = 0; i < (firstProof!).Length; i++) + { + byte[] nodeBytes = (firstProof!)[i]; + var node = new TrieNode(NodeType.Unknown, nodeBytes); + node.ResolveKey(store, i == 0); + + nodes.Add(node); + if (i < (firstProof!).Length - 1) + { + //IBatch batch = store.GetOrStartNewBatch(); + //batch[node.Keccak!.Bytes] = nodeBytes; + //db.Set(node.Keccak!, nodeBytes); + } + } + + for (int i = 0; i < (lastProof!).Length; i++) + { + byte[] nodeBytes = (lastProof!)[i]; + var node = new TrieNode(NodeType.Unknown, nodeBytes); + node.ResolveKey(store, i == 0); + + nodes.Add(node); + if (i < (lastProof!).Length - 1) + { + //IBatch batch = store.GetOrStartNewBatch(); + //batch[node.Keccak!.Bytes] = nodeBytes; + //db.Set(node.Keccak!, nodeBytes); + } + } + + tree.RootRef = nodes[0]; + + tree.Set(TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths[0].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[1].Path, TestItem.Tree.AccountsWithPaths[1].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[2].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[3].Path, TestItem.Tree.AccountsWithPaths[3].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[5].Path, TestItem.Tree.AccountsWithPaths[5].Account); + + tree.Commit(0); + + Assert.AreEqual(_inputTree.RootHash, tree.RootHash); + Assert.AreEqual(6, db.Keys.Count); // we don't persist proof nodes (boundary nodes) + Assert.IsFalse(db.KeyExists(rootHash)); // the root node is a part of the proof nodes + } + + [Test] + public void RecreateAccountStateFromOneRangeWithNonExistenceProof() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + AccountProofCollector accountProofCollector = new(Keccak.Zero.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + AddRangeResult result = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths, firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + Assert.AreEqual(11, db.Keys.Count); // we persist proof nodes (boundary nodes) via stitching + Assert.IsTrue(db.KeyExists(rootHash)); + } + + [Test] + public void RecreateAccountStateFromOneRangeWithExistenceProof() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountsWithPaths[0].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + MemDb db = new(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths, firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + Assert.AreEqual(11, db.Keys.Count); // we persist proof nodes (boundary nodes) via stitching + Assert.IsTrue(db.KeyExists(rootHash)); + } + + [Test] + public void RecreateAccountStateFromOneRangeWithoutProof() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + MemDb db = new(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths); + + Assert.AreEqual(AddRangeResult.OK, result); + Assert.AreEqual(11, db.Keys.Count); // we don't have the proofs so we persist all nodes + Assert.IsTrue(db.KeyExists(rootHash)); // the root node is NOT a part of the proof nodes + } + + [Test] + public void RecreateAccountStateFromMultipleRange() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + // output state + MemDb db = new(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(Keccak.Zero.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..2], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(2, db.Keys.Count); + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + var result2 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[2..4], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(5, db.Keys.Count); // we don't persist proof nodes (boundary nodes) + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + var result3 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4..6], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.OK, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + Assert.AreEqual(11, db.Keys.Count); // we persist proof nodes (boundary nodes) via stitching + Assert.IsTrue(db.KeyExists(rootHash)); + } + + [Test] + public void MissingAccountFromRange() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + // output state + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(Keccak.Zero.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..2], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(2, db.Keys.Count); + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + // missing TestItem.Tree.AccountsWithHashes[2] + var result2 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[3..4], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(2, db.Keys.Count); + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + var result3 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4..6], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.DifferentRootHash, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + Assert.AreEqual(6, db.Keys.Count); + Assert.IsFalse(db.KeyExists(rootHash)); + } + } +} diff --git a/src/Nethermind/Nethermind.State.Test/SnapSync/RecreateStateFromStorageRangesTests.cs b/src/Nethermind/Nethermind.State.Test/SnapSync/RecreateStateFromStorageRangesTests.cs new file mode 100644 index 00000000000..c807c448c9e --- /dev/null +++ b/src/Nethermind/Nethermind.State.Test/SnapSync/RecreateStateFromStorageRangesTests.cs @@ -0,0 +1,179 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.State.Snap; +using Nethermind.Synchronization.SnapSync; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; +using NUnit.Framework; + +namespace Nethermind.Store.Test +{ + [TestFixture] + public class RecreateStateFromStorageRangesTests + { + + private TrieStore _store; + private StateTree _inputStateTree; + private StorageTree _inputStorageTree; + + [OneTimeSetUp] + public void Setup() + { + _store = new TrieStore(new MemDb(), LimboLogs.Instance); + (_inputStateTree, _inputStorageTree) = TestItem.Tree.GetTrees(_store); + } + + [Test] + public void RecreateStorageStateFromOneRangeWithNonExistenceProof() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths, proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + } + + [Test] + public void RecreateAccountStateFromOneRangeWithExistenceProof() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[0].Path, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths, proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + } + + [Test] + public void RecreateStorageStateFromOneRangeWithoutProof() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + MemDb db = new MemDb(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[0].Path, TestItem.Tree.SlotsWithPaths); + + Assert.AreEqual(AddRangeResult.OK, result); + } + + [Test] + public void RecreateAccountStateFromMultipleRange() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + // output state + MemDb db = new MemDb(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[1].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + var result1 = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths[0..2], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[3].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result2 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[2..4], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result3 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[4..6], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.OK, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + } + + [Test] + public void MissingAccountFromRange() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + // output state + MemDb db = new MemDb(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[1].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + var result1 = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths[0..2], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[3].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result2 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[3..4], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result3 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[4..6], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.DifferentRootHash, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + } + } +} diff --git a/src/Nethermind/Nethermind.State.Test/SnapSync/Storage/SnapStorageTests.cs b/src/Nethermind/Nethermind.State.Test/SnapSync/Storage/SnapStorageTests.cs new file mode 100644 index 00000000000..8dfa33484a5 --- /dev/null +++ b/src/Nethermind/Nethermind.State.Test/SnapSync/Storage/SnapStorageTests.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; +using Nethermind.State.Snap.Storage; +using Nethermind.Trie; +using NUnit.Framework; + +namespace Nethermind.Store.Test.SnapSync.Storage +{ + [TestFixture] + public class SnapStorageTests + { + [Test] + public void AddAccounts_OneLayer_GetRange() + { + SortedList list = new(); + + for (int i = 0; i < 100; i++) + { + byte[] accountBytes = TestItem.GenerateIndexedAccountRlp(i); + Keccak randomKeccak = TestItem.GetRandomKeccak(); + TrieNode leaf = TrieNodeFactory.CreateLeaf(new HexPrefix(true), accountBytes); + + list.Add(randomKeccak, leaf); + } + + SnapStorage storage = new(); + foreach (var item in list) + { + storage.AddLeafNode(item.Key, item.Value, 1001); + } + + Keccak startingHash = list.Keys[20]; + Keccak endHash = list.Keys[29]; + + var range1 = storage.GetRange(startingHash, endHash, 1001); + var range2 = storage.GetRange(startingHash, endHash, 1002); + + Assert.AreEqual(10, range1.Length); + Assert.IsTrue(AreEqual(20, 29, list, range1)); + + Assert.AreEqual(10, range2.Length); + Assert.IsTrue(AreEqual(20, 29, list, range2)); + } + + [Test] + public void AddAccounts_MultipleLayers_GetRange() + { + SortedList list = new(); + + for (int i = 0; i < 100; i++) + { + byte[] accountBytes = TestItem.GenerateIndexedAccountRlp(i); + Keccak randomKeccak = TestItem.GetRandomKeccak(); + TrieNode leaf = TrieNodeFactory.CreateLeaf(new HexPrefix(true), accountBytes); + + list.Add(randomKeccak, leaf); + } + + SnapStorage storage = new(); + + // create bottom layer + foreach (var item in list) + { + storage.AddLeafNode(item.Key, item.Value, 1001); + } + + // block 1002 + byte[] updateAccountBytes_1002 = TestItem.GenerateRandomAccountRlp(); + TrieNode updateAccount_1002 = TrieNodeFactory.CreateLeaf(new HexPrefix(true), updateAccountBytes_1002); + storage.AddLeafNode(list.Keys[21], updateAccount_1002, 1002); + + byte[] newAccountBytes_1002 = TestItem.GenerateRandomAccountRlp(); + TrieNode newAccount_1002 = TrieNodeFactory.CreateLeaf(new HexPrefix(true), newAccountBytes_1002); + Keccak newAddress = TestItem.GetRandomKeccak(); + while(newAddress <= list.Keys[21] || newAddress >= list.Keys[22]) + { + newAddress = TestItem.GetRandomKeccak(); + } + storage.AddLeafNode(newAddress, newAccount_1002, 1002); + + // block 1003 + storage.AddLeafNode(list.Keys[21], null, 1003); + + + Keccak startingHash = list.Keys[20]; + Keccak endHash = list.Keys[30]; + + var range_1001 = storage.GetRange(startingHash, endHash, 1001); + var range_1002 = storage.GetRange(startingHash, endHash, 1002); + var range_1003 = storage.GetRange(startingHash, endHash, 1003); + + Assert.AreEqual(11, range_1001.Length); + Assert.IsTrue(AreEqual(20, 30, list, range_1001)); + + Assert.AreEqual(12, range_1002.Length); + Assert.AreEqual(range_1002[1].node, updateAccount_1002); + Assert.AreEqual(range_1002[2].node, newAccount_1002); + + Assert.AreEqual(11, range_1003.Length); + Assert.AreEqual(range_1003[1].node, newAccount_1002); + } + + private bool AreEqual(int startIndex, int endIndex, SortedList inputList, (Keccak path, TrieNode node)[] range) + { + for (int i = startIndex; i <= endIndex; i++) + { + if(inputList.Keys[i] != range[i - startIndex].path) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs b/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs index dff9d820c5a..9a5b7aab7e0 100644 --- a/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs +++ b/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs @@ -66,7 +66,38 @@ private static byte[] ToKey(UInt256 index) return bytes; } - internal AccountProofCollector(byte[] hashedAddress, params byte[][] storageKeys) + /// + /// Only for testing + /// + public AccountProofCollector(byte[] hashedAddress, Keccak[] keccakStorageKeys) + { + keccakStorageKeys ??= Array.Empty(); + + _fullAccountPath = Nibbles.FromBytes(hashedAddress); + + _accountProof = new AccountProof(); + _accountProof.StorageProofs = new StorageProof[keccakStorageKeys.Length]; + _accountProof.Address = _address; + + _fullStoragePaths = new Nibble[keccakStorageKeys.Length][]; + _storageProofItems = new List[keccakStorageKeys.Length]; + for (int i = 0; i < _storageProofItems.Length; i++) + { + _storageProofItems[i] = new List(); + } + + for (int i = 0; i < keccakStorageKeys.Length; i++) + { + _fullStoragePaths[i] = Nibbles.FromBytes(keccakStorageKeys[i].Bytes); + + _accountProof.StorageProofs[i] = new StorageProof(); + // we don't know the key (index) + //_accountProof.StorageProofs[i].Key = storageKeys[i]; + _accountProof.StorageProofs[i].Value = new byte[] { 0 }; + } + } + + public AccountProofCollector(byte[] hashedAddress, params byte[][] storageKeys) { storageKeys ??= Array.Empty(); _fullAccountPath = Nibbles.FromBytes(hashedAddress); @@ -90,7 +121,7 @@ internal AccountProofCollector(byte[] hashedAddress, params byte[][] storageKeys _accountProof.StorageProofs[i] = new StorageProof(); _accountProof.StorageProofs[i].Key = storageKeys[i]; - _accountProof.StorageProofs[i].Value = new byte[] {0}; + _accountProof.StorageProofs[i].Value = new byte[] { 0 }; } } @@ -103,7 +134,6 @@ public AccountProofCollector(Address address, params byte[][] storageKeys) public AccountProofCollector(Address address, UInt256[] storageKeys) : this(address, storageKeys.Select(ToKey).ToArray()) { - _accountProof.Address = _address = address ?? throw new ArgumentNullException(nameof(address)); } public AccountProof BuildResult() diff --git a/src/Nethermind/Nethermind.State/Proofs/ProofVerifier.cs b/src/Nethermind/Nethermind.State/Proofs/ProofVerifier.cs index ec7b4356919..cf9e899716b 100644 --- a/src/Nethermind/Nethermind.State/Proofs/ProofVerifier.cs +++ b/src/Nethermind/Nethermind.State/Proofs/ProofVerifier.cs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Nethermind.Core.Crypto; @@ -24,15 +26,17 @@ namespace Nethermind.State.Proofs { public static class ProofVerifier { - public static byte[]? Verify(byte[][] proof, Keccak root) + /// + /// Verifies one proof - address path from the bottom to the root. + /// + /// The Value of the bottom most proof node. For example an Account. + public static byte[]? VerifyOneProof(byte[][] proof, Keccak root) { if (proof.Length == 0) { return null; } - TrieNode trieNode = new(NodeType.Unknown, proof.Last()); - trieNode.ResolveNode(null); for (int i = proof.Length; i > 0; i--) { Keccak proofHash = Keccak.Compute(proof[i - 1]); @@ -52,6 +56,9 @@ public static class ProofVerifier } } + TrieNode trieNode = new(NodeType.Unknown, proof.Last()); + trieNode.ResolveNode(null); + return trieNode.Value; } } diff --git a/src/Nethermind/Nethermind.State/Snap/AccountRange.cs b/src/Nethermind/Nethermind.State/Snap/AccountRange.cs new file mode 100644 index 00000000000..e7e3c9946c1 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/AccountRange.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; + +namespace Nethermind.State.Snap +{ + public class AccountRange + { + public AccountRange(Keccak rootHash, Keccak startingHash, Keccak limitHash = null, long? blockNumber = null) + { + RootHash = rootHash; + StartingHash = startingHash; + BlockNumber = blockNumber; + LimitHash = limitHash; + } + + public long? BlockNumber { get; } + + /// + /// Root hash of the account trie to serve + /// + public Keccak RootHash { get;} + + /// + /// Account hash of the first to retrieve + /// + public Keccak StartingHash { get; } + + /// + /// Account hash after which to stop serving data + /// + public Keccak? LimitHash { get; } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/AccountsAndProofs.cs b/src/Nethermind/Nethermind.State/Snap/AccountsAndProofs.cs new file mode 100644 index 00000000000..dd8dfd93aa7 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/AccountsAndProofs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.State.Snap +{ + public class AccountsAndProofs + { + public PathWithAccount[] PathAndAccounts { get; set; } + public byte[][] Proofs { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/AccountsToRefreshRequest.cs b/src/Nethermind/Nethermind.State/Snap/AccountsToRefreshRequest.cs new file mode 100644 index 00000000000..38e4f27b3f0 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/AccountsToRefreshRequest.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.State.Snap +{ + public class AccountsToRefreshRequest + { + /// + /// Root hash of the account trie to serve + /// + public Keccak RootHash { get; set; } + + public AccountWithStorageStartingHash[] Paths { get; set; } + } + + public class AccountWithStorageStartingHash + { + public PathWithAccount PathAndAccount { get; set; } + public Keccak StorageStartingHash { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/Constants.cs b/src/Nethermind/Nethermind.State/Snap/Constants.cs new file mode 100644 index 00000000000..fb8695ae0ff --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/Constants.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.State.Snap +{ + public class Constants + { + public const int MaxDistanceFromHead = 128; + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/PathGroup.cs b/src/Nethermind/Nethermind.State/Snap/PathGroup.cs new file mode 100644 index 00000000000..2c43e705d98 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/PathGroup.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.State.Snap +{ + public class PathGroup + { + public byte[][] Group { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/PathWithAccount.cs b/src/Nethermind/Nethermind.State/Snap/PathWithAccount.cs new file mode 100644 index 00000000000..42adb52ef15 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/PathWithAccount.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.State.Snap +{ + public class PathWithAccount + { + public PathWithAccount() { } + + public PathWithAccount(Keccak path, Account account) + { + Path = path; + Account = account; + } + + public Keccak Path { get; set; } + public Account Account { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/PathWithStorageSlot.cs b/src/Nethermind/Nethermind.State/Snap/PathWithStorageSlot.cs new file mode 100644 index 00000000000..fccd61a4e3b --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/PathWithStorageSlot.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; + +namespace Nethermind.State.Snap +{ + public class PathWithStorageSlot + { + public PathWithStorageSlot(Keccak keyHash, byte[] slotRlpValue) + { + Path = keyHash; + SlotRlpValue = slotRlpValue; + } + + public Keccak Path { get; set; } + public byte[] SlotRlpValue { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/SlotsAndProofs.cs b/src/Nethermind/Nethermind.State/Snap/SlotsAndProofs.cs new file mode 100644 index 00000000000..b608a4aaa10 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/SlotsAndProofs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.State.Snap +{ + public class SlotsAndProofs + { + public PathWithStorageSlot[][] PathsAndSlots { get; set; } + public byte[][] Proofs { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/Storage/SnapLayer.cs b/src/Nethermind/Nethermind.State/Snap/Storage/SnapLayer.cs new file mode 100644 index 00000000000..c7c9a4d2ff2 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/Storage/SnapLayer.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; +using Nethermind.Trie; + +namespace Nethermind.State.Snap.Storage +{ + public class SnapLayer : SortedDictionary + { + + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/Storage/SnapStorage.cs b/src/Nethermind/Nethermind.State/Snap/Storage/SnapStorage.cs new file mode 100644 index 00000000000..7a5e772fe79 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/Storage/SnapStorage.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; +using Nethermind.Int256; +using Nethermind.Trie; + +namespace Nethermind.State.Snap.Storage +{ + public class SnapStorage + { + private UInt256 _bottomBlockNumber; + private SnapLayer _bottomLayer; + private Dictionary _layers = new(); + + /// + /// + /// + /// null if DELETE + public void AddLeafNode(Keccak path, TrieNode? leafNode, UInt256 blockNumber) + { + if (blockNumber < _bottomBlockNumber) + { + throw new ArgumentException($"Block number smaller than Bottom Block Number ({_bottomBlockNumber})", nameof(blockNumber)); + } + + ReorgLayers(blockNumber); + + SnapLayer layer; + if (blockNumber == _bottomBlockNumber) + { + layer = _bottomLayer; + } + else + { + if (_layers.TryGetValue(blockNumber, out SnapLayer existingLayer)) + { + layer = existingLayer; + } + else + { + layer = new(); + _layers.Add(blockNumber, layer); + } + } + + layer[path] = leafNode; + } + + public (Keccak path, TrieNode node)[] GetRange(Keccak startingHash, Keccak endHash, UInt256 blockNumber) + { + SnapLayer resultLayer = new(); + + FlattenLayers(startingHash, endHash, _bottomLayer, resultLayer); + + for (UInt256 i = _bottomBlockNumber + 1; i <= blockNumber; i++) + { + if (_layers.TryGetValue(i, out SnapLayer layer)) + { + FlattenLayers(startingHash, endHash, layer, resultLayer); + } + } + + return resultLayer.Select(i => (i.Key, i.Value)).ToArray(); + } + + private void FlattenLayers(Keccak startingHash, Keccak endHash, SnapLayer sourceLayer, SnapLayer resultLayer) + { + foreach (var item in sourceLayer.Where(n => n.Key >= startingHash && n.Key <= endHash)) + { + if(item.Value is null) + { + resultLayer.Remove(item.Key); + } + else + { + resultLayer[item.Key] = item.Value; + } + } + } + + private void ReorgLayers(UInt256 maxBlockNumber) + { + if(_bottomLayer is null) + { + _bottomLayer = new(); + _bottomBlockNumber = maxBlockNumber; + return; + } + + UInt256 newBottomBlockNumber = maxBlockNumber - Constants.MaxDistanceFromHead; + + if (newBottomBlockNumber <= _bottomBlockNumber) + { + return; + } + + for (UInt256 i = _bottomBlockNumber + 1; i <= newBottomBlockNumber; i++) + { + if (_layers.TryGetValue(i, out SnapLayer layer)) + { + foreach (var item in layer) + { + _bottomLayer[item.Key] = item.Value; + } + + _layers.Remove(i); + } + } + + _bottomBlockNumber = newBottomBlockNumber; + } + } +} diff --git a/src/Nethermind/Nethermind.State/Snap/StorageRange.cs b/src/Nethermind/Nethermind.State/Snap/StorageRange.cs new file mode 100644 index 00000000000..9b406c6e0d6 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Snap/StorageRange.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; + +namespace Nethermind.State.Snap +{ + public class StorageRange + { + public long? BlockNumber { get; set; } + + /// + /// Root hash of the account trie to serve + /// + public Keccak RootHash { get; set; } + + /// + /// Accounts of the storage tries to serve + /// + public PathWithAccount[] Accounts { get; set; } + + /// + /// Account hash of the first to retrieve + /// + public Keccak? StartingHash { get; set; } + + /// + /// Account hash after which to stop serving data + /// + public Keccak? LimitHash { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.State/StateTree.cs b/src/Nethermind/Nethermind.State/StateTree.cs index d7a56669af7..c28c437c29a 100644 --- a/src/Nethermind/Nethermind.State/StateTree.cs +++ b/src/Nethermind/Nethermind.State/StateTree.cs @@ -56,7 +56,7 @@ public StateTree(ITrieStore? store, ILogManager? logManager) return _decoder.Decode(bytes.AsRlpStream()); } - + [DebuggerStepThrough] internal Account? Get(Keccak keccak) // for testing { @@ -76,9 +76,12 @@ public void Set(Address address, Account? account) } [DebuggerStepThrough] - internal void Set(Keccak keccak, Account? account) // for testing + public Rlp? Set(Keccak keccak, Account? account) { - Set(keccak.Bytes, account is null ? null : account.IsTotallyEmpty ? EmptyAccountRlp : Rlp.Encode(account)); + Rlp rlp = account is null ? null : account.IsTotallyEmpty ? EmptyAccountRlp : Rlp.Encode(account); + + Set(keccak.Bytes, rlp); + return rlp; } } } diff --git a/src/Nethermind/Nethermind.State/StorageTree.cs b/src/Nethermind/Nethermind.State/StorageTree.cs index 8453fc940a8..2d62e5d1f94 100644 --- a/src/Nethermind/Nethermind.State/StorageTree.cs +++ b/src/Nethermind/Nethermind.State/StorageTree.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -45,7 +45,13 @@ static StorageTree() Cache[index] = Keccak.Compute(buffer).Bytes; } } - + + public StorageTree(ITrieStore? trieStore, ILogManager? logManager) + : base(trieStore, Keccak.EmptyTreeHash, false, true, logManager) + { + TrieType = TrieType.Storage; + } + public StorageTree(ITrieStore? trieStore, Keccak rootHash, ILogManager? logManager) : base(trieStore, rootHash, false, true, logManager) { @@ -80,14 +86,26 @@ public byte[] Get(in UInt256 index, Keccak? storageRoot = null) } public void Set(in UInt256 index, byte[] value) + { + var key = GetKey(index); + SetInternal(key, value); + } + + public void Set(Keccak key, byte[] value, bool rlpEncode = true) + { + SetInternal(key.Bytes, value, rlpEncode); + } + + private void SetInternal(Span rawKey, byte[] value, bool rlpEncode = true) { if (value.IsZero()) { - Set(GetKey(index), Array.Empty()); + Set(rawKey, Array.Empty()); } else { - Set(GetKey(index), Rlp.Encode(value)); + Rlp rlpEncoded = rlpEncode ? Rlp.Encode(value) : new Rlp(value); + Set(rawKey, rlpEncoded); } } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs index 5166ef4fdcf..f7803f2d0cc 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs @@ -48,6 +48,7 @@ using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; using Nethermind.Synchronization.Reporting; +using Nethermind.Synchronization.SnapSync; using Nethermind.Trie.Pruning; using Nethermind.TxPool; using NSubstitute; @@ -525,6 +526,7 @@ public ThrowingPeer(long number, UInt256 totalDiff, Keccak headHash = null) public long HeadNumber { get; set; } public UInt256 TotalDifficulty { get; set; } = UInt256.MaxValue; public bool IsInitialized { get; set; } + public bool IsPriority { get; set; } public void Disconnect(DisconnectReason reason, string details) { @@ -872,11 +874,13 @@ public Context(BlockTree? blockTree = null) MemDb stateDb = new(); SyncConfig syncConfig = new(); + ProgressTracker progressTracker = new(BlockTree, stateDb, LimboLogs.Instance); SyncProgressResolver syncProgressResolver = new( BlockTree, NullReceiptStorage.Instance, stateDb, new TrieStore(stateDb, LimboLogs.Instance), + progressTracker, syncConfig, LimboLogs.Instance); TotalDifficultyBasedBetterPeerStrategy bestPeerStrategy = @@ -946,6 +950,7 @@ public void ExtendTree(long newLength) public long HeadNumber { get; set; } public UInt256 TotalDifficulty { get; set; } public bool IsInitialized { get; set; } + public bool IsPriority { get; set; } public void Disconnect(DisconnectReason reason, string details) { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs index dcaddb7e6d5..2aa4fd7c2a8 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/PendingSyncItemsTests.cs @@ -26,29 +26,29 @@ namespace Nethermind.Synchronization.Test.FastSync [TestFixture] public class PendingSyncItemsTests { - private StateSyncFeed.IPendingSyncItems Init() + private IPendingSyncItems Init() { - return new StateSyncFeed.PendingSyncItems(); + return new PendingSyncItems(); } [Test] public void At_start_count_is_zero() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.Count.Should().Be(0); } [Test] public void Description_does_not_throw_at_start() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.Description.Should().NotBeNullOrWhiteSpace(); } [Test] public void Max_levels_should_be_zero_at_start() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.MaxStateLevel.Should().Be(0); items.MaxStorageLevel.Should().Be(0); } @@ -56,29 +56,29 @@ public void Max_levels_should_be_zero_at_start() [Test] public void Can_recalculate_priorities_at_start() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.RecalculatePriorities().Should().NotBeNullOrWhiteSpace(); } [Test] public void Peek_state_is_null_at_start() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.PeekState().Should().Be(null); } [Test] public void Can_clear_at_start() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.Clear(); } [Test] public void Can_peek_root() { - StateSyncFeed.IPendingSyncItems items = Init(); - StateSyncItem stateSyncItem = new(Keccak.Zero, NodeDataType.State, 0, 0); + IPendingSyncItems items = Init(); + StateSyncItem stateSyncItem = new (Keccak.Zero, null, null, NodeDataType.State, 0, 0); items.PushToSelectedStream(stateSyncItem, 0); items.PeekState().Should().Be(stateSyncItem); } @@ -86,8 +86,8 @@ public void Can_peek_root() [Test] public void Can_recalculate_and_clear_with_root_only() { - StateSyncFeed.IPendingSyncItems items = Init(); - StateSyncItem stateSyncItem = new(Keccak.Zero, NodeDataType.State, 0, 0); + IPendingSyncItems items = Init(); + StateSyncItem stateSyncItem = new (Keccak.Zero, null, null, NodeDataType.State, 0, 0); items.PushToSelectedStream(stateSyncItem, 0); items.RecalculatePriorities(); items.Clear(); @@ -97,7 +97,7 @@ public void Can_recalculate_and_clear_with_root_only() [Test] public void Prioritizes_depth() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.MaxStateLevel = 64; PushState(items, 0, 0); @@ -115,7 +115,7 @@ public void Prioritizes_depth() [Test] public void Prioritizes_code_over_storage_over_state() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.MaxStateLevel = 64; PushState(items, 64, 0); @@ -133,7 +133,7 @@ public void Prioritizes_code_over_storage_over_state() [Test] public void Prefers_left() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.MaxStateLevel = 64; PushState(items, 1, 10000); // something far-right @@ -150,7 +150,7 @@ public void Prefers_left() [Test] public void Prefers_left_single_branch() { - StateSyncFeed.IPendingSyncItems items = Init(); + IPendingSyncItems items = Init(); items.MaxStateLevel = 64; PushState(items, 1, 15); // branch child 16 @@ -164,24 +164,24 @@ public void Prefers_left_single_branch() batch[2].Rightness.Should().Be(15); } - private static StateSyncItem PushCode(StateSyncFeed.IPendingSyncItems items, int progress = 0) + private static StateSyncItem PushCode(IPendingSyncItems items, int progress = 0) { return PushItem(items, NodeDataType.Code, 0, 0, progress); } - private static StateSyncItem PushStorage(StateSyncFeed.IPendingSyncItems items, int level, uint rightness, int progress = 0) + private static StateSyncItem PushStorage(IPendingSyncItems items, int level, uint rightness, int progress = 0) { return PushItem(items, NodeDataType.Storage, level, rightness, progress); } - private static StateSyncItem PushState(StateSyncFeed.IPendingSyncItems items, int level, uint rightness, int progress = 0) + private static StateSyncItem PushState(IPendingSyncItems items, int level, uint rightness, int progress = 0) { return PushItem(items, NodeDataType.State, level, rightness, progress); } - private static StateSyncItem PushItem(StateSyncFeed.IPendingSyncItems items, NodeDataType nodeDataType, int level, uint rightness, int progress = 0) + private static StateSyncItem PushItem(IPendingSyncItems items, NodeDataType nodeDataType, int level, uint rightness, int progress = 0) { - StateSyncItem stateSyncItem1 = new(Keccak.Zero, nodeDataType, level, rightness); + StateSyncItem stateSyncItem1 = new (Keccak.Zero, null, null, nodeDataType, level, rightness); items.PushToSelectedStream(stateSyncItem1, progress); return stateSyncItem1; } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs new file mode 100644 index 00000000000..7e2de3dd9bb --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.State.Snap; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.SnapSync; +using Nethermind.Trie.Pruning; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.FastSync +{ + [TestFixture] + [Parallelizable(ParallelScope.All)] + public class StateSyncFeedHealingTests : StateSyncFeedTestsBase + { + [Test] + public async Task HealTreeWithoutBoundaryProofs() + { + DbContext dbContext = new DbContext(_logger, _logManager); + TestItem.Tree.FillStateTreeWithTestAccounts(dbContext.RemoteStateTree); + + Keccak rootHash = dbContext.RemoteStateTree.RootHash; + + ProcessAccountRange(dbContext.RemoteStateTree, dbContext.LocalStateTree, 1, rootHash, TestItem.Tree.AccountsWithPaths); + + SyncPeerMock mock = new SyncPeerMock(dbContext.RemoteStateDb, dbContext.RemoteCodeDb); + + SafeContext ctx = PrepareDownloader(dbContext, mock); + await ActivateAndWait(ctx, dbContext, 1024); + + DetailedProgress data = ctx.TreeFeed.GetDetailedProgress(); + + + dbContext.CompareTrees("END"); + Assert.AreEqual(dbContext.RemoteStateTree.RootHash, dbContext.LocalStateTree.RootHash); + Assert.AreEqual(0, data.RequestedNodesCount); // 4 boundary proof nodes stitched together => 0 + } + + [Test] + public async Task HealBigSquezedRandomTree() + { + DbContext dbContext = new DbContext(_logger, _logManager); + + int pathPoolCount = 100_000; + Keccak[] pathPool = new Keccak[pathPoolCount]; + SortedDictionary accounts = new(); + int updatesCount = 0; + int deletionsCount = 0; + + for (int i = 0; i < pathPoolCount; i++) + { + byte[] key = new byte[32]; + ((UInt256)i).ToBigEndian(key); + Keccak keccak = new Keccak(key); + pathPool[i] = keccak; + } + + // generate Remote Tree + for (int accountIndex = 0; accountIndex < 10000; accountIndex++) + { + Account account = TestItem.GenerateRandomAccount(); + Keccak path = pathPool[TestItem.Random.Next(pathPool.Length - 1)]; + + dbContext.RemoteStateTree.Set(path, account); + accounts[path] = account; + } + + dbContext.RemoteStateTree.Commit(0); + + int startingHashIndex = 0; + int endHashIndex = 0; + int blockJumps = 5; + for (int blockNumber = 1; blockNumber <= blockJumps; blockNumber++) + { + for (int i = 0; i < 19; i++) + { + endHashIndex = startingHashIndex + 1000; + + ProcessAccountRange(dbContext.RemoteStateTree, dbContext.LocalStateTree, blockNumber, dbContext.RemoteStateTree.RootHash, + accounts.Where(a => a.Key >= pathPool[startingHashIndex] && a.Key <= pathPool[endHashIndex]).Select(a => new PathWithAccount(a.Key, a.Value)).ToArray()); + + startingHashIndex = endHashIndex + 1; + } + + for (int accountIndex = 0; accountIndex < 1000; accountIndex++) + { + Account account = TestItem.GenerateRandomAccount(); + Keccak path = pathPool[TestItem.Random.Next(pathPool.Length - 1)]; + + if (accounts.ContainsKey(path)) + { + if (TestItem.Random.NextSingle() > 0.5) + { + dbContext.RemoteStateTree.Set(path, account); + accounts[path] = account; + updatesCount++; + } + else + { + dbContext.RemoteStateTree.Set(path, null); + accounts.Remove(path); + deletionsCount++; + } + + + } + else + { + dbContext.RemoteStateTree.Set(path, account); + accounts[path] = account; + } + } + + dbContext.RemoteStateTree.Commit(blockNumber); + } + + endHashIndex = startingHashIndex + 1000; + while (endHashIndex < pathPool.Length - 1) + { + endHashIndex = startingHashIndex + 1000; + if (endHashIndex > pathPool.Length - 1) + { + endHashIndex = pathPool.Length - 1; + } + + ProcessAccountRange(dbContext.RemoteStateTree, dbContext.LocalStateTree, blockJumps, dbContext.RemoteStateTree.RootHash, + accounts.Where(a => a.Key >= pathPool[startingHashIndex] && a.Key <= pathPool[endHashIndex]).Select(a => new PathWithAccount(a.Key, a.Value)).ToArray()); + + + startingHashIndex += 1000; + } + + SyncPeerMock mock = new SyncPeerMock(dbContext.RemoteStateDb, dbContext.RemoteCodeDb); + + dbContext.LocalStateTree.RootHash = dbContext.RemoteStateTree.RootHash; + SafeContext ctx = PrepareDownloader(dbContext, mock); + await ActivateAndWait(ctx, dbContext, 9); + + DetailedProgress data = ctx.TreeFeed.GetDetailedProgress(); + + dbContext.LocalStateTree.UpdateRootHash(); + dbContext.CompareTrees("END"); + _logger.Info($"REQUESTED NODES TO HEAL: {data.RequestedNodesCount}"); + Assert.IsTrue(data.RequestedNodesCount < accounts.Count/2); + } + + private static void ProcessAccountRange(StateTree remoteStateTree, StateTree localStateTree, int blockNumber, Keccak rootHash, PathWithAccount[] accounts) + { + Keccak startingHash = accounts.First().Path; + Keccak endHash = accounts.Last().Path; + + AccountProofCollector accountProofCollector = new(startingHash.Bytes); + remoteStateTree.Accept(accountProofCollector, remoteStateTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(endHash.Bytes); + remoteStateTree.Accept(accountProofCollector, remoteStateTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + (_, _, _, _) = SnapProviderHelper.AddAccountRange(localStateTree, blockNumber, rootHash, startingHash, accounts, firstProof!.Concat(lastProof!).ToArray()); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs index 11d2fa51f2e..c26f9566245 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs @@ -50,263 +50,8 @@ namespace Nethermind.Synchronization.Test.FastSync { [TestFixture] [Parallelizable(ParallelScope.All)] - public class StateSyncFeedTests + public class StateSyncFeedTests : StateSyncFeedTestsBase { - private static IBlockTree _blockTree; - private static IBlockTree BlockTree => LazyInitializer.EnsureInitialized(ref _blockTree, () => Build.A.BlockTree().OfChainLength(100).TestObject); - - private ILogger _logger; - private ILogManager _logManager; - - private class SafeContext - { - public ISyncModeSelector SyncModeSelector; - public ISyncPeerPool Pool; - public StateSyncFeed Feed; - public StateSyncDispatcher StateSyncDispatcher; - } - - [SetUp] - public void Setup() - { - _logManager = NUnitLogManager.Instance;// LimboLogs.Instance; - _logger = new NUnitLogger(LogLevel.Info);// LimboTraceLogger.Instance; - TrieScenarios.InitOnce(); - } - - [TearDown] - public void TearDown() - { - (_logger as ConsoleAsyncLogger)?.Flush(); - } - - private static StorageTree SetStorage(ITrieStore trieStore, byte i) - { - StorageTree remoteStorageTree = new(trieStore, Keccak.EmptyTreeHash, LimboLogs.Instance); - for (int j = 0; j < i; j++) remoteStorageTree.Set((UInt256) j, new[] {(byte) j, i}); - - remoteStorageTree.Commit(0); - return remoteStorageTree; - } - - private class DbContext - { - private readonly ILogger _logger; - - public DbContext(ILogger logger, ILogManager logManager) - { - _logger = logger; - RemoteDb = new MemDb(); - LocalDb = new MemDb(); - RemoteStateDb = RemoteDb; - LocalStateDb = LocalDb; - LocalCodeDb = new MemDb(); - RemoteCodeDb = new MemDb(); - RemoteTrieStore = new TrieStore(RemoteStateDb, logManager); - - RemoteStateTree = new StateTree(RemoteTrieStore, logManager); - LocalStateTree = new StateTree(new TrieStore(LocalStateDb, logManager), logManager); - } - - public IDb RemoteCodeDb { get; } - public IDb LocalCodeDb { get; } - public MemDb RemoteDb { get; } - public MemDb LocalDb { get; } - public ITrieStore RemoteTrieStore { get; } - public IDb RemoteStateDb { get; } - public IDb LocalStateDb { get; } - public StateTree RemoteStateTree { get; } - public StateTree LocalStateTree { get; } - - public void CompareTrees(string stage, bool skipLogs = false) - { - if (!skipLogs) _logger.Info($"==================== {stage} ===================="); - LocalStateTree.RootHash = RemoteStateTree.RootHash; - - if (!skipLogs) _logger.Info("-------------------- REMOTE --------------------"); - TreeDumper dumper = new(); - RemoteStateTree.Accept(dumper, RemoteStateTree.RootHash); - string remote = dumper.ToString(); - if (!skipLogs) _logger.Info(remote); - if (!skipLogs) _logger.Info("-------------------- LOCAL --------------------"); - dumper.Reset(); - LocalStateTree.Accept(dumper, LocalStateTree.RootHash); - string local = dumper.ToString(); - if (!skipLogs) _logger.Info(local); - - if (stage == "END") - { - Assert.AreEqual(remote, local, $"{remote}{Environment.NewLine}{local}"); - TrieStatsCollector collector = new(LocalCodeDb, new OneLoggerLogManager(_logger)); - LocalStateTree.Accept(collector, LocalStateTree.RootHash); - Assert.AreEqual(0, collector.Stats.MissingNodes); - Assert.AreEqual(0, collector.Stats.MissingCode); - } - - // Assert.AreEqual(dbContext._remoteCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); - // Assert.AreEqual(dbContext._remoteCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); - // - // Assert.AreEqual(dbContext._remoteDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); - // Assert.AreEqual(dbContext._remoteDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); - } - - public void CompareCodeDbs() - { - // Assert.AreEqual(dbContext._remoteCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); - // Assert.AreEqual(dbContext._remoteCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); - - // Assert.AreEqual(dbContext._remoteDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); - // Assert.AreEqual(dbContext._remoteDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); - } - } - - private class SyncPeerMock : ISyncPeer - { - public static Func, Task> NotPreimage = request => - { - var result = new byte[request.Count][]; - - int i = 0; - foreach (Keccak _ in request) result[i++] = new byte[] {1, 2, 3}; - - return Task.FromResult(result); - }; - - public static Func, Task> EmptyArraysInResponses = request => - { - var result = new byte[request.Count][]; - - int i = 0; - foreach (Keccak _ in request) result[i++] = new byte[0]; - - return Task.FromResult(result); - }; - - private readonly IDb _codeDb; - private readonly IDb _stateDb; - - private Func, Task> _executorResultFunction; - - private Keccak[] _filter; - - public SyncPeerMock(IDb stateDb, IDb codeDb, Func, Task> executorResultFunction = null) - { - _stateDb = stateDb; - _codeDb = codeDb; - - if (executorResultFunction != null) _executorResultFunction = executorResultFunction; - - Node = new Node(TestItem.PublicKeyA, "127.0.0.1", 30302, true); - } - - public int MaxResponseLength { get; set; } = int.MaxValue; - - public Node Node { get; } - public string ClientId => "executorMock"; - public Keccak HeadHash { get; set; } - public long HeadNumber { get; set; } - public UInt256 TotalDifficulty { get; set; } - public bool IsInitialized { get; set; } - - public void Disconnect(DisconnectReason reason, string details) - { - throw new NotImplementedException(); - } - - public Task GetBlockBodies(IReadOnlyList blockHashes, CancellationToken token) - { - throw new NotImplementedException(); - } - - public Task GetBlockHeaders(Keccak blockHash, int maxBlocks, int skip, CancellationToken token) - { - throw new NotImplementedException(); - } - - public Task GetBlockHeaders(long number, int maxBlocks, int skip, CancellationToken token) - { - throw new NotImplementedException(); - } - - public Task GetHeadBlockHeader(Keccak hash, CancellationToken token) - { - return Task.FromResult(BlockTree.Head?.Header); - } - - public void NotifyOfNewBlock(Block block, SendBlockPriority priority) - { - throw new NotImplementedException(); - } - - public PublicKey Id => Node.Id; - - public void SendNewTransactions(IEnumerable txs, bool sendFullTx) - { - throw new NotImplementedException(); - } - - public Task GetReceipts(IReadOnlyList blockHash, CancellationToken token) - { - throw new NotImplementedException(); - } - - public Task GetNodeData(IReadOnlyList hashes, CancellationToken token) - { - if (_executorResultFunction != null) return _executorResultFunction(hashes); - - var responses = new byte[hashes.Count][]; - - int i = 0; - foreach (Keccak item in hashes) - { - if (i >= MaxResponseLength) break; - - if (_filter == null || _filter.Contains(item)) responses[i] = _stateDb[item.Bytes] ?? _codeDb[item.Bytes]; - - i++; - } - - return Task.FromResult(responses); - } - - public void SetFilter(Keccak[] availableHashes) - { - _filter = availableHashes; - } - - public void RegisterSatelliteProtocol(string protocol, T protocolHandler) where T : class - { - throw new NotImplementedException(); - } - - public bool TryGetSatelliteProtocol(string protocol, out T protocolHandler) where T : class - { - throw new NotImplementedException(); - } - } - - private SafeContext PrepareDownloader(DbContext dbContext, ISyncPeer syncPeer) - { - SafeContext ctx = new(); - ctx = new SafeContext(); - BlockTree blockTree = Build.A.BlockTree().OfChainLength((int) BlockTree.BestSuggestedHeader.Number).TestObject; - ITimerFactory timerFactory = Substitute.For(); - ctx.Pool = new SyncPeerPool(blockTree, new NodeStatsManager(timerFactory, LimboLogs.Instance), new TotalDifficultyBasedBetterPeerStrategy(null, LimboLogs.Instance), 25, LimboLogs.Instance); - ctx.Pool.Start(); - ctx.Pool.AddPeer(syncPeer); - - SyncConfig syncConfig = new(); - syncConfig.FastSync = true; - ctx.SyncModeSelector = StaticSelector.StateNodesWithFastBlocks; - ctx.Feed = new StateSyncFeed(dbContext.LocalCodeDb, dbContext.LocalStateDb, ctx.SyncModeSelector, blockTree, _logManager); - ctx.StateSyncDispatcher = - new StateSyncDispatcher(ctx.Feed, ctx.Pool, new StateSyncAllocationStrategyFactory(), _logManager); - ctx.StateSyncDispatcher.Start(CancellationToken.None); - return ctx; - } - - private const int TimeoutLength = 1000; - [Test] [TestCaseSource(nameof(Scenarios))] [Retry(3)] @@ -353,24 +98,22 @@ public async Task Big_test((string Name, Action Setu .Set(TestItem.Addresses[i], TrieScenarios.AccountJustState0.WithChangedBalance(i) .WithChangedNonce(2) .WithChangedCodeHash(Keccak.Compute(TrieScenarios.Code3)) - .WithChangedStorageRoot(SetStorage(dbContext.RemoteTrieStore, (byte) (i % 7)).RootHash)); + .WithChangedStorageRoot(SetStorage(dbContext.RemoteTrieStore, (byte)(i % 7)).RootHash)); dbContext.RemoteStateTree.UpdateRootHash(); dbContext.RemoteStateTree.Commit(0); - + ctx.Feed.FallAsleep(); ctx.Pool.WakeUpAll(); mock.SetFilter(null); await ActivateAndWait(ctx, dbContext, 1024); - + dbContext.CompareTrees("END"); dbContext.CompareCodeDbs(); } - public static (string Name, Action Action)[] Scenarios => TrieScenarios.Scenarios; - [Test] [TestCaseSource(nameof(Scenarios))] // [Retry(3)] @@ -378,14 +121,14 @@ public async Task Can_download_a_full_state((string Name, Action - { - if (e.NewState == SyncFeedState.Dormant) - { - dormantAgainSource.TrySetResult(0); - } - }; - - safeContext.Feed.ResetStateRoot(blockNumber, dbContext.RemoteStateTree.RootHash); - safeContext.Feed.Activate(); - var watch = Stopwatch.StartNew(); - await Task.WhenAny( - dormantAgainSource.Task, - Task.Delay(timeout)); - TestContext.WriteLine($"Waited {watch.ElapsedMilliseconds}"); + dbContext.CompareTrees("END"); } [Test] @@ -451,7 +174,7 @@ public async Task Can_download_when_executor_sends_shorter_responses((string Nam { DbContext dbContext = new(_logger, _logManager); testCase.SetupTree(dbContext.RemoteStateTree, dbContext.RemoteTrieStore, dbContext.RemoteCodeDb); - + dbContext.CompareTrees("BEGIN"); @@ -460,7 +183,7 @@ public async Task Can_download_when_executor_sends_shorter_responses((string Nam SafeContext ctx = PrepareDownloader(dbContext, mock); await ActivateAndWait(ctx, dbContext, 1024); - + dbContext.CompareTrees("END"); } @@ -472,7 +195,7 @@ public async Task When_saving_root_goes_asleep() DbContext dbContext = new(_logger, _logManager); dbContext.RemoteStateTree.Set(TestItem.KeccakA, Build.An.Account.TestObject); dbContext.RemoteStateTree.Commit(0); - + dbContext.CompareTrees("BEGIN"); @@ -480,7 +203,7 @@ public async Task When_saving_root_goes_asleep() SafeContext ctx = PrepareDownloader(dbContext, mock); await ActivateAndWait(ctx, dbContext, 1024); - + dbContext.CompareTrees("END"); ctx.Feed.CurrentState.Should().Be(SyncFeedState.Dormant); @@ -493,7 +216,7 @@ public async Task Can_download_with_moving_target((string Name, Action new Keccak(k)).ToArray()); @@ -502,7 +225,7 @@ public async Task Can_download_with_moving_target((string Name, Action. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using DotNetty.Common.Concurrency; +using FluentAssertions; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Timers; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.Stats; +using Nethermind.Stats.Model; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.StateSync; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; +using NSubstitute; +using NUnit.Framework; +using BlockTree = Nethermind.Blockchain.BlockTree; + +namespace Nethermind.Synchronization.Test.FastSync +{ + public class StateSyncFeedTestsBase + { + private const int TimeoutLength = 1000; + + protected static IBlockTree _blockTree; + private static IBlockTree BlockTree => LazyInitializer.EnsureInitialized(ref _blockTree, () => Build.A.BlockTree().OfChainLength(100).TestObject); + + protected ILogger _logger; + protected ILogManager _logManager; + + public static (string Name, Action Action)[] Scenarios => TrieScenarios.Scenarios; + + [SetUp] + public void Setup() + { + _logManager = NUnitLogManager.Instance;// LimboLogs.Instance; + _logger = new NUnitLogger(LogLevel.Info);// LimboTraceLogger.Instance; + TrieScenarios.InitOnce(); + } + + [TearDown] + public void TearDown() + { + (_logger as ConsoleAsyncLogger)?.Flush(); + } + + protected static StorageTree SetStorage(ITrieStore trieStore, byte i) + { + StorageTree remoteStorageTree = new StorageTree(trieStore, Keccak.EmptyTreeHash, LimboLogs.Instance); + for (int j = 0; j < i; j++) remoteStorageTree.Set((UInt256) j, new[] {(byte) j, i}); + + remoteStorageTree.Commit(0); + return remoteStorageTree; + } + + protected SafeContext PrepareDownloader(DbContext dbContext, ISyncPeer syncPeer) + { + SafeContext ctx = new SafeContext(); + ctx = new SafeContext(); + BlockTree blockTree = Build.A.BlockTree().OfChainLength((int)BlockTree.BestSuggestedHeader.Number).TestObject; + ITimerFactory timerFactory = Substitute.For(); + ctx.Pool = new SyncPeerPool(blockTree, new NodeStatsManager(timerFactory, LimboLogs.Instance), new TotalDifficultyBasedBetterPeerStrategy(null, LimboLogs.Instance), 25, LimboLogs.Instance); + ctx.Pool.Start(); + ctx.Pool.AddPeer(syncPeer); + + SyncConfig syncConfig = new SyncConfig(); + syncConfig.FastSync = true; + ctx.SyncModeSelector = StaticSelector.StateNodesWithFastBlocks; + ctx.TreeFeed = new(SyncMode.StateNodes, dbContext.LocalCodeDb, dbContext.LocalStateDb, blockTree, _logManager); + ctx.Feed = new StateSyncFeed(ctx.SyncModeSelector, ctx.TreeFeed, _logManager); + ctx.StateSyncDispatcher = + new StateSyncDispatcher(ctx.Feed, ctx.Pool, new StateSyncAllocationStrategyFactory(), _logManager); + ctx.StateSyncDispatcher.Start(CancellationToken.None); + return ctx; + } + + protected async Task ActivateAndWait(SafeContext safeContext, DbContext dbContext, long blockNumber, int timeout = TimeoutLength) + { + DotNetty.Common.Concurrency.TaskCompletionSource dormantAgainSource = new DotNetty.Common.Concurrency.TaskCompletionSource(); + safeContext.Feed.StateChanged += (s, e) => + { + if (e.NewState == SyncFeedState.Dormant) + { + dormantAgainSource.TrySetResult(0); + } + }; + + safeContext.TreeFeed.ResetStateRoot(blockNumber, dbContext.RemoteStateTree.RootHash, safeContext.Feed.CurrentState); + safeContext.Feed.Activate(); + var watch = Stopwatch.StartNew(); + await Task.WhenAny( + dormantAgainSource.Task, + Task.Delay(timeout)); + TestContext.WriteLine($"Waited {watch.ElapsedMilliseconds}"); + } + + protected class SafeContext + { + public ISyncModeSelector SyncModeSelector; + public ISyncPeerPool Pool; + public TreeSync TreeFeed; + public StateSyncFeed Feed; + public StateSyncDispatcher StateSyncDispatcher; + } + + protected class DbContext + { + private readonly ILogger _logger; + + public DbContext(ILogger logger, ILogManager logManager) + { + _logger = logger; + RemoteDb = new MemDb(); + LocalDb = new MemDb(); + RemoteStateDb = RemoteDb; + LocalStateDb = LocalDb; + LocalCodeDb = new MemDb(); + RemoteCodeDb = new MemDb(); + RemoteTrieStore = new TrieStore(RemoteStateDb, logManager); + + RemoteStateTree = new StateTree(RemoteTrieStore, logManager); + LocalStateTree = new StateTree(new TrieStore(LocalStateDb, logManager), logManager); + } + + public IDb RemoteCodeDb { get; } + public IDb LocalCodeDb { get; } + public MemDb RemoteDb { get; } + public MemDb LocalDb { get; } + public ITrieStore RemoteTrieStore { get; } + public IDb RemoteStateDb { get; } + public IDb LocalStateDb { get; } + public StateTree RemoteStateTree { get; } + public StateTree LocalStateTree { get; } + + public void CompareTrees(string stage, bool skipLogs = false) + { + if (!skipLogs) _logger.Info($"==================== {stage} ===================="); + LocalStateTree.RootHash = RemoteStateTree.RootHash; + + if (!skipLogs) _logger.Info("-------------------- REMOTE --------------------"); + TreeDumper dumper = new TreeDumper(); + RemoteStateTree.Accept(dumper, RemoteStateTree.RootHash); + string remote = dumper.ToString(); + if (!skipLogs) _logger.Info(remote); + if (!skipLogs) _logger.Info("-------------------- LOCAL --------------------"); + dumper.Reset(); + LocalStateTree.Accept(dumper, LocalStateTree.RootHash); + string local = dumper.ToString(); + if (!skipLogs) _logger.Info(local); + + if (stage == "END") + { + Assert.AreEqual(remote, local, $"{remote}{Environment.NewLine}{local}"); + TrieStatsCollector collector = new TrieStatsCollector(LocalCodeDb, new OneLoggerLogManager(_logger)); + LocalStateTree.Accept(collector, LocalStateTree.RootHash); + Assert.AreEqual(0, collector.Stats.MissingNodes); + Assert.AreEqual(0, collector.Stats.MissingCode); + } + + // Assert.AreEqual(dbContext._remoteCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); + // Assert.AreEqual(dbContext._remoteCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); + // + // Assert.AreEqual(dbContext._remoteDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); + // Assert.AreEqual(dbContext._remoteDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); + } + + public void CompareCodeDbs() + { + // Assert.AreEqual(dbContext._remoteCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); + // Assert.AreEqual(dbContext._remoteCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), dbContext._localCodeDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); + + // Assert.AreEqual(dbContext._remoteDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Keys.OrderBy(k => k, Bytes.Comparer).ToArray(), "keys"); + // Assert.AreEqual(dbContext._remoteDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), _localDb.Values.OrderBy(k => k, Bytes.Comparer).ToArray(), "values"); + } + } + + protected class SyncPeerMock : ISyncPeer + { + public static Func, Task> NotPreimage = request => + { + var result = new byte[request.Count][]; + + int i = 0; + foreach (Keccak _ in request) result[i++] = new byte[] {1, 2, 3}; + + return Task.FromResult(result); + }; + + public static Func, Task> EmptyArraysInResponses = request => + { + var result = new byte[request.Count][]; + + int i = 0; + foreach (Keccak _ in request) result[i++] = new byte[0]; + + return Task.FromResult(result); + }; + + private readonly IDb _codeDb; + private readonly IDb _stateDb; + + private Func, Task> _executorResultFunction; + + private Keccak[] _filter; + + public SyncPeerMock(IDb stateDb, IDb codeDb, Func, Task> executorResultFunction = null) + { + _stateDb = stateDb; + _codeDb = codeDb; + + if (executorResultFunction != null) _executorResultFunction = executorResultFunction; + + Node = new Node(TestItem.PublicKeyA, "127.0.0.1", 30302, true); + } + + public int MaxResponseLength { get; set; } = int.MaxValue; + + public Node Node { get; } + public string ClientId => "executorMock"; + public Keccak HeadHash { get; set; } + public long HeadNumber { get; set; } + public UInt256 TotalDifficulty { get; set; } + public bool IsInitialized { get; set; } + public bool IsPriority { get; set; } + + public void Disconnect(DisconnectReason reason, string details) + { + throw new NotImplementedException(); + } + + public Task GetBlockBodies(IReadOnlyList blockHashes, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task GetBlockHeaders(Keccak blockHash, int maxBlocks, int skip, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task GetBlockHeaders(long number, int maxBlocks, int skip, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task GetHeadBlockHeader(Keccak hash, CancellationToken token) + { + return Task.FromResult(BlockTree.Head?.Header); + } + + public void NotifyOfNewBlock(Block block, SendBlockPriority priority) + { + throw new NotImplementedException(); + } + + public PublicKey Id => Node.Id; + + public void SendNewTransactions(IEnumerable txs, bool sendFullTx) + { + throw new NotImplementedException(); + } + + public Task GetReceipts(IReadOnlyList blockHash, CancellationToken token) + { + throw new NotImplementedException(); + } + + public Task GetNodeData(IReadOnlyList hashes, CancellationToken token) + { + if (_executorResultFunction != null) return _executorResultFunction(hashes); + + var responses = new byte[hashes.Count][]; + + int i = 0; + foreach (Keccak item in hashes) + { + if (i >= MaxResponseLength) break; + + if (_filter == null || _filter.Contains(item)) responses[i] = _stateDb[item.Bytes] ?? _codeDb[item.Bytes]; + + i++; + } + + return Task.FromResult(responses); + } + + public void SetFilter(Keccak[] availableHashes) + { + _filter = availableHashes; + } + + public void RegisterSatelliteProtocol(string protocol, T protocolHandler) where T : class + { + throw new NotImplementedException(); + } + + public bool TryGetSatelliteProtocol(string protocol, out T protocolHandler) where T : class + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/LatencySyncPeerMock.cs b/src/Nethermind/Nethermind.Synchronization.Test/LatencySyncPeerMock.cs index 6c2bb9008e1..00a617a912b 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/LatencySyncPeerMock.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/LatencySyncPeerMock.cs @@ -66,6 +66,7 @@ public LatencySyncPeerMock(IBlockTree tree, int latency = 5) public Keccak HeadHash { get; set; } public UInt256 TotalDifficulty { get; set; } public bool IsInitialized { get; set; } = true; + public bool IsPriority { get; set; } public void Disconnect(DisconnectReason reason, string details) { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/OldStyleFullSynchronizerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/OldStyleFullSynchronizerTests.cs index fde9c3f7d49..bf660d170c1 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/OldStyleFullSynchronizerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/OldStyleFullSynchronizerTests.cs @@ -36,6 +36,7 @@ using Nethermind.Synchronization.Blocks; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.SnapSync; using Nethermind.Trie.Pruning; using NSubstitute; using NUnit.Framework; @@ -65,13 +66,18 @@ public async Task Setup() NodeStatsManager stats = new(timerFactory, LimboLogs.Instance); _pool = new SyncPeerPool(_blockTree, stats, new TotalDifficultyBasedBetterPeerStrategy(null, LimboLogs.Instance), 25, LimboLogs.Instance); SyncConfig syncConfig = new(); + ProgressTracker progressTracker = new(_blockTree, dbProvider.StateDb, LimboLogs.Instance); SyncProgressResolver resolver = new( _blockTree, _receiptStorage, _stateDb, new TrieStore(_stateDb, LimboLogs.Instance), + progressTracker, syncConfig, LimboLogs.Instance); + + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + TotalDifficultyBasedBetterPeerStrategy bestPeerStrategy = new(resolver, LimboLogs.Instance); MultiSyncModeSelector syncModeSelector = new(resolver, _pool, syncConfig, No.BeaconSync, bestPeerStrategy, LimboLogs.Instance); Pivot pivot = new (syncConfig); @@ -79,7 +85,7 @@ public async Task Setup() _receiptStorage, Always.Valid, Always.Valid, _pool, stats, syncModeSelector, syncConfig, pivot, new TotalDifficultyBasedBetterPeerStrategy(resolver, LimboLogs.Instance), LimboLogs.Instance); _synchronizer = new Synchronizer(dbProvider, MainnetSpecProvider.Instance, _blockTree, _receiptStorage, - _pool, stats, syncModeSelector, syncConfig, blockDownloaderFactory, pivot, LimboLogs.Instance); + _pool, stats, syncModeSelector, syncConfig, snapProvider, blockDownloaderFactory, pivot, LimboLogs.Instance); _syncServer = new SyncServer( _stateDb, _codeDb, diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/Extensions.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/Extensions.cs index e8ffc5d01c2..9ffc0648a74 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/Extensions.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/Extensions.cs @@ -3,7 +3,7 @@ using System.Linq; using Nethermind.Synchronization.ParallelSync; using NSubstitute; -using static Nethermind.Synchronization.Test.ParallelSync.MultiSyncModeSelectorTests; +using static Nethermind.Synchronization.Test.ParallelSync.MultiSyncModeSelectorTestsBase; namespace Nethermind.Synchronization.Test.ParallelSync { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorFastSyncTests.cs similarity index 95% rename from src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.cs rename to src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorFastSyncTests.cs index 20603509170..d868e9dc20a 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorFastSyncTests.cs @@ -17,11 +17,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Threading.Tasks; using FluentAssertions; using Nethermind.Blockchain.Synchronization; using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Test.Builders; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Synchronization.ParallelSync; @@ -34,33 +34,10 @@ namespace Nethermind.Synchronization.Test.ParallelSync [Parallelizable(ParallelScope.All)] [TestFixture(false)] [TestFixture(true)] - public partial class MultiSyncModeSelectorTests + public class MultiSyncModeSelectorFastSyncTests : MultiSyncModeSelectorTestsBase { - public enum FastBlocksState + public MultiSyncModeSelectorFastSyncTests(bool needToWaitForHeaders) : base(needToWaitForHeaders) { - None, - FinishedHeaders, - FinishedBodies, - FinishedReceipts - } - - private readonly bool _needToWaitForHeaders; - - public MultiSyncModeSelectorTests(bool needToWaitForHeaders) - { - _needToWaitForHeaders = needToWaitForHeaders; - } - - private SyncMode GetExpectationsIfNeedToWaitForHeaders(SyncMode expectedSyncModes) - { - if (_needToWaitForHeaders && (expectedSyncModes & SyncMode.FastHeaders) == SyncMode.FastHeaders) - { - expectedSyncModes &= ~SyncMode.StateNodes; - expectedSyncModes &= ~SyncMode.Full; - expectedSyncModes &= ~SyncMode.FastSync; - } - - return expectedSyncModes; } [Test] @@ -196,7 +173,7 @@ public void Finished_fast_sync_but_not_state_sync_and_lesser_peers_are_known() .WhenFastSyncWithoutFastBlocksIsConfigured() .TheSyncModeShouldBe(SyncMode.None); } - + [TestCase(FastBlocksState.None)] [TestCase(FastBlocksState.FinishedHeaders)] public void Finished_fast_sync_but_not_state_sync_and_lesser_peers_are_known_in_fast_blocks(FastBlocksState fastBlocksState) @@ -214,7 +191,6 @@ public void Finished_fast_sync_but_not_state_sync() Scenario.GoesLikeThis(_needToWaitForHeaders) .IfThisNodeJustFinishedFastBlocksAndFastSync() .AndGoodPeersAreKnown() - .WhenFastSyncWithoutFastBlocksIsConfigured() .WhenFastSyncWithFastBlocksIsConfigured() .TheSyncModeShouldBe(SyncMode.StateNodes); } @@ -250,7 +226,7 @@ public void Just_after_finishing_state_sync_and_fast_blocks(FastBlocksState fast .AndGoodPeersAreKnown() .TheSyncModeShouldBe(SyncMode.Full | fastBlocksState.GetSyncMode(true)); } - + [TestCase(FastBlocksState.None)] [TestCase(FastBlocksState.FinishedHeaders)] [TestCase(FastBlocksState.FinishedBodies)] @@ -292,7 +268,7 @@ public void When_just_started_full_sync() .ThenInAnyFastSyncConfiguration() .TheSyncModeShouldBe(SyncMode.Full); } - + [TestCase(FastBlocksState.None)] [TestCase(FastBlocksState.FinishedHeaders)] [TestCase(FastBlocksState.FinishedBodies)] @@ -336,7 +312,7 @@ public void When_recently_started_full_sync() .ThenInAnyFastSyncConfiguration() .TheSyncModeShouldBe(SyncMode.Full); } - + [Test] public void When_recently_started_full_sync_on_empty_clique_chain() { @@ -386,7 +362,7 @@ public void Can_switch_to_a_better_branch_while_full_synced() .ThenInAnyFastSyncConfiguration() .TheSyncModeShouldBe(SyncMode.Full); } - + [Test] public void Should_not_sync_when_synced_and_peer_reports_wrong_higher_total_difficulty() { @@ -497,7 +473,7 @@ public void Does_not_move_back_to_state_sync_mistakenly_when_in_full_sync_becaus .WhenFastSyncWithFastBlocksIsConfigured() .TheSyncModeShouldBe(GetExpectationsIfNeedToWaitForHeaders(SyncMode.Full | SyncMode.FastHeaders)); } - + [Test] public void Switch_correctly_from_full_sync_to_state_nodes_catch_up() { @@ -518,14 +494,14 @@ public void Switch_correctly_from_full_sync_to_state_nodes_catch_up() syncPeer.TotalDifficulty.Returns(header.TotalDifficulty ?? 0); syncPeer.IsInitialized.Returns(true); syncPeer.ClientId.Returns("nethermind"); - + syncPeers.Add(syncPeer); ISyncPeerPool syncPeerPool = Substitute.For(); IEnumerable peerInfos = syncPeers.Select(p => new PeerInfo(p)); syncPeerPool.InitializedPeers.Returns(peerInfos); syncPeerPool.AllPeers.Returns(peerInfos); - ISyncConfig syncConfig = new SyncConfig() {FastSyncCatchUpHeightDelta = 2}; + ISyncConfig syncConfig = new SyncConfig() { FastSyncCatchUpHeightDelta = 2 }; syncConfig.FastSync = true; TotalDifficultyBasedBetterPeerStrategy bestPeerStrategy = new(syncProgressResolver, LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorSnapSyncTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorSnapSyncTests.cs new file mode 100644 index 00000000000..581bde48b97 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorSnapSyncTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Synchronization.ParallelSync; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.ParallelSync +{ + [Parallelizable(ParallelScope.All)] + [TestFixture(false)] + [TestFixture(true)] + public class MultiSyncModeSelectorSnapSyncTests : MultiSyncModeSelectorTestsBase + { + public MultiSyncModeSelectorSnapSyncTests(bool needToWaitForHeaders) : base(needToWaitForHeaders) + { + } + + [Test] + public void Simple_snap_sync() + { + Scenario.GoesLikeThis(_needToWaitForHeaders) + .IfThisNodeHasNeverSyncedBefore() + .AndGoodPeersAreKnown() + .WhenSnapSyncWithoutFastBlocksIsConfigured() + .TheSyncModeShouldBe(SyncMode.FastSync); + } + + [Test] + public void Simple_snap_sync_with_fast_blocks() + { + // note that before we download at least one header we cannot start fast sync + Scenario.GoesLikeThis(_needToWaitForHeaders) + .IfThisNodeHasNeverSyncedBefore() + .AndGoodPeersAreKnown() + .WhenSnapSyncWithFastBlocksIsConfigured() + .TheSyncModeShouldBe(SyncMode.FastHeaders); + } + + [Test] + public void Finished_fast_sync_but_not_snap_sync() + { + Scenario.GoesLikeThis(_needToWaitForHeaders) + .IfThisNodeJustFinishedFastBlocksAndFastSync() + .AndGoodPeersAreKnown() + .WhenSnapSyncWithFastBlocksIsConfigured() + .TheSyncModeShouldBe(SyncMode.SnapSync); + } + + [Test] + public void Finished_fast_sync_but_not_snap_sync_and_fast_blocks_in_progress() + { + Scenario.GoesLikeThis(_needToWaitForHeaders) + .ThisNodeFinishedFastSyncButNotFastBlocks() + .AndGoodPeersAreKnown() + .WhenSnapSyncWithFastBlocksIsConfigured() + .TheSyncModeShouldBe(GetExpectationsIfNeedToWaitForHeaders(SyncMode.SnapSync | SyncMode.FastHeaders)); + } + + [Test] + public void Finished_snap_node_but_not_fast_blocks() + { + Scenario.GoesLikeThis(_needToWaitForHeaders) + .ThisNodeFinishedFastSyncButNotFastBlocks() + .WhenSnapSyncWithFastBlocksIsConfigured() + .AndGoodPeersAreKnown() + .TheSyncModeShouldBe(GetExpectationsIfNeedToWaitForHeaders(SyncMode.SnapSync | SyncMode.FastHeaders)); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs index ba472042ade..0a836e1ada4 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs @@ -15,7 +15,7 @@ namespace Nethermind.Synchronization.Test.ParallelSync { - public partial class MultiSyncModeSelectorTests + public partial class MultiSyncModeSelectorTestsBase { public static class Scenario { @@ -671,6 +671,32 @@ public ScenarioBuilder WhenFastSyncWithoutFastBlocksIsConfigured() return this; } + public ScenarioBuilder WhenSnapSyncWithFastBlocksIsConfigured() + { + _configActions.Add(() => + { + SyncConfig.FastSync = true; + SyncConfig.SnapSync = true; + SyncConfig.FastBlocks = true; + return "snap sync with fast blocks"; + }); + + return this; + } + + public ScenarioBuilder WhenSnapSyncWithoutFastBlocksIsConfigured() + { + _configActions.Add(() => + { + SyncConfig.FastSync = true; + SyncConfig.SnapSync = true; + SyncConfig.FastBlocks = false; + return "snap sync without fast blocks"; + }); + + return this; + } + public ScenarioBuilder WhenFullArchiveSyncIsConfigured() { _configActions.Add(() => diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTestsBase.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTestsBase.cs new file mode 100644 index 00000000000..23aa98cd398 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTestsBase.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.ParallelSync +{ + public partial class MultiSyncModeSelectorTestsBase + { + public enum FastBlocksState + { + None, + FinishedHeaders, + FinishedBodies, + FinishedReceipts + } + + protected readonly bool _needToWaitForHeaders; + + public MultiSyncModeSelectorTestsBase(bool needToWaitForHeaders) + { + _needToWaitForHeaders = needToWaitForHeaders; + } + + protected SyncMode GetExpectationsIfNeedToWaitForHeaders(SyncMode expectedSyncModes) + { + if (_needToWaitForHeaders && (expectedSyncModes & SyncMode.FastHeaders) == SyncMode.FastHeaders) + { + expectedSyncModes &= ~SyncMode.StateNodes; + expectedSyncModes &= ~SyncMode.SnapSync; + expectedSyncModes &= ~SyncMode.Full; + expectedSyncModes &= ~SyncMode.FastSync; + } + + return expectedSyncModes; + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/SyncDispatcherTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/SyncDispatcherTests.cs index 24b2e7838a6..4b2f65c32a3 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/SyncDispatcherTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/SyncDispatcherTests.cs @@ -93,6 +93,10 @@ public void RemovePeer(ISyncPeer syncPeer) { } + public void SetPeerPriority(PublicKey id) + { + } + public void RefreshTotalDifficulty(ISyncPeer syncPeer, Keccak hash) { } @@ -172,7 +176,7 @@ public TestSyncFeed(bool isMultiFeed = true) private ConcurrentQueue _returned = new(); - public override SyncResponseHandlingResult HandleResponse(TestBatch response) + public override SyncResponseHandlingResult HandleResponse(TestBatch response, PeerInfo peer = null) { if (response.Result == null) { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs new file mode 100644 index 00000000000..bc84a00fd16 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs @@ -0,0 +1,282 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.State.Snap; +using Nethermind.Synchronization.SnapSync; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.SnapSync +{ + [TestFixture] + public class RecreateStateFromAccountRangesTests + { + private StateTree _inputTree; + + [OneTimeSetUp] + public void Setup() + { + _inputTree = TestItem.Tree.GetStateTree(null); + } + + //[Test] + public void Test01() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountsWithPaths[0].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + MemDb db = new (); + TrieStore store = new (db, LimboLogs.Instance); + StateTree tree = new (store, LimboLogs.Instance); + + IList nodes = new List(); + + for (int i = 0; i < (firstProof!).Length; i++) + { + byte[] nodeBytes = (firstProof!)[i]; + var node = new TrieNode(NodeType.Unknown, nodeBytes); + node.ResolveKey(store, i == 0); + + nodes.Add(node); + if (i < (firstProof!).Length - 1) + { + //IBatch batch = store.GetOrStartNewBatch(); + //batch[node.Keccak!.Bytes] = nodeBytes; + //db.Set(node.Keccak!, nodeBytes); + } + } + + for (int i = 0; i < (lastProof!).Length; i++) + { + byte[] nodeBytes = (lastProof!)[i]; + var node = new TrieNode(NodeType.Unknown, nodeBytes); + node.ResolveKey(store, i == 0); + + nodes.Add(node); + if (i < (lastProof!).Length - 1) + { + //IBatch batch = store.GetOrStartNewBatch(); + //batch[node.Keccak!.Bytes] = nodeBytes; + //db.Set(node.Keccak!, nodeBytes); + } + } + + tree.RootRef = nodes[0]; + + tree.Set(TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths[0].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[1].Path, TestItem.Tree.AccountsWithPaths[1].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[2].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[3].Path, TestItem.Tree.AccountsWithPaths[3].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4].Account); + tree.Set(TestItem.Tree.AccountsWithPaths[5].Path, TestItem.Tree.AccountsWithPaths[5].Account); + + tree.Commit(0); + + Assert.AreEqual(_inputTree.RootHash, tree.RootHash); + Assert.AreEqual(6, db.Keys.Count); // we don't persist proof nodes (boundary nodes) + Assert.IsFalse(db.KeyExists(rootHash)); // the root node is a part of the proof nodes + } + + [Test] + public void RecreateAccountStateFromOneRangeWithNonExistenceProof() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + AccountProofCollector accountProofCollector = new(Keccak.Zero.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths, firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + Assert.AreEqual(11, db.Keys.Count); // we persist proof nodes (boundary nodes) via stitching + Assert.IsTrue(db.KeyExists(rootHash)); + } + + [Test] + public void RecreateAccountStateFromOneRangeWithExistenceProof() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountsWithPaths[0].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + MemDb db = new(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths, firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + Assert.AreEqual(11, db.Keys.Count); // we persist proof nodes (boundary nodes) via stitching + Assert.IsTrue(db.KeyExists(rootHash)); + } + + [Test] + public void RecreateAccountStateFromOneRangeWithoutProof() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + MemDb db = new(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + var result = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[0].Path, TestItem.Tree.AccountsWithPaths); + + Assert.AreEqual(AddRangeResult.OK, result); + Assert.AreEqual(11, db.Keys.Count); // we don't have the proofs so we persist all nodes + Assert.IsTrue(db.KeyExists(rootHash)); // the root node is NOT a part of the proof nodes + } + + [Test] + public void RecreateAccountStateFromMultipleRange() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + // output state + MemDb db = new(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(Keccak.Zero.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..2], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(2, db.Keys.Count); + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + var result2 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[2..4], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(5, db.Keys.Count); // we don't persist proof nodes (boundary nodes) + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + var result3 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4..6], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.OK, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + Assert.AreEqual(11, db.Keys.Count); // we persist proof nodes (boundary nodes) via stitching + Assert.IsTrue(db.KeyExists(rootHash)); + } + + [Test] + public void MissingAccountFromRange() + { + Keccak rootHash = _inputTree.RootHash; // "0x8c81279168edc449089449bc0f2136fc72c9645642845755633cf259cd97988b" + + // output state + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(Keccak.Zero.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[1].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + byte[][] lastProof = accountProofCollector.BuildResult().Proof; + + var result1 = snapProvider.AddAccountRange(1, rootHash, Keccak.Zero, TestItem.Tree.AccountsWithPaths[0..2], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(2, db.Keys.Count); + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[2].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[3].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + // missing TestItem.Tree.AccountsWithHashes[2] + var result2 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[2].Path, TestItem.Tree.AccountsWithPaths[3..4], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(2, db.Keys.Count); + + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[4].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + firstProof = accountProofCollector.BuildResult().Proof; + accountProofCollector = new(TestItem.Tree.AccountsWithPaths[5].Path.Bytes); + _inputTree.Accept(accountProofCollector, _inputTree.RootHash); + lastProof = accountProofCollector.BuildResult().Proof; + + var result3 = snapProvider.AddAccountRange(1, rootHash, TestItem.Tree.AccountsWithPaths[4].Path, TestItem.Tree.AccountsWithPaths[4..6], firstProof!.Concat(lastProof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.DifferentRootHash, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + Assert.AreEqual(6, db.Keys.Count); + Assert.IsFalse(db.KeyExists(rootHash)); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs new file mode 100644 index 00000000000..b990a6570d5 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs @@ -0,0 +1,182 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.State.Snap; +using Nethermind.Synchronization.SnapSync; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.SnapSync +{ + [TestFixture] + public class RecreateStateFromStorageRangesTests + { + + private TrieStore _store; + private StateTree _inputStateTree; + private StorageTree _inputStorageTree; + + [OneTimeSetUp] + public void Setup() + { + _store = new TrieStore(new MemDb(), LimboLogs.Instance); + (_inputStateTree, _inputStorageTree) = TestItem.Tree.GetTrees(_store); + } + + [Test] + public void RecreateStorageStateFromOneRangeWithNonExistenceProof() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + var result = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths, proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + } + + [Test] + public void RecreateAccountStateFromOneRangeWithExistenceProof() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[0].Path, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + MemDb db = new (); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + var result = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths, proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result); + } + + [Test] + public void RecreateStorageStateFromOneRangeWithoutProof() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + MemDb db = new MemDb(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + var result = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[0].Path, TestItem.Tree.SlotsWithPaths); + + Assert.AreEqual(AddRangeResult.OK, result); + } + + [Test] + public void RecreateAccountStateFromMultipleRange() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + // output state + MemDb db = new MemDb(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[1].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + var result1 = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths[0..2], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[3].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result2 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[2..4], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result3 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[4..6], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.OK, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + } + + [Test] + public void MissingAccountFromRange() + { + Keccak rootHash = _inputStorageTree!.RootHash; // "..." + + // output state + MemDb db = new MemDb(); + DbProvider dbProvider = new(DbModeHint.Mem); + dbProvider.RegisterDb(DbNames.State, db); + ProgressTracker progressTracker = new(null, dbProvider.GetDb(DbNames.State), LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + + AccountProofCollector accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { Keccak.Zero, TestItem.Tree.SlotsWithPaths[1].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + var proof = accountProofCollector.BuildResult(); + + var result1 = snapProvider.AddStorageRange(1, null, rootHash, Keccak.Zero, TestItem.Tree.SlotsWithPaths[0..2], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[3].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result2 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[2].Path, TestItem.Tree.SlotsWithPaths[3..4], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + accountProofCollector = new(TestItem.Tree.AccountAddress0.Bytes, new Keccak[] { TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[5].Path }); + _inputStateTree!.Accept(accountProofCollector, _inputStateTree.RootHash); + proof = accountProofCollector.BuildResult(); + + var result3 = snapProvider.AddStorageRange(1, null, rootHash, TestItem.Tree.SlotsWithPaths[4].Path, TestItem.Tree.SlotsWithPaths[4..6], proof!.StorageProofs![0].Proof!.Concat(proof!.StorageProofs![1].Proof!).ToArray()); + + Assert.AreEqual(AddRangeResult.OK, result1); + Assert.AreEqual(AddRangeResult.DifferentRootHash, result2); + Assert.AreEqual(AddRangeResult.OK, result3); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapSyncFeed/AnalyzeResponsePerPeerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapSyncFeed/AnalyzeResponsePerPeerTests.cs new file mode 100644 index 00000000000..6a72cc2ae85 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapSyncFeed/AnalyzeResponsePerPeerTests.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Logging; +using Nethermind.Network.P2P.Subprotocols.Eth.V62; +using Nethermind.Network.P2P.Subprotocols.Eth.V66; +using Nethermind.Network.P2P.Subprotocols.Snap; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.SnapSync; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Synchronization.Test.SnapSync +{ + [TestFixture] + internal class AnalyzeResponsePerPeerTests + { + [Test] + public void Test01() + { + PeerInfo peer1 = new(null); + PeerInfo peer2 = new(null); + + ISyncModeSelector selector = Substitute.For(); + ISnapProvider snapProvider = Substitute.For(); + + SnapSyncFeed feed = new(selector, snapProvider, null, LimboLogs.Instance); + + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + + var result = feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + + Assert.AreEqual(SyncResponseHandlingResult.LesserQuality, result); + + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + result = feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + Assert.AreEqual(SyncResponseHandlingResult.LesserQuality, result); + } + + [Test] + public void Test02() + { + PeerInfo peer1 = new(null); + PeerInfo peer2 = new(null); + + ISyncModeSelector selector = Substitute.For(); + ISnapProvider snapProvider = Substitute.For(); + + SnapSyncFeed feed = new(selector, snapProvider, null, LimboLogs.Instance); + + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + + var result = feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + + Assert.AreEqual(SyncResponseHandlingResult.LesserQuality, result); + + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + result = feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + Assert.AreEqual(SyncResponseHandlingResult.OK, result); + } + + [Test] + public void Test03() + { + PeerInfo peer1 = new(null); + PeerInfo peer2 = new(null); + + ISyncModeSelector selector = Substitute.For(); + ISnapProvider snapProvider = Substitute.For(); + + SnapSyncFeed feed = new(selector, snapProvider, null, LimboLogs.Instance); + + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer2); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + feed.AnalyzeResponsePerPeer(AddRangeResult.ExpiredRootHash, peer1); + var result = feed.AnalyzeResponsePerPeer(AddRangeResult.DifferentRootHash, peer1); + Assert.AreEqual(SyncResponseHandlingResult.OK, result); + + snapProvider.Received(1).UpdatePivot(); + } + + [Test] + public void Test04() + { + PeerInfo peer1 = new(null); + + ISyncModeSelector selector = Substitute.For(); + ISnapProvider snapProvider = Substitute.For(); + + SnapSyncFeed feed = new(selector, snapProvider, null, LimboLogs.Instance); + + for (int i = 0; i < 200; i++) + { + feed.AnalyzeResponsePerPeer(AddRangeResult.OK, peer1); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerMock.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerMock.cs index 949a3ef86c3..f514248755e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerMock.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerMock.cs @@ -80,6 +80,7 @@ private void RunQueue() public long HeadNumber { get; set; } public UInt256 TotalDifficulty { get; set; } public bool IsInitialized { get; set; } + public bool IsPriority { get; set; } public void Disconnect(DisconnectReason reason, string details) { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs index 639cd5c6ad2..cb51697d166 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncPeerPoolTests.cs @@ -19,6 +19,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Core; @@ -43,13 +44,15 @@ private class Context : IAsyncDisposable { public INodeStatsManager Stats; public IBlockTree BlockTree; + public IBetterPeerStrategy PeerStrategy; public SyncPeerPool Pool; public Context() { BlockTree = Substitute.For(); Stats = Substitute.For(); - Pool = new SyncPeerPool(BlockTree, Stats, new TotalDifficultyBasedBetterPeerStrategy(null, LimboLogs.Instance), 25, 50, LimboLogs.Instance); + PeerStrategy = new TotalDifficultyBasedBetterPeerStrategy(null, LimboLogs.Instance); + Pool = new SyncPeerPool(BlockTree, Stats, PeerStrategy, 25, 50, LimboLogs.Instance); } public async ValueTask DisposeAsync() @@ -72,6 +75,7 @@ public SimpleSyncPeerMock(PublicKey publicKey, string description = "simple mock public long HeadNumber { get; set; } public UInt256 TotalDifficulty { get; set; } = 1; public bool IsInitialized { get; set; } + public bool IsPriority { get; set; } public bool DisconnectRequested { get; set; } @@ -181,6 +185,84 @@ public async Task Will_disconnect_one_when_at_max() ctx.Pool.DropUselessPeers(true); Assert.True(peers.Any(p => p.DisconnectRequested)); } + + [TestCase(0)] + [TestCase(10)] + [TestCase(24)] + public async Task Will_not_disconnect_any_priority_peer_if_their_amount_is_lower_than_max(byte number) + { + const int peersMaxCount = 25; + const int priorityPeersMaxCount = 25; + await using Context ctx = new(); + ctx.Pool = new SyncPeerPool(ctx.BlockTree, ctx.Stats, ctx.PeerStrategy, peersMaxCount, priorityPeersMaxCount,50, LimboLogs.Instance); + var peers = await SetupPeers(ctx, peersMaxCount); + + // setting priority to all peers except one - peers[number] + for (int i = 0; i < priorityPeersMaxCount; i++) + { + if (i != number) + { + ctx.Pool.SetPeerPriority(peers[i].Id); + } + } + await WaitForPeersInitialization(ctx); + ctx.Pool.DropUselessPeers(true); + Assert.True(peers[number].DisconnectRequested); + } + + [Test] + public async Task Can_disconnect_priority_peer_if_their_amount_is_max() + { + const int peersMaxCount = 25; + const int priorityPeersMaxCount = 25; + await using Context ctx = new(); + ctx.Pool = new SyncPeerPool(ctx.BlockTree, ctx.Stats, ctx.PeerStrategy, peersMaxCount, priorityPeersMaxCount,50, LimboLogs.Instance); + var peers = await SetupPeers(ctx, peersMaxCount); + + foreach (SimpleSyncPeerMock peer in peers) + { + ctx.Pool.SetPeerPriority(peer.Id); + } + await WaitForPeersInitialization(ctx); + ctx.Pool.DropUselessPeers(true); + Assert.True(peers.Any(p => p.DisconnectRequested)); + } + + [Test] + public async Task Should_increment_PriorityPeerCount_when_added_priority_peer_and_decrement_after_removal() + { + const int peersMaxCount = 1; + const int priorityPeersMaxCount = 1; + await using Context ctx = new(); + ctx.Pool = new SyncPeerPool(ctx.BlockTree, ctx.Stats, ctx.PeerStrategy, peersMaxCount, priorityPeersMaxCount,50, LimboLogs.Instance); + + SimpleSyncPeerMock peer = new(TestItem.PublicKeyA) { IsPriority = true }; + ctx.Pool.Start(); + ctx.Pool.AddPeer(peer); + await WaitForPeersInitialization(ctx); + ctx.Pool.PriorityPeerCount.Should().Be(1); + + ctx.Pool.RemovePeer(peer); + ctx.Pool.PriorityPeerCount.Should().Be(0); + } + + [Test] + public async Task Should_increment_PriorityPeerCount_when_called_SetPriorityPeer() + { + const int peersMaxCount = 1; + const int priorityPeersMaxCount = 1; + await using Context ctx = new(); + ctx.Pool = new SyncPeerPool(ctx.BlockTree, ctx.Stats, ctx.PeerStrategy, peersMaxCount, priorityPeersMaxCount,50, LimboLogs.Instance); + + SimpleSyncPeerMock peer = new(TestItem.PublicKeyA) { IsPriority = false }; + ctx.Pool.Start(); + ctx.Pool.AddPeer(peer); + await WaitForPeersInitialization(ctx); + ctx.Pool.PriorityPeerCount.Should().Be(0); + + ctx.Pool.SetPeerPriority(peer.Id); + ctx.Pool.PriorityPeerCount.Should().Be(1); + } [Test] public async Task Cannot_remove_when_stopped() diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs index 77ccecb9f6a..b580b4e18dc 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs @@ -23,6 +23,7 @@ using Nethermind.Db; using Nethermind.Logging; using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.SnapSync; using Nethermind.Trie.Pruning; using NSubstitute; using NUnit.Framework; @@ -41,8 +42,9 @@ public void Header_block_is_0_when_no_header_was_suggested() IDb stateDb = new MemDb(); SyncConfig syncConfig = new(); syncConfig.PivotNumber = "1"; - - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); blockTree.BestSuggestedHeader.Returns((BlockHeader) null); Assert.AreEqual(0, syncProgressResolver.FindBestHeader()); } @@ -55,8 +57,9 @@ public void Best_block_is_0_when_no_block_was_suggested() IDb stateDb = new MemDb(); SyncConfig syncConfig = new(); syncConfig.PivotNumber = "1"; - - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); blockTree.BestSuggestedBody.Returns((Block) null); Assert.AreEqual(0, syncProgressResolver.FindBestFullBlock()); } @@ -69,8 +72,9 @@ public void Best_state_is_head_when_there_are_no_suggested_blocks() IDb stateDb = Substitute.For(); SyncConfig syncConfig = new(); syncConfig.PivotNumber = "1"; - - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); var head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; blockTree.Head.Returns(head); blockTree.BestSuggestedHeader.Returns(head.Header); @@ -86,8 +90,9 @@ public void Best_state_is_suggested_if_there_is_suggested_block_with_state() IDb stateDb = Substitute.For(); SyncConfig syncConfig = new(); syncConfig.PivotNumber = "1"; - - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); var head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; var suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -106,8 +111,9 @@ public void Best_state_is_head_if_there_is_suggested_block_without_state() IDb stateDb = Substitute.For(); SyncConfig syncConfig = new(); syncConfig.PivotNumber = "1"; - - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); var head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; var suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -127,8 +133,9 @@ public void Is_fast_block_finished_returns_true_when_no_fast_block_sync_is_used( SyncConfig syncConfig = new(); syncConfig.FastBlocks = false; syncConfig.PivotNumber = "1"; - - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); Assert.True(syncProgressResolver.IsFastBlocksHeadersFinished()); Assert.True(syncProgressResolver.IsFastBlocksBodiesFinished()); Assert.True(syncProgressResolver.IsFastBlocksReceiptsFinished()); @@ -145,10 +152,11 @@ public void Is_fast_block_headers_finished_returns_false_when_headers_not_downlo syncConfig.DownloadBodiesInFastSync = true; syncConfig.DownloadReceiptsInFastSync = true; syncConfig.PivotNumber = "1"; - + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(2).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); Assert.False(syncProgressResolver.IsFastBlocksHeadersFinished()); } @@ -163,11 +171,12 @@ public void Is_fast_block_bodies_finished_returns_false_when_blocks_not_download syncConfig.DownloadBodiesInFastSync = true; syncConfig.DownloadReceiptsInFastSync = true; syncConfig.PivotNumber = "1"; - + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); blockTree.LowestInsertedBodyNumber.Returns(2); - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); Assert.False(syncProgressResolver.IsFastBlocksBodiesFinished()); } @@ -182,13 +191,14 @@ public void Is_fast_block_receipts_finished_returns_false_when_receipts_not_down syncConfig.DownloadBodiesInFastSync = true; syncConfig.DownloadReceiptsInFastSync = true; syncConfig.PivotNumber = "1"; - + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); blockTree.LowestInsertedBodyNumber.Returns(1); receiptStorage.LowestInsertedReceiptBlockNumber.Returns(2); SyncProgressResolver syncProgressResolver = new( - blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); Assert.False(syncProgressResolver.IsFastBlocksReceiptsFinished()); } @@ -203,12 +213,13 @@ public void Is_fast_block_bodies_finished_returns_true_when_bodies_not_downloade syncConfig.DownloadBodiesInFastSync = false; syncConfig.DownloadReceiptsInFastSync = true; syncConfig.PivotNumber = "1"; - + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); blockTree.LowestInsertedBodyNumber.Returns(2); receiptStorage.LowestInsertedReceiptBlockNumber.Returns(1); - SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = new(blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); Assert.True(syncProgressResolver.IsFastBlocksBodiesFinished()); } @@ -223,13 +234,14 @@ public void Is_fast_block_receipts_finished_returns_true_when_receipts_not_downl syncConfig.DownloadBodiesInFastSync = true; syncConfig.DownloadReceiptsInFastSync = false; syncConfig.PivotNumber = "1"; - + ProgressTracker progressTracker = new(blockTree, stateDb, LimboLogs.Instance); + blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); blockTree.LowestInsertedBodyNumber.Returns(1); receiptStorage.LowestInsertedReceiptBlockNumber.Returns(2); SyncProgressResolver syncProgressResolver = new( - blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, LimboLogs.Instance); + blockTree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, LimboLogs.Instance); Assert.True(syncProgressResolver.IsFastBlocksReceiptsFinished()); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs index de9b346bd78..4cc022106a7 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs @@ -52,6 +52,7 @@ using NSubstitute; using NUnit.Framework; using BlockTree = Nethermind.Blockchain.BlockTree; +using Nethermind.Synchronization.SnapSync; namespace Nethermind.Synchronization.Test { @@ -353,8 +354,11 @@ private SyncTestContext CreateSyncManager(int index) new MiningConfig(), logManager); + ProgressTracker progressTracker = new(tree, dbProvider.StateDb, LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + SyncProgressResolver resolver = new( - tree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, syncConfig, logManager); + tree, receiptStorage, stateDb, NullTrieNodeResolver.Instance, progressTracker, syncConfig, logManager); TotalDifficultyBasedBetterPeerStrategy bestPeerStrategy = new(resolver, LimboLogs.Instance); MultiSyncModeSelector selector = new(resolver, syncPeerPool, syncConfig, No.BeaconSync, bestPeerStrategy, logManager); Pivot pivot = new(syncConfig); @@ -379,6 +383,7 @@ private SyncTestContext CreateSyncManager(int index) nodeStatsManager, StaticSelector.Full, syncConfig, + snapProvider, blockDownloaderFactory, pivot, logManager); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerTests.cs index bff3f096dac..58977dff3bf 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerTests.cs @@ -50,6 +50,7 @@ using Nethermind.Trie.Pruning; using NSubstitute; using NUnit.Framework; +using Nethermind.Synchronization.SnapSync; namespace Nethermind.Synchronization.Test { @@ -109,6 +110,7 @@ private void UpdateHead() public UInt256 TotalDifficulty { get; set; } public bool IsInitialized { get; set; } + public bool IsPriority { get; set; } public void Disconnect(DisconnectReason reason, string details) { @@ -320,14 +322,18 @@ ISyncConfig GetSyncConfig() => PoSSwitcher poSSwitcher = new(mergeConfig, dbProvider.MetadataDb, BlockTree, new SingleReleaseSpecProvider(Constantinople.Instance, 1), _logManager); + ProgressTracker progressTracker = new(BlockTree, dbProvider.StateDb, LimboLogs.Instance); + SnapProvider snapProvider = new(progressTracker, dbProvider, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = new( BlockTree, NullReceiptStorage.Instance, stateDb, new TrieStore(stateDb, LimboLogs.Instance), + progressTracker, syncConfig, _logManager); - + if (IsMerge(synchronizerType)) SyncPeerPool = new SyncPeerPool(BlockTree, stats, new MergeBetterPeerStrategy( @@ -379,6 +385,7 @@ ISyncConfig GetSyncConfig() => stats, syncModeSelector, syncConfig, + snapProvider, blockDownloaderFactory, pivot, new BeaconSync(beaconPivot, BlockTree,syncConfig, blockCacheService, LimboLogs.Instance), @@ -414,6 +421,7 @@ ISyncConfig GetSyncConfig() => stats, syncModeSelector, syncConfig, + snapProvider, blockDownloaderFactory, pivot, _logManager); diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs index 25705e95dfb..a0ac9d24515 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs @@ -119,7 +119,7 @@ public bool TrySetReceipts(int index, TxReceipt[]? receipts, out Block block) block = Blocks[_indexMapping[index]]; receipts ??= Array.Empty(); - bool result = _receiptsRecovery.TryRecover(block, receipts); + bool result = _receiptsRecovery.TryRecover(block, receipts, false) != ReceiptsRecoveryResult.Fail; if (result) { ValidateReceipts(block, receipts); diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs index e4817a4cef3..7f0a0e199b1 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/FastSyncFeed.cs @@ -55,7 +55,7 @@ private DownloaderOptions BuildOptions() public override Task PrepareRequest() => Task.FromResult(_blocksRequest); - public override SyncResponseHandlingResult HandleResponse(BlocksRequest response) + public override SyncResponseHandlingResult HandleResponse(BlocksRequest response, PeerInfo peer = null) { FallAsleep(); return SyncResponseHandlingResult.OK; diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/FullSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/FullSyncFeed.cs index c241d7dfb24..77b7369ce58 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/FullSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/FullSyncFeed.cs @@ -38,7 +38,7 @@ public FullSyncFeed(ISyncModeSelector syncModeSelector, ILogManager logManager) // ReSharper disable once RedundantTypeArgumentsOfMethod public override Task PrepareRequest() => Task.FromResult(_blocksRequest); - public override SyncResponseHandlingResult HandleResponse(BlocksRequest? response) + public override SyncResponseHandlingResult HandleResponse(BlocksRequest? response, PeerInfo peer = null) { FallAsleep(); return SyncResponseHandlingResult.OK; diff --git a/src/Nethermind/Nethermind.Synchronization/FastBlocks/BodiesSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/FastBlocks/BodiesSyncFeed.cs index 4c1d46b77de..002508a8a39 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastBlocks/BodiesSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastBlocks/BodiesSyncFeed.cs @@ -127,7 +127,7 @@ private void PostFinishCleanUp() return Task.FromResult(batch); } - public override SyncResponseHandlingResult HandleResponse(BodiesSyncBatch? batch) + public override SyncResponseHandlingResult HandleResponse(BodiesSyncBatch? batch, PeerInfo peer = null) { batch?.MarkHandlingStart(); try diff --git a/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs index 61282dfd730..259720807aa 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs @@ -265,7 +265,7 @@ private void LogStateOnPrepare() } } - public override SyncResponseHandlingResult HandleResponse(HeadersSyncBatch? batch) + public override SyncResponseHandlingResult HandleResponse(HeadersSyncBatch? batch, PeerInfo peer = null) { if (batch == null) { diff --git a/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs index b235279e46f..df432db82ba 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs @@ -146,7 +146,7 @@ private void PostFinishCleanUp() return Task.FromResult(batch); } - public override SyncResponseHandlingResult HandleResponse(ReceiptsSyncBatch? batch) + public override SyncResponseHandlingResult HandleResponse(ReceiptsSyncBatch? batch, PeerInfo peer = null) { batch?.MarkHandlingStart(); try diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DependentItem.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/DependentItem.cs similarity index 60% rename from src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DependentItem.cs rename to src/Nethermind/Nethermind.Synchronization/FastSync/DependentItem.cs index 05571405167..0b6326fb89d 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DependentItem.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/DependentItem.cs @@ -19,24 +19,21 @@ namespace Nethermind.Synchronization.FastSync { - public partial class StateSyncFeed + [DebuggerDisplay("{SyncItem.Hash} {Counter}")] + internal class DependentItem { - [DebuggerDisplay("{SyncItem.Hash} {Counter}")] - private class DependentItem - { - public StateSyncItem SyncItem { get; } - public byte[] Value { get; } - public int Counter { get; set; } + public StateSyncItem SyncItem { get; } + public byte[] Value { get; } + public int Counter { get; set; } - public bool IsAccount { get; } + public bool IsAccount { get; } - public DependentItem(StateSyncItem syncItem, byte[] value, int counter, bool isAccount = false) - { - SyncItem = syncItem; - Value = value; - Counter = counter; - IsAccount = isAccount; - } + public DependentItem(StateSyncItem syncItem, byte[] value, int counter, bool isAccount = false) + { + SyncItem = syncItem; + Value = value; + Counter = counter; + IsAccount = isAccount; } } } diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DependentItemComparer.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/DependentItemComparer.cs similarity index 54% rename from src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DependentItemComparer.cs rename to src/Nethermind/Nethermind.Synchronization/FastSync/DependentItemComparer.cs index 579ca445c97..d48f25e880e 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DependentItemComparer.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/DependentItemComparer.cs @@ -20,38 +20,35 @@ namespace Nethermind.Synchronization.FastSync { - public partial class StateSyncFeed + internal class DependentItemComparer : IEqualityComparer { - private class DependentItemComparer : IEqualityComparer + private DependentItemComparer() { - private DependentItemComparer() - { - } + } - private static DependentItemComparer? _instance; + private static DependentItemComparer? _instance; - public static DependentItemComparer Instance + public static DependentItemComparer Instance + { + get { - get + if (_instance == null) { - if (_instance == null) - { - LazyInitializer.EnsureInitialized(ref _instance, () => new DependentItemComparer()); - } - - return _instance; + LazyInitializer.EnsureInitialized(ref _instance, () => new DependentItemComparer()); } - } - public bool Equals(DependentItem? x, DependentItem? y) - { - return x?.SyncItem.Hash == y?.SyncItem.Hash; + return _instance; } + } - public int GetHashCode(DependentItem obj) - { - return obj?.SyncItem.Hash.GetHashCode() ?? 0; - } + public bool Equals(DependentItem? x, DependentItem? y) + { + return x?.SyncItem.Hash == y?.SyncItem.Hash; + } + + public int GetHashCode(DependentItem obj) + { + return obj?.SyncItem.Hash.GetHashCode() ?? 0; } } } diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/DetailedProgress.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/DetailedProgress.cs new file mode 100644 index 00000000000..35982f7dbd8 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/DetailedProgress.cs @@ -0,0 +1,164 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using Nethermind.Blockchain; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Synchronization.FastSync +{ + public class DetailedProgress + { + private long _lastDataSize; + // private long _lastHandledNodesCount; + internal long LastDbReads; + internal decimal AverageTimeInHandler; + // ReSharper disable once NotAccessedField.Local + internal long LastRequestedNodesCount; + // ReSharper disable once NotAccessedField.Local + internal long LastSavedNodesCount; + internal long ConsumedNodesCount; + internal long SavedStorageCount; + internal long SavedStateCount; + internal long SavedNodesCount; + internal long SavedAccounts; + internal long SavedCode; + internal long RequestedNodesCount; + internal long HandledNodesCount; + internal long SecondsInSync; + internal long DbChecks; + internal long CheckWasCached; + internal long CheckWasInDependencies; + internal long StateWasThere; + internal long StateWasNotThere; + internal long EmptishCount; + internal long InvalidFormatCount; + internal long OkCount; + internal long BadQualityCount; + internal long NotAssignedCount; + internal long DataSize; + + private long TotalRequestsCount => EmptishCount + InvalidFormatCount + BadQualityCount + OkCount + NotAssignedCount; + public long ProcessedRequestsCount => EmptishCount + BadQualityCount + OkCount; + + internal (DateTime small, DateTime full) LastReportTime = (DateTime.MinValue, DateTime.MinValue); + + private Known.SizeInfo? _chainSizeInfo; + + public DetailedProgress(ulong chainId, byte[] serializedInitialState) + { + if (Known.ChainSize.ContainsKey(chainId)) + { + _chainSizeInfo = Known.ChainSize[chainId]; + } + + LoadFromSerialized(serializedInitialState); + } + + internal void DisplayProgressReport(int pendingRequestsCount, BranchProgress branchProgress, ILogger logger) + { + TimeSpan sinceLastReport = DateTime.UtcNow - LastReportTime.small; + if (sinceLastReport > TimeSpan.FromSeconds(1)) + { + // decimal savedNodesPerSecond = 1000m * (SavedNodesCount - LastSavedNodesCount) / (decimal) sinceLastReport.TotalMilliseconds; + decimal savedKBytesPerSecond = 1000m * ((DataSize - _lastDataSize) / 1000m) / (decimal)sinceLastReport.TotalMilliseconds; + // decimal requestedNodesPerSecond = 1000m * (RequestedNodesCount - LastRequestedNodesCount) / (decimal) sinceLastReport.TotalMilliseconds; + // decimal handledNodesPerSecond = 1000m * (HandledNodesCount - _lastHandledNodesCount) / (decimal) sinceLastReport.TotalMilliseconds; + _lastDataSize = DataSize; + // LastSavedNodesCount = SavedNodesCount; + // LastRequestedNodesCount = RequestedNodesCount; + // _lastHandledNodesCount = HandledNodesCount; + // if (_logger.IsInfo) _logger.Info($"Time {TimeSpan.FromSeconds(_secondsInSync):dd\\.hh\\:mm\\:ss} | {(decimal) _dataSize / 1000 / 1000,6:F2}MB | kBps: {savedKBytesPerSecond,5:F0} | P: {_pendingRequests.Count} | acc {_savedAccounts} | queues {StreamsDescription} | db {_averageTimeInHandler:f2}ms"); + + Metrics.StateSynced = DataSize; + string dataSizeInfo = $"{(decimal)DataSize / 1000 / 1000,6:F2}MB"; + if (_chainSizeInfo != null) + { + decimal percentage = Math.Min(1, (decimal)DataSize / _chainSizeInfo.Value.Current); + dataSizeInfo = string.Concat( + $"~{percentage:P2} | ", dataSizeInfo, + $" / ~{(decimal)_chainSizeInfo.Value.Current / 1000 / 1000,6:F2}MB"); + } + + if (logger.IsInfo) logger.Info( + $"State Sync {TimeSpan.FromSeconds(SecondsInSync):dd\\.hh\\:mm\\:ss} | {dataSizeInfo} | branches: {branchProgress.Progress:P2} | kB/s: {savedKBytesPerSecond,5:F0} | accounts {SavedAccounts} | nodes {SavedNodesCount} | diagnostics: {pendingRequestsCount}.{AverageTimeInHandler:f2}ms"); + if (logger.IsDebug && DateTime.UtcNow - LastReportTime.full > TimeSpan.FromSeconds(10)) + { + long allChecks = CheckWasInDependencies + CheckWasCached + StateWasThere + StateWasNotThere; + if (logger.IsDebug) logger.Debug($"OK {(decimal)OkCount / TotalRequestsCount:p2} | Emptish: {(decimal)EmptishCount / TotalRequestsCount:p2} | BadQuality: {(decimal)BadQualityCount / TotalRequestsCount:p2} | InvalidFormat: {(decimal)InvalidFormatCount / TotalRequestsCount:p2} | NotAssigned {(decimal)NotAssignedCount / TotalRequestsCount:p2}"); + if (RequestedNodesCount > 0) + { + decimal consumedRatio = (decimal)ConsumedNodesCount / RequestedNodesCount; + decimal saveRatio = (decimal)SavedNodesCount / RequestedNodesCount; + decimal dbCheckRatio = (decimal)DbChecks / RequestedNodesCount; + if (logger.IsDebug) logger.Debug( + $"Consumed {consumedRatio:p2} | Saved {saveRatio:p2} | DB Reads : {dbCheckRatio:p2} | DB checks {StateWasThere}/{StateWasNotThere + StateWasThere} | Cached {(decimal)CheckWasCached / allChecks:P2} + {(decimal)CheckWasInDependencies / allChecks:P2}"); + } + + LastReportTime.full = DateTime.UtcNow; + } + + LastReportTime.small = DateTime.UtcNow; + } + } + + private void LoadFromSerialized(byte[] serializedData) + { + if (serializedData != null) + { + RlpStream rlpStream = new(serializedData); + rlpStream.ReadSequenceLength(); + ConsumedNodesCount = rlpStream.DecodeLong(); + SavedStorageCount = rlpStream.DecodeLong(); + SavedStateCount = rlpStream.DecodeLong(); + SavedNodesCount = rlpStream.DecodeLong(); + SavedAccounts = rlpStream.DecodeLong(); + SavedCode = rlpStream.DecodeLong(); + RequestedNodesCount = rlpStream.DecodeLong(); + DbChecks = rlpStream.DecodeLong(); + StateWasThere = rlpStream.DecodeLong(); + StateWasNotThere = rlpStream.DecodeLong(); + DataSize = rlpStream.DecodeLong(); + + if (rlpStream.Position != rlpStream.Length) + { + SecondsInSync = rlpStream.DecodeLong(); + } + } + } + + public byte[] Serialize() + { + Rlp rlp = Rlp.Encode( + Rlp.Encode(ConsumedNodesCount), + Rlp.Encode(SavedStorageCount), + Rlp.Encode(SavedStateCount), + Rlp.Encode(SavedNodesCount), + Rlp.Encode(SavedAccounts), + Rlp.Encode(SavedCode), + Rlp.Encode(RequestedNodesCount), + Rlp.Encode(DbChecks), + Rlp.Encode(StateWasThere), + Rlp.Encode(StateWasNotThere), + Rlp.Encode(DataSize), + Rlp.Encode(SecondsInSync)); + + return rlp.Bytes; + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs new file mode 100644 index 00000000000..b6d69e7a50a --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs @@ -0,0 +1,218 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Nethermind.Synchronization.FastSync +{ + internal interface IPendingSyncItems + { + StateSyncItem? PeekState(); + string RecalculatePriorities(); + List TakeBatch(int maxSize); + void Clear(); + byte MaxStateLevel { get; set; } + byte MaxStorageLevel { get; set; } + int Count { get; } + string Description { get; } + void PushToSelectedStream(StateSyncItem item, decimal progress); + } + + internal class PendingSyncItems : IPendingSyncItems + { + private ConcurrentStack[] _allStacks = new ConcurrentStack[7]; + + private ConcurrentStack CodeItems => _allStacks[0]; + + private ConcurrentStack StorageItemsPriority0 => _allStacks[1]; + private ConcurrentStack StorageItemsPriority1 => _allStacks[2]; + private ConcurrentStack StorageItemsPriority2 => _allStacks[3]; + + private ConcurrentStack StateItemsPriority0 => _allStacks[4]; + private ConcurrentStack StateItemsPriority1 => _allStacks[5]; + private ConcurrentStack StateItemsPriority2 => _allStacks[6]; + + private decimal _lastSyncProgress; + private uint _maxStorageRightness; // for priority calculation (prefer left) + private uint _maxRightness; // for priority calculation (prefer left) + + public byte MaxStorageLevel { get; set; } + public byte MaxStateLevel { get; set; } + + public PendingSyncItems() + { + for (int i = 0; i < _allStacks.Length; i++) + { + _allStacks[i] = new ConcurrentStack(); + } + } + + public void PushToSelectedStream(StateSyncItem stateSyncItem, decimal progress) + { + _lastSyncProgress = progress; + double priority = CalculatePriority(stateSyncItem.NodeDataType, stateSyncItem.Level, stateSyncItem.Rightness); + + var selectedCollection = stateSyncItem.NodeDataType switch + { + NodeDataType.Code => CodeItems, + NodeDataType.State when priority <= 0.5f => StateItemsPriority0, + NodeDataType.State when priority <= 1.5f => StateItemsPriority1, + NodeDataType.State => StateItemsPriority2, + NodeDataType.Storage when priority <= 0.5f => StorageItemsPriority0, + NodeDataType.Storage when priority <= 1.5f => StorageItemsPriority1, + NodeDataType.Storage => StorageItemsPriority2, + _ => throw new ArgumentOutOfRangeException() + }; + + selectedCollection.Push(stateSyncItem); + } + + private string LevelsDescription => $"{MaxStorageLevel:D2} {_maxStorageRightness:D8} | {MaxStateLevel:D2} {_maxRightness:D8}"; + public string Description => $"{CodeItems?.Count ?? 0:D4} + {StorageItemsPriority0?.Count ?? 0:D6} {StorageItemsPriority1?.Count ?? 0:D6} {StorageItemsPriority2?.Count ?? 0:D6} + {StateItemsPriority0?.Count ?? 0:D6} {StateItemsPriority1?.Count ?? 0:D6} {StateItemsPriority2?.Count ?? 0:D6}"; + public int Count => _allStacks.Sum(n => n?.Count ?? 0); + + public StateSyncItem? PeekState() + { + StateItemsPriority0.TryPeek(out StateSyncItem? node); + + if (node == null) + { + StateItemsPriority1.TryPeek(out node); + } + + if (node == null) + { + StateItemsPriority2.TryPeek(out node); + } + + return node; + } + + private float CalculatePriority(NodeDataType nodeDataType, byte level, uint rightness) + { + if (nodeDataType == NodeDataType.Code) + { + return 0f; + } + + switch (nodeDataType) + { + case NodeDataType.Storage: + MaxStorageLevel = Math.Max(MaxStorageLevel, level); + _maxStorageRightness = Math.Max(_maxStorageRightness, rightness); + break; + case NodeDataType.State: + MaxStateLevel = Math.Max(MaxStateLevel, level); + _maxRightness = Math.Max(_maxRightness, rightness); + break; + } + + float priority = CalculatePriority(level, rightness); + + return priority; + } + + private float CalculatePriority(byte level, uint rightness) + { + // the more synced we are the more to the right we want to go - hence the sync progress modifier at the end + // we want to keep more or less to the same side (left or right - we chose left) so we punish + // the high child indices + // we want to go deep first so we add bonus for the depth + float priority = 1.00f - (float)level / Math.Max(MaxStateLevel, (byte)1) + (float)rightness / Math.Max(_maxRightness, 1) - (float)_lastSyncProgress / 2; + return priority; + } + + public void Clear() + { + for (int i = 0; i < _allStacks.Length; i++) + { + _allStacks[i]?.Clear(); + } + } + + private bool TryTake(out StateSyncItem? node) + { + for (int i = 0; i < _allStacks.Length; i++) + { + if (_allStacks[i].TryPop(out node)) + { + return true; + } + } + + node = null; + return false; + } + + public List TakeBatch(int maxSize) + { + // the limitation is to prevent an early explosion of request sizes with low level nodes + // the moment we find the first leaf we will know something more about the tree structure and hence + // prevent lot of Stream2 entries to stay in memory for a long time + int length = MaxStateLevel == 64 ? maxSize : Math.Max(1, (int)(maxSize * ((decimal)MaxStateLevel / 64) * ((decimal)MaxStateLevel / 64))); + + List requestItems = new(length); + for (int i = 0; i < length; i++) + { + if (TryTake(out StateSyncItem? requestItem)) + { + requestItems.Add(requestItem!); + } + else + { + break; + } + } + + return requestItems; + } + + public string RecalculatePriorities() + { + Stopwatch stopwatch = Stopwatch.StartNew(); + + string reviewMessage = $"Node sync queues review ({LevelsDescription}):" + Environment.NewLine; + reviewMessage += $" before {Description}" + Environment.NewLine; + + List temp = new(); + while (StateItemsPriority2.TryPop(out StateSyncItem? poppedSyncItem)) + { + temp.Add(poppedSyncItem!); + } + + while (StorageItemsPriority2.TryPop(out StateSyncItem? poppedSyncItem)) + { + temp.Add(poppedSyncItem!); + } + + foreach (StateSyncItem syncItem in temp) + { + PushToSelectedStream(syncItem, _lastSyncProgress); + } + + reviewMessage += $" after {Description}" + Environment.NewLine; + + stopwatch.Stop(); + reviewMessage += $" time spent in review: {stopwatch.ElapsedMilliseconds}ms"; + return reviewMessage; + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DetailedProgress.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DetailedProgress.cs deleted file mode 100644 index cab70682818..00000000000 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.DetailedProgress.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) 2021 Demerzel Solutions Limited -// This file is part of the Nethermind library. -// -// The Nethermind library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Nethermind library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Nethermind. If not, see . -// - -using System; -using Nethermind.Blockchain; -using Nethermind.Logging; -using Nethermind.Serialization.Rlp; - -namespace Nethermind.Synchronization.FastSync -{ - public partial class StateSyncFeed - { - private class DetailedProgress - { - private long _lastDataSize; - // private long _lastHandledNodesCount; - internal long LastDbReads; - internal decimal AverageTimeInHandler; - // ReSharper disable once NotAccessedField.Local - internal long LastRequestedNodesCount; - // ReSharper disable once NotAccessedField.Local - internal long LastSavedNodesCount; - internal long ConsumedNodesCount; - internal long SavedStorageCount; - internal long SavedStateCount; - internal long SavedNodesCount; - internal long SavedAccounts; - internal long SavedCode; - internal long RequestedNodesCount; - internal long HandledNodesCount; - internal long SecondsInSync; - internal long DbChecks; - internal long CheckWasCached; - internal long CheckWasInDependencies; - internal long StateWasThere; - internal long StateWasNotThere; - internal long EmptishCount; - internal long InvalidFormatCount; - internal long OkCount; - internal long BadQualityCount; - internal long NotAssignedCount; - internal long DataSize; - - private long TotalRequestsCount => EmptishCount + InvalidFormatCount + BadQualityCount + OkCount + NotAssignedCount; - public long ProcessedRequestsCount => EmptishCount + BadQualityCount + OkCount; - - internal (DateTime small, DateTime full) LastReportTime = (DateTime.MinValue, DateTime.MinValue); - - private Known.SizeInfo? _chainSizeInfo; - - public DetailedProgress(ulong chainId, byte[] serializedInitialState) - { - if (Known.ChainSize.ContainsKey(chainId)) - { - _chainSizeInfo = Known.ChainSize[chainId]; - } - - LoadFromSerialized(serializedInitialState); - } - - internal void DisplayProgressReport(int pendingRequestsCount, BranchProgress branchProgress, ILogger logger) - { - TimeSpan sinceLastReport = DateTime.UtcNow - LastReportTime.small; - if (sinceLastReport > TimeSpan.FromSeconds(1)) - { - // decimal savedNodesPerSecond = 1000m * (SavedNodesCount - LastSavedNodesCount) / (decimal) sinceLastReport.TotalMilliseconds; - decimal savedKBytesPerSecond = 1000m * ((DataSize - _lastDataSize) / 1000m) / (decimal) sinceLastReport.TotalMilliseconds; - // decimal requestedNodesPerSecond = 1000m * (RequestedNodesCount - LastRequestedNodesCount) / (decimal) sinceLastReport.TotalMilliseconds; - // decimal handledNodesPerSecond = 1000m * (HandledNodesCount - _lastHandledNodesCount) / (decimal) sinceLastReport.TotalMilliseconds; - _lastDataSize = DataSize; - // LastSavedNodesCount = SavedNodesCount; - // LastRequestedNodesCount = RequestedNodesCount; - // _lastHandledNodesCount = HandledNodesCount; - // if (_logger.IsInfo) _logger.Info($"Time {TimeSpan.FromSeconds(_secondsInSync):dd\\.hh\\:mm\\:ss} | {(decimal) _dataSize / 1000 / 1000,6:F2}MB | kBps: {savedKBytesPerSecond,5:F0} | P: {_pendingRequests.Count} | acc {_savedAccounts} | queues {StreamsDescription} | db {_averageTimeInHandler:f2}ms"); - - Metrics.StateSynced = DataSize; - string dataSizeInfo = $"{(decimal) DataSize / 1000 / 1000,6:F2}MB"; - if (_chainSizeInfo != null) - { - decimal percentage = Math.Min(1, (decimal) DataSize / _chainSizeInfo.Value.Current); - dataSizeInfo = string.Concat( - $"~{percentage:P2} | ", dataSizeInfo, - $" / ~{(decimal) _chainSizeInfo.Value.Current / 1000 / 1000,6:F2}MB"); - } - - if (logger.IsInfo) logger.Info( - $"State Sync {TimeSpan.FromSeconds(SecondsInSync):dd\\.hh\\:mm\\:ss} | {dataSizeInfo} | branches: {branchProgress.Progress:P2} | kB/s: {savedKBytesPerSecond,5:F0} | accounts {SavedAccounts} | nodes {SavedNodesCount} | diagnostics: {pendingRequestsCount}.{AverageTimeInHandler:f2}ms"); - if (logger.IsDebug && DateTime.UtcNow - LastReportTime.full > TimeSpan.FromSeconds(10)) - { - long allChecks = CheckWasInDependencies + CheckWasCached + StateWasThere + StateWasNotThere; - if (logger.IsDebug) logger.Debug($"OK {(decimal) OkCount / TotalRequestsCount:p2} | Emptish: {(decimal) EmptishCount / TotalRequestsCount:p2} | BadQuality: {(decimal) BadQualityCount / TotalRequestsCount:p2} | InvalidFormat: {(decimal) InvalidFormatCount / TotalRequestsCount:p2} | NotAssigned {(decimal) NotAssignedCount / TotalRequestsCount:p2}"); - if (RequestedNodesCount > 0) - { - decimal consumedRatio = (decimal) ConsumedNodesCount / RequestedNodesCount; - decimal saveRatio = (decimal) SavedNodesCount / RequestedNodesCount; - decimal dbCheckRatio = (decimal) DbChecks / RequestedNodesCount; - if (logger.IsDebug) logger.Debug( - $"Consumed {consumedRatio:p2} | Saved {saveRatio:p2} | DB Reads : {dbCheckRatio:p2} | DB checks {StateWasThere}/{StateWasNotThere + StateWasThere} | Cached {(decimal) CheckWasCached / allChecks:P2} + {(decimal) CheckWasInDependencies / allChecks:P2}"); - } - - LastReportTime.full = DateTime.UtcNow; - } - - LastReportTime.small = DateTime.UtcNow; - } - } - - private void LoadFromSerialized(byte[] serializedData) - { - if (serializedData != null) - { - RlpStream rlpStream = new(serializedData); - rlpStream.ReadSequenceLength(); - ConsumedNodesCount = rlpStream.DecodeLong(); - SavedStorageCount = rlpStream.DecodeLong(); - SavedStateCount = rlpStream.DecodeLong(); - SavedNodesCount = rlpStream.DecodeLong(); - SavedAccounts = rlpStream.DecodeLong(); - SavedCode = rlpStream.DecodeLong(); - RequestedNodesCount = rlpStream.DecodeLong(); - DbChecks = rlpStream.DecodeLong(); - StateWasThere = rlpStream.DecodeLong(); - StateWasNotThere = rlpStream.DecodeLong(); - DataSize = rlpStream.DecodeLong(); - - if (rlpStream.Position != rlpStream.Length) - { - SecondsInSync = rlpStream.DecodeLong(); - } - } - } - - public byte[] Serialize() - { - Rlp rlp = Rlp.Encode( - Rlp.Encode(ConsumedNodesCount), - Rlp.Encode(SavedStorageCount), - Rlp.Encode(SavedStateCount), - Rlp.Encode(SavedNodesCount), - Rlp.Encode(SavedAccounts), - Rlp.Encode(SavedCode), - Rlp.Encode(RequestedNodesCount), - Rlp.Encode(DbChecks), - Rlp.Encode(StateWasThere), - Rlp.Encode(StateWasNotThere), - Rlp.Encode(DataSize), - Rlp.Encode(SecondsInSync)); - - return rlp.Bytes; - } - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.PendingSyncItems.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.PendingSyncItems.cs deleted file mode 100644 index d33a662905b..00000000000 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.PendingSyncItems.cs +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) 2021 Demerzel Solutions Limited -// This file is part of the Nethermind library. -// -// The Nethermind library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The Nethermind library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the Nethermind. If not, see . -// - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace Nethermind.Synchronization.FastSync -{ - public partial class StateSyncFeed - { - internal interface IPendingSyncItems - { - StateSyncItem? PeekState(); - string RecalculatePriorities(); - List TakeBatch(int maxSize); - void Clear(); - byte MaxStateLevel { get; set; } - byte MaxStorageLevel { get; set; } - int Count { get; } - string Description { get; } - void PushToSelectedStream(StateSyncItem item, decimal progress); - } - - internal class PendingSyncItems : IPendingSyncItems - { - private ConcurrentStack[] _allStacks = new ConcurrentStack[7]; - - private ConcurrentStack CodeItems => _allStacks[0]; - - private ConcurrentStack StorageItemsPriority0 => _allStacks[1]; - private ConcurrentStack StorageItemsPriority1 => _allStacks[2]; - private ConcurrentStack StorageItemsPriority2 => _allStacks[3]; - - private ConcurrentStack StateItemsPriority0 => _allStacks[4]; - private ConcurrentStack StateItemsPriority1 => _allStacks[5]; - private ConcurrentStack StateItemsPriority2 => _allStacks[6]; - - private decimal _lastSyncProgress; - private uint _maxStorageRightness; // for priority calculation (prefer left) - private uint _maxRightness; // for priority calculation (prefer left) - - public byte MaxStorageLevel { get; set; } - public byte MaxStateLevel { get; set; } - - public PendingSyncItems() - { - for (int i = 0; i < _allStacks.Length; i++) - { - _allStacks[i] = new ConcurrentStack(); - } - } - - public void PushToSelectedStream(StateSyncItem stateSyncItem, decimal progress) - { - _lastSyncProgress = progress; - double priority = CalculatePriority(stateSyncItem.NodeDataType, stateSyncItem.Level, stateSyncItem.Rightness); - - var selectedCollection = stateSyncItem.NodeDataType switch - { - NodeDataType.Code => CodeItems, - NodeDataType.State when priority <= 0.5f => StateItemsPriority0, - NodeDataType.State when priority <= 1.5f => StateItemsPriority1, - NodeDataType.State => StateItemsPriority2, - NodeDataType.Storage when priority <= 0.5f => StorageItemsPriority0, - NodeDataType.Storage when priority <= 1.5f => StorageItemsPriority1, - NodeDataType.Storage => StorageItemsPriority2, - _ => throw new ArgumentOutOfRangeException() - }; - - selectedCollection.Push(stateSyncItem); - } - - private string LevelsDescription => $"{MaxStorageLevel:D2} {_maxStorageRightness:D8} | {MaxStateLevel:D2} {_maxRightness:D8}"; - public string Description => $"{CodeItems?.Count ?? 0:D4} + {StorageItemsPriority0?.Count ?? 0:D6} {StorageItemsPriority1?.Count ?? 0:D6} {StorageItemsPriority2?.Count ?? 0:D6} + {StateItemsPriority0?.Count ?? 0:D6} {StateItemsPriority1?.Count ?? 0:D6} {StateItemsPriority2?.Count ?? 0:D6}"; - public int Count => _allStacks.Sum(n => n?.Count ?? 0); - - public StateSyncItem? PeekState() - { - StateItemsPriority0.TryPeek(out StateSyncItem? node); - - if (node == null) - { - StateItemsPriority1.TryPeek(out node); - } - - if (node == null) - { - StateItemsPriority2.TryPeek(out node); - } - - return node; - } - - private float CalculatePriority(NodeDataType nodeDataType, byte level, uint rightness) - { - if (nodeDataType == NodeDataType.Code) - { - return 0f; - } - - switch (nodeDataType) - { - case NodeDataType.Storage: - MaxStorageLevel = Math.Max(MaxStorageLevel, level); - _maxStorageRightness = Math.Max(_maxStorageRightness, rightness); - break; - case NodeDataType.State: - MaxStateLevel = Math.Max(MaxStateLevel, level); - _maxRightness = Math.Max(_maxRightness, rightness); - break; - } - - float priority = CalculatePriority(level, rightness); - - return priority; - } - - private float CalculatePriority(byte level, uint rightness) - { - // the more synced we are the more to the right we want to go - hence the sync progress modifier at the end - // we want to keep more or less to the same side (left or right - we chose left) so we punish - // the high child indices - // we want to go deep first so we add bonus for the depth - float priority = 1.00f - (float) level / Math.Max(MaxStateLevel, (byte) 1) + (float) rightness / Math.Max(_maxRightness, 1) - (float) _lastSyncProgress / 2; - return priority; - } - - public void Clear() - { - for (int i = 0; i < _allStacks.Length; i++) - { - _allStacks[i]?.Clear(); - } - } - - private bool TryTake(out StateSyncItem? node) - { - for (int i = 0; i < _allStacks.Length; i++) - { - if (_allStacks[i].TryPop(out node)) - { - return true; - } - } - - node = null; - return false; - } - - public List TakeBatch(int maxSize) - { - // the limitation is to prevent an early explosion of request sizes with low level nodes - // the moment we find the first leaf we will know something more about the tree structure and hence - // prevent lot of Stream2 entries to stay in memory for a long time - int length = MaxStateLevel == 64 ? maxSize : Math.Max(1, (int) (maxSize * ((decimal) MaxStateLevel / 64) * ((decimal) MaxStateLevel / 64))); - - List requestHashes = new(); - for (int i = 0; i < length; i++) - { - if (TryTake(out StateSyncItem? requestItem)) - { - requestHashes.Add(requestItem!); - } - else - { - break; - } - } - - return requestHashes; - } - - public string RecalculatePriorities() - { - Stopwatch stopwatch = new(); - stopwatch.Start(); - - string reviewMessage = $"Node sync queues review ({LevelsDescription}):" + Environment.NewLine; - reviewMessage += $" before {Description}" + Environment.NewLine; - - List temp = new(); - while (StateItemsPriority2.TryPop(out StateSyncItem? poppedSyncItem)) - { - temp.Add(poppedSyncItem!); - } - - while (StorageItemsPriority2.TryPop(out StateSyncItem? poppedSyncItem)) - { - temp.Add(poppedSyncItem!); - } - - foreach (StateSyncItem syncItem in temp) - { - PushToSelectedStream(syncItem, _lastSyncProgress); - } - - reviewMessage += $" after {Description}" + Environment.NewLine; - - stopwatch.Stop(); - reviewMessage += $" time spent in review: {stopwatch.ElapsedMilliseconds}ms"; - return reviewMessage; - } - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.cs index 7c1b62ffdc3..8851105c5b6 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncFeed.cs @@ -19,175 +19,55 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; -using Nethermind.Blockchain; -using Nethermind.Core; -using Nethermind.Core.Caching; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Db; using Nethermind.Logging; -using Nethermind.Serialization.Rlp; using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; -using Nethermind.Trie; -using Nethermind.Trie.Pruning; namespace Nethermind.Synchronization.FastSync { public partial class StateSyncFeed : SyncFeed, IDisposable { - public const int AlreadySavedCapacity = 1024 * 1024; - public const int MaxRequestSize = 384; - private const StateSyncBatch EmptyBatch = null; - private static readonly AccountDecoder AccountDecoder = new(); - - private readonly DetailedProgress _data; - private readonly IPendingSyncItems _pendingItems; - - private readonly Keccak _fastSyncProgressKey = Keccak.Zero; - - private DateTime _lastReview = DateTime.UtcNow; - private DateTime _currentSyncStart; - private long _currentSyncStartSecondsInSync; - - private readonly object _stateDbLock = new(); - private readonly object _codeDbLock = new(); - - private readonly Stopwatch _networkWatch = new(); private readonly Stopwatch _handleWatch = new(); - - private Keccak _rootNode = Keccak.EmptyTreeHash; - private int _rootSaved; - private readonly ILogger _logger; - private readonly IDb _codeDb; - private readonly IDb _stateDb; private readonly ISyncModeSelector _syncModeSelector; - private readonly IBlockTree _blockTree; - - private readonly ConcurrentDictionary _pendingRequests = new (); - private Dictionary> _dependencies = new (); - private LruKeyCache _alreadySaved = new(AlreadySavedCapacity, "saved nodes"); - private readonly HashSet _codesSameAsNodes = new(); - - private BranchProgress _branchProgress; - private int _hintsToResetRoot; - private long _blockNumber; + private readonly TreeSync _treeSync; public override bool IsMultiFeed => true; public override AllocationContexts Contexts => AllocationContexts.State; - public StateSyncFeed(IDb codeDb, - IDb stateDb, + public StateSyncFeed( ISyncModeSelector syncModeSelector, - IBlockTree blockTree, + TreeSync treeSync, ILogManager logManager) { - _codeDb = codeDb; - _stateDb = stateDb; - _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _syncModeSelector = syncModeSelector ?? throw new ArgumentNullException(nameof(syncModeSelector)); + _treeSync = treeSync ?? throw new ArgumentNullException(nameof(treeSync)); _syncModeSelector.Changed += SyncModeSelectorOnChanged; _logger = logManager.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - - byte[] progress = _codeDb.Get(_fastSyncProgressKey); - _data = new DetailedProgress(_blockTree.ChainId, progress); - _pendingItems = new PendingSyncItems(); - _branchProgress = new BranchProgress(0, _logger); } public override async Task PrepareRequest() { - if (_rootSaved == 1) - { - VerifyPostSyncCleanUp(); - FinishThisSyncRound(); - return EmptyBatch!; - } - - if ((_syncModeSelector.Current & SyncMode.StateNodes) != SyncMode.StateNodes) - { - return EmptyBatch!; - } - try { - if (_rootNode == Keccak.EmptyTreeHash) - { - if (_logger.IsDebug) _logger.Debug("Falling asleep - root is empty tree"); - FinishThisSyncRound(); - return EmptyBatch!; - } + (bool continueProcessing, bool finishSyncRound) = _treeSync.ValidatePrepareRequest(_syncModeSelector.Current); - if (_hintsToResetRoot >= 32) + if (finishSyncRound) { - if (_logger.IsDebug) _logger.Debug("Falling asleep - many missing responses"); FinishThisSyncRound(); - return EmptyBatch!; } - bool rootNodeKeyExists; - lock (_stateDbLock) + if (!continueProcessing) { - try - { - // it finished downloading - rootNodeKeyExists = _stateDb.KeyExists(_rootNode); - } - catch (ObjectDisposedException) - { - return EmptyBatch!; - } - } - - if (rootNodeKeyExists) - { - try - { - VerifyPostSyncCleanUp(); - FinishThisSyncRound(); - return EmptyBatch!; - } - catch (ObjectDisposedException) - { - return EmptyBatch!; - } - } - - List requestHashes = _pendingItems.TakeBatch(MaxRequestSize); - LogRequestInfo(requestHashes); - - long secondsInCurrentSync = (long)(DateTime.UtcNow - _currentSyncStart).TotalSeconds; - - if (requestHashes.Count > 0) - { - StateSyncItem[] requestedNodes = requestHashes.ToArray(); - StateSyncBatch result = new(requestedNodes); - - Interlocked.Add(ref _data.RequestedNodesCount, result.RequestedNodes.Length); - Interlocked.Exchange(ref _data.SecondsInSync, _currentSyncStartSecondsInSync + secondsInCurrentSync); - - if (_logger.IsTrace) _logger.Trace($"After preparing a request of {requestHashes.Count} from ({_pendingItems.Description}) nodes | {_dependencies.Count}"); - if (_logger.IsTrace) _logger.Trace($"Adding pending request {result}"); - _pendingRequests.TryAdd(result, null); - - Interlocked.Increment(ref Metrics.StateSyncRequests); - return await Task.FromResult(result); - } - - if (requestHashes.Count == 0 && secondsInCurrentSync >= Timeouts.Eth.Seconds) - { - // trying to reproduce past behaviour where we can recognize the transition time this way - Interlocked.Increment(ref _hintsToResetRoot); + return EmptyBatch!; } - return await Task.FromResult(EmptyBatch); + return await _treeSync.PrepareRequest(_syncModeSelector.Current); } catch (Exception e) { @@ -196,238 +76,9 @@ public StateSyncFeed(IDb codeDb, } } - public override SyncResponseHandlingResult HandleResponse(StateSyncBatch? batch) - { - if (batch == EmptyBatch) - { - if (_logger.IsError) _logger.Error("Received empty batch as a response"); - return SyncResponseHandlingResult.InternalError; - } - - if (_logger.IsTrace) _logger.Trace($"Removing pending request {batch}"); - if (!_pendingRequests.TryRemove(batch, out _)) - { - if (_logger.IsDebug) _logger.Debug($"Cannot remove pending request {batch}"); - return SyncResponseHandlingResult.OK; - } - - int requestLength = batch.RequestedNodes?.Length ?? 0; - int responseLength = batch.Responses?.Length ?? 0; - - void AddAgainAllItems() - { - for (int i = 0; i < requestLength; i++) - { - AddNodeToPending(batch.RequestedNodes![i], null, "missing", true); - } - } - - try - { - lock (_handleWatch) - { - if (DateTime.UtcNow - _lastReview > TimeSpan.FromSeconds(60)) - { - _lastReview = DateTime.UtcNow; - string reviewMessage = _pendingItems.RecalculatePriorities(); - if (_logger.IsDebug) _logger.Debug(reviewMessage); - } - - _handleWatch.Restart(); - - bool isMissingRequestData = batch.RequestedNodes == null; - if (isMissingRequestData) - { - _hintsToResetRoot++; - - AddAgainAllItems(); - if (_logger.IsWarn) _logger.Warn("Batch response had invalid format"); - Interlocked.Increment(ref _data.InvalidFormatCount); - return isMissingRequestData ? SyncResponseHandlingResult.InternalError : SyncResponseHandlingResult.NotAssigned; - } - - if (batch.Responses == null) - { - AddAgainAllItems(); - if (_logger.IsTrace) _logger.Trace("Batch was not assigned to any peer."); - Interlocked.Increment(ref _data.NotAssignedCount); - return SyncResponseHandlingResult.NotAssigned; - } - - if (_logger.IsTrace) _logger.Trace($"Received node data - {responseLength} items in response to {requestLength}"); - int nonEmptyResponses = 0; - int invalidNodes = 0; - for (int i = 0; i < batch.RequestedNodes!.Length; i++) - { - StateSyncItem currentStateSyncItem = batch.RequestedNodes[i]; - - /* if the peer has limit on number of requests in a batch then the response will possibly be - shorter than the request */ - if (batch.Responses.Length < i + 1) - { - AddNodeToPending(currentStateSyncItem, null, "missing", true); - continue; - } - - /* if the peer does not have details of this particular node */ - byte[] currentResponseItem = batch.Responses[i]; - if (currentResponseItem == null) - { - AddNodeToPending(batch.RequestedNodes[i], null, "missing", true); - continue; - } - - /* node sent data that is not consistent with its hash - it happens surprisingly often */ - if (!ValueKeccak.Compute(currentResponseItem).BytesAsSpan.SequenceEqual(currentStateSyncItem.Hash.Bytes)) - { - AddNodeToPending(currentStateSyncItem, null, "missing", true); - if (_logger.IsTrace) _logger.Trace($"Peer sent invalid data (batch {requestLength}->{responseLength}) of length {batch.Responses[i]?.Length} of type {batch.RequestedNodes[i].NodeDataType} at level {batch.RequestedNodes[i].Level} of type {batch.RequestedNodes[i].NodeDataType} Keccak({batch.Responses[i].ToHexString()}) != {batch.RequestedNodes[i].Hash}"); - invalidNodes++; - continue; - } - - nonEmptyResponses++; - NodeDataType nodeDataType = currentStateSyncItem.NodeDataType; - if (nodeDataType == NodeDataType.Code) - { - SaveNode(currentStateSyncItem, currentResponseItem); - continue; - } - - HandleTrieNode(currentStateSyncItem, currentResponseItem, ref invalidNodes); - } - - Interlocked.Add(ref _data.ConsumedNodesCount, nonEmptyResponses); - StoreProgressInDb(); - - if (_logger.IsTrace) _logger.Trace($"After handling response (non-empty responses {nonEmptyResponses}) of {batch.RequestedNodes.Length} from ({_pendingItems.Description}) nodes"); - - /* magic formula is ratio of our desired batch size - 1024 to Geth max batch size 384 times some missing nodes ratio */ - bool isEmptish = (decimal)nonEmptyResponses / Math.Max(requestLength, 1) < 384m / 1024m * 0.75m; - if (isEmptish) - { - Interlocked.Increment(ref _hintsToResetRoot); - Interlocked.Increment(ref _data.EmptishCount); - } - else - { - Interlocked.Exchange(ref _hintsToResetRoot, 0); - } - - /* here we are very forgiving for Geth nodes that send bad data fast */ - bool isBadQuality = nonEmptyResponses > 64 && (decimal)invalidNodes / Math.Max(requestLength, 1) > 0.50m; - if (isBadQuality) Interlocked.Increment(ref _data.BadQualityCount); - - bool isEmpty = nonEmptyResponses == 0 && !isBadQuality; - if (isEmpty) - { - if (_logger.IsDebug) _logger.Debug($"Peer sent no data in response to a request of length {batch.RequestedNodes.Length}"); - return SyncResponseHandlingResult.NoProgress; - } - - if (!isEmptish && !isBadQuality) - { - Interlocked.Increment(ref _data.OkCount); - } - - SyncResponseHandlingResult result = isEmptish - ? SyncResponseHandlingResult.Emptish - : isBadQuality - ? SyncResponseHandlingResult.LesserQuality - : SyncResponseHandlingResult.OK; - - _data.DisplayProgressReport(_pendingRequests.Count, _branchProgress, _logger); - - long total = _handleWatch.ElapsedMilliseconds + _networkWatch.ElapsedMilliseconds; - if (total != 0) - { - // calculate averages - if (_logger.IsTrace) _logger.Trace($"Prepare batch {_networkWatch.ElapsedMilliseconds}ms ({(decimal)_networkWatch.ElapsedMilliseconds / total:P0}) - Handle {_handleWatch.ElapsedMilliseconds}ms ({(decimal)_handleWatch.ElapsedMilliseconds / total:P0})"); - } - - if (_handleWatch.ElapsedMilliseconds > 250) - { - if (_logger.IsDebug) _logger.Debug($"Handle watch {_handleWatch.ElapsedMilliseconds}, DB reads {_data.DbChecks - _data.LastDbReads}, ratio {(decimal)_handleWatch.ElapsedMilliseconds / Math.Max(1, _data.DbChecks - _data.LastDbReads)}"); - } - - _data.LastDbReads = _data.DbChecks; - _data.AverageTimeInHandler = (_data.AverageTimeInHandler * (_data.ProcessedRequestsCount - 1) + _handleWatch.ElapsedMilliseconds) / _data.ProcessedRequestsCount; - Interlocked.Add(ref _data.HandledNodesCount, nonEmptyResponses); - return result; - } - } - catch (Exception e) - { - _logger.Error("Error when handling state sync response", e); - return SyncResponseHandlingResult.InternalError; - } - finally - { - _handleWatch.Stop(); - } - } - - public void ResetStateRoot(long blockNumber, Keccak stateRoot) + public override SyncResponseHandlingResult HandleResponse(StateSyncBatch? batch, PeerInfo peer = null) { - if (CurrentState != SyncFeedState.Dormant) - { - throw new InvalidOperationException("Cannot reset state sync on an active feed"); - } - - Interlocked.Exchange(ref _hintsToResetRoot, 0); - - if (_logger.IsInfo) _logger.Info($"Setting state sync state root to {blockNumber} {stateRoot}"); - _currentSyncStart = DateTime.UtcNow; - _currentSyncStartSecondsInSync = _data.SecondsInSync; - - _data.LastReportTime = (DateTime.UtcNow, DateTime.UtcNow); - _data.LastSavedNodesCount = _data.SavedNodesCount; - _data.LastRequestedNodesCount = _data.RequestedNodesCount; - if (_rootNode != stateRoot) - { - _branchProgress = new BranchProgress(blockNumber, _logger); - _blockNumber = blockNumber; - _rootNode = stateRoot; - lock (_dependencies) _dependencies.Clear(); - lock (_codesSameAsNodes) _codesSameAsNodes.Clear(); - - if (_logger.IsDebug) _logger.Debug($"Clearing node stacks ({_pendingItems.Description})"); - _pendingItems.Clear(); - Interlocked.Exchange(ref _rootSaved, 0); - } - else - { - foreach ((StateSyncBatch pendingRequest, _) in _pendingRequests) - { - // re-add the pending request - for (int i = 0; i < pendingRequest.RequestedNodes.Length; i++) - { - AddNodeToPending(pendingRequest.RequestedNodes[i], null, "pending request", true); - } - } - } - - _pendingRequests.Clear(); - - bool hasOnlyRootNode = false; - - if (_rootNode != Keccak.EmptyTreeHash) - { - if (_pendingItems.Count == 1) - { - // state root can only be located on state stream - StateSyncItem? potentialRoot = _pendingItems.PeekState(); - if (potentialRoot?.Hash == _rootNode) - { - hasOnlyRootNode = true; - } - } - - if (!hasOnlyRootNode) - { - AddNodeToPending(new StateSyncItem(_rootNode, NodeDataType.State), null, "initial"); - } - } + return _treeSync.HandleResponse(batch); } public void Dispose() @@ -441,417 +92,19 @@ private void SyncModeSelectorOnChanged(object? sender, SyncModeChangedEventArgs { if ((e.Current & SyncMode.StateNodes) == SyncMode.StateNodes) { - BlockHeader bestSuggested = _blockTree.BestSuggestedHeader; - if (bestSuggested == null || bestSuggested.Number == 0) - { - return; - } - - if (_logger.IsInfo) _logger.Info($"Starting the node data sync from the {bestSuggested.ToString(BlockHeader.Format.Short)} {bestSuggested.StateRoot} root"); - ResetStateRoot(bestSuggested.Number, bestSuggested.StateRoot!); + _treeSync.ResetStateRootToBestSuggested(CurrentState); Activate(); } } } - private AddNodeResult AddNodeToPending(StateSyncItem syncItem, DependentItem? dependentItem, string reason, bool missing = false) - { - if (!missing) - { - if (syncItem.Level <= 2) - { - _branchProgress.ReportSynced(syncItem, NodeProgressState.Requested); - } - - if (_alreadySaved.Get(syncItem.Hash)) - { - Interlocked.Increment(ref _data.CheckWasCached); - if (_logger.IsTrace) _logger.Trace($"Node already in the DB - skipping {syncItem.Hash}"); - _branchProgress.ReportSynced(syncItem, NodeProgressState.AlreadySaved); - return AddNodeResult.AlreadySaved; - } - - object lockToTake = syncItem.NodeDataType == NodeDataType.Code ? _codeDbLock : _stateDbLock; - lock (lockToTake) - { - IDb dbToCheck = syncItem.NodeDataType == NodeDataType.Code ? _codeDb : _stateDb; - Interlocked.Increment(ref _data.DbChecks); - bool keyExists = dbToCheck.KeyExists(syncItem.Hash); - - if (keyExists) - { - if (_logger.IsTrace) _logger.Trace($"Node already in the DB - skipping {syncItem.Hash}"); - _alreadySaved.Set(syncItem.Hash); - Interlocked.Increment(ref _data.StateWasThere); - _branchProgress.ReportSynced(syncItem, NodeProgressState.AlreadySaved); - return AddNodeResult.AlreadySaved; - } - - Interlocked.Increment(ref _data.StateWasNotThere); - } - - bool isAlreadyRequested; - lock (_dependencies) - { - isAlreadyRequested = _dependencies.ContainsKey(syncItem.Hash); - if (dependentItem != null) - { - if (_logger.IsTrace) _logger.Trace($"Adding dependency {syncItem.Hash} -> {dependentItem.SyncItem.Hash}"); - AddDependency(syncItem.Hash, dependentItem); - } - } - - /* same items can have same hashes and we only need them once - * there is an issue when we have an item, we add it to dependencies, then we request it and the request times out - * and we never request it again because it was already on the dependencies list */ - if (isAlreadyRequested) - { - Interlocked.Increment(ref _data.CheckWasInDependencies); - if (_logger.IsTrace) _logger.Trace($"Node already requested - skipping {syncItem.Hash}"); - return AddNodeResult.AlreadyRequested; - } - } - - _pendingItems.PushToSelectedStream(syncItem, _branchProgress.LastProgress); - if (_logger.IsTrace) _logger.Trace($"Added a node {syncItem.Hash} - {reason}"); - return AddNodeResult.Added; - } - - private void PossiblySaveDependentNodes(Keccak hash) - { - List nodesToSave = new(); - lock (_dependencies) - { - if (_dependencies.ContainsKey(hash)) - { - HashSet dependentItems = _dependencies[hash]; - - if (_logger.IsTrace) - { - string nodeNodes = dependentItems.Count == 1 ? "node" : "nodes"; - _logger.Trace($"{dependentItems.Count} {nodeNodes} dependent on {hash}"); - } - - foreach (DependentItem dependentItem in dependentItems) - { - dependentItem.Counter--; - - if (dependentItem.Counter == 0) - { - nodesToSave.Add(dependentItem); - } - } - - _dependencies.Remove(hash); - } - else - { - if (_logger.IsTrace) _logger.Trace($"No nodes dependent on {hash}"); - } - } - - foreach (DependentItem dependentItem in nodesToSave) - { - if (dependentItem.IsAccount) Interlocked.Increment(ref _data.SavedAccounts); - SaveNode(dependentItem.SyncItem, dependentItem.Value); - } - } - - private void SaveNode(StateSyncItem syncItem, byte[] data) - { - if (_logger.IsTrace) _logger.Trace($"SAVE {new string('+', syncItem.Level * 2)}{syncItem.NodeDataType.ToString().ToUpperInvariant()} {syncItem.Hash}"); - Interlocked.Increment(ref _data.SavedNodesCount); - switch (syncItem.NodeDataType) - { - case NodeDataType.State: - { - Interlocked.Increment(ref _data.SavedStateCount); - lock (_stateDbLock) - { - Interlocked.Add(ref _data.DataSize, data.Length); - Interlocked.Increment(ref Metrics.SyncedStateTrieNodes); - _stateDb.Set(syncItem.Hash, data); - } - - break; - } - case NodeDataType.Storage: - { - lock (_codesSameAsNodes) - { - if (_codesSameAsNodes.Contains(syncItem.Hash)) - { - lock (_codeDbLock) - { - Interlocked.Add(ref _data.DataSize, data.Length); - Interlocked.Increment(ref Metrics.SyncedCodes); - _codeDb.Set(syncItem.Hash, data); - } - - _codesSameAsNodes.Remove(syncItem.Hash); - } - } - - Interlocked.Increment(ref _data.SavedStorageCount); - lock (_stateDbLock) - { - Interlocked.Add(ref _data.DataSize, data.Length); - Interlocked.Increment(ref Metrics.SyncedStorageTrieNodes); - _stateDb.Set(syncItem.Hash, data); - } - - break; - } - case NodeDataType.Code: - { - Interlocked.Increment(ref _data.SavedCode); - lock (_codeDbLock) - { - Interlocked.Add(ref _data.DataSize, data.Length); - Interlocked.Increment(ref Metrics.SyncedCodes); - _codeDb.Set(syncItem.Hash, data); - } - - break; - } - } - - if (syncItem.IsRoot) - { - if (_logger.IsInfo) _logger.Info($"Saving root {syncItem.Hash} of {_branchProgress.CurrentSyncBlock}"); - - Interlocked.Exchange(ref _rootSaved, 1); - } - - _branchProgress.ReportSynced(syncItem.Level, syncItem.ParentBranchChildIndex, syncItem.BranchChildIndex, syncItem.NodeDataType, NodeProgressState.Saved); - PossiblySaveDependentNodes(syncItem.Hash); - } - - private void VerifyPostSyncCleanUp() - { - lock (_dependencies) - { - if (_dependencies.Count != 0) - { - if (_logger.IsError) _logger.Error($"POSSIBLE FAST SYNC CORRUPTION | Dependencies hanging after the root node saved - count: {_dependencies.Count}, first: {_dependencies.Keys.First()}"); - } - - _dependencies = new Dictionary>(); - // _alreadySaved = new LruKeyCache(AlreadySavedCapacity, "saved nodes"); - } - - if (_pendingItems.Count != 0) - { - if (_logger.IsError) _logger.Error($"POSSIBLE FAST SYNC CORRUPTION | Nodes left after the root node saved - count: {_pendingItems.Count}"); - } - } - - private void StoreProgressInDb() - { - byte[] serializedData = _data.Serialize(); - lock (_stateDbLock) - { - lock (_codeDbLock) - { - _codeDb[_fastSyncProgressKey.Bytes] = serializedData; - } - } - } - - private void HandleTrieNode(StateSyncItem currentStateSyncItem, byte[] currentResponseItem, ref int invalidNodes) - { - NodeDataType nodeDataType = currentStateSyncItem.NodeDataType; - TrieNode trieNode = new(NodeType.Unknown, currentResponseItem); - trieNode.ResolveNode(NullTrieNodeResolver.Instance); // TODO: will this work now? - switch (trieNode.NodeType) - { - case NodeType.Unknown: - invalidNodes++; - if (_logger.IsError) _logger.Error($"Node {currentStateSyncItem.Hash} resolved to {nameof(NodeType.Unknown)}"); - break; - case NodeType.Branch: - DependentItem dependentBranch = new(currentStateSyncItem, currentResponseItem, 0); - - // children may have the same hashes (e.g. a set of accounts with the same code at different addresses) - HashSet alreadyProcessedChildHashes = new(); - for (int childIndex = 15; childIndex >= 0; childIndex--) - { - Keccak? childHash = trieNode.GetChildHash(childIndex); - if (childHash != null && - alreadyProcessedChildHashes.Contains(childHash)) - { - continue; - } - - alreadyProcessedChildHashes.Add(childHash); - - if (childHash != null) - { - AddNodeResult addChildResult = AddNodeToPending(new StateSyncItem(childHash, nodeDataType, currentStateSyncItem.Level + 1, CalculateRightness(trieNode.NodeType, currentStateSyncItem, childIndex)) {BranchChildIndex = (short) childIndex, ParentBranchChildIndex = currentStateSyncItem.BranchChildIndex}, dependentBranch, "branch child"); - if (addChildResult != AddNodeResult.AlreadySaved) - { - dependentBranch.Counter++; - } - else - { - _branchProgress.ReportSynced(currentStateSyncItem.Level + 1, currentStateSyncItem.BranchChildIndex, childIndex, currentStateSyncItem.NodeDataType, NodeProgressState.AlreadySaved); - } - } - else - { - _branchProgress.ReportSynced(currentStateSyncItem.Level + 1, currentStateSyncItem.BranchChildIndex, childIndex, currentStateSyncItem.NodeDataType, NodeProgressState.Empty); - } - } - - if (dependentBranch.Counter == 0) - { - SaveNode(currentStateSyncItem, currentResponseItem); - } - - break; - case NodeType.Extension: - Keccak? next = trieNode.GetChild(NullTrieNodeResolver.Instance, 0)?.Keccak; - if (next != null) - { - DependentItem dependentItem = new(currentStateSyncItem, currentResponseItem, 1); - AddNodeResult addResult = AddNodeToPending( - new StateSyncItem( - next, - nodeDataType, - currentStateSyncItem.Level + trieNode.Path!.Length, - CalculateRightness(trieNode.NodeType, currentStateSyncItem, 0)) - {ParentBranchChildIndex = currentStateSyncItem.BranchChildIndex}, - dependentItem, - "extension child"); - if (addResult == AddNodeResult.AlreadySaved) - { - SaveNode(currentStateSyncItem, currentResponseItem); - } - } - else - { - /* this happens when we have a short RLP format of the node - * that would not be stored as Keccak but full RLP*/ - SaveNode(currentStateSyncItem, currentResponseItem); - } - - break; - case NodeType.Leaf: - if (nodeDataType == NodeDataType.State) - { - _pendingItems.MaxStateLevel = 64; - DependentItem dependentItem = new(currentStateSyncItem, currentResponseItem, 0, true); - (Keccak codeHash, Keccak storageRoot) = AccountDecoder.DecodeHashesOnly(new RlpStream(trieNode.Value)); - if (codeHash != Keccak.OfAnEmptyString) - { - // prepare a branch without the code DB - // this only protects against being same as storage root? - if (codeHash == storageRoot) - { - lock (_codesSameAsNodes) - { - _codesSameAsNodes.Add(codeHash); - } - } - else - { - AddNodeResult addCodeResult = AddNodeToPending(new StateSyncItem(codeHash, NodeDataType.Code, 0, currentStateSyncItem.Rightness), dependentItem, "code"); - if (addCodeResult != AddNodeResult.AlreadySaved) dependentItem.Counter++; - } - } - - if (storageRoot != Keccak.EmptyTreeHash) - { - AddNodeResult addStorageNodeResult = AddNodeToPending(new StateSyncItem(storageRoot, NodeDataType.Storage, 0, currentStateSyncItem.Rightness), dependentItem, "storage"); - if (addStorageNodeResult != AddNodeResult.AlreadySaved) dependentItem.Counter++; - } - - if (dependentItem.Counter == 0) - { - Interlocked.Increment(ref _data.SavedAccounts); - SaveNode(currentStateSyncItem, currentResponseItem); - } - } - else - { - _pendingItems.MaxStorageLevel = 64; - SaveNode(currentStateSyncItem, currentResponseItem); - } - - break; - default: - if (_logger.IsError) _logger.Error($"Unknown value {currentStateSyncItem.NodeDataType} of {nameof(NodeDataType)} at {currentStateSyncItem.Hash}"); - invalidNodes++; - break; - } - } - - private static uint CalculateRightness(NodeType nodeType, StateSyncItem currentStateSyncItem, int childIndex) - { - if (nodeType == NodeType.Branch) - { - return currentStateSyncItem.Rightness + (uint) Math.Pow(16, Math.Max(0, 7 - currentStateSyncItem.Level)) * (uint) childIndex; - } - - if (nodeType == NodeType.Extension) - { - return currentStateSyncItem.Rightness + (uint) Math.Pow(16, Math.Max(0, 7 - currentStateSyncItem.Level)) * 16 - 1; - } - - throw new InvalidOperationException($"Not designed for {nodeType}"); - } - - /// - /// Stores items that cannot be yet persisted. These items will be persisted as soon as all their descendants - /// get persisted. - /// - /// Sync item that this item is dependent on. - /// Item that can only be persisted if all its dependenies are persisted - private void AddDependency(Keccak dependency, DependentItem dependentItem) - { - lock (_dependencies) - { - if (!_dependencies.ContainsKey(dependency)) - { - _dependencies[dependency] = new HashSet(DependentItemComparer.Instance); - } - - _dependencies[dependency].Add(dependentItem); - } - } - private void FinishThisSyncRound() { lock (_handleWatch) { FallAsleep(); - ResetStateRoot(_blockNumber, _rootNode); - } - } - - private void LogRequestInfo(List requestHashes) - { - int requestSize = requestHashes.Count; - if (requestSize < MaxRequestSize) - { - if (_logger.IsDebug) _logger.Debug($"Sending limited size request {requestSize} at level {_pendingItems.MaxStateLevel}"); - } - - if (_logger.IsTrace) _logger.Trace($"Preparing a request of length {requestSize} from ({_pendingItems.Description}) nodes"); - if (_logger.IsTrace) - { - foreach (StateSyncItem stateSyncItem in requestHashes) - { - _logger.Trace($"Requesting {stateSyncItem.Hash}"); - } + _treeSync.ResetStateRoot(CurrentState); } } - - private enum AddNodeResult - { - AlreadySaved, - AlreadyRequested, - Added - } } } diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs index 2df09e3df4c..e3b7df7b419 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . +using System; using System.Diagnostics; using Nethermind.Core.Crypto; @@ -22,9 +23,11 @@ namespace Nethermind.Synchronization.FastSync [DebuggerDisplay("{Level} {NodeDataType} {Hash}")] public class StateSyncItem { - public StateSyncItem(Keccak hash, NodeDataType nodeType, int level = 0, uint rightness = 0) + public StateSyncItem(Keccak hash, byte[] accountPathNibbles, byte[] pathNibbles, NodeDataType nodeType, int level = 0, uint rightness = 0) { Hash = hash; + AccountPathNibbles = accountPathNibbles; + PathNibbles = pathNibbles ?? Array.Empty(); NodeDataType = nodeType; Level = (byte)level; Rightness = rightness; @@ -32,6 +35,10 @@ public StateSyncItem(Keccak hash, NodeDataType nodeType, int level = 0, uint rig public Keccak Hash { get; } + public byte[] AccountPathNibbles { get; } + + public byte[] PathNibbles { get; } + public NodeDataType NodeDataType { get; } public byte Level { get; } diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs new file mode 100644 index 00000000000..0efe4929ffd --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs @@ -0,0 +1,840 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Synchronization.FastSync +{ + public class TreeSync + { + public const int AlreadySavedCapacity = 1024 * 1024; + public const int MaxRequestSize = 384; + + private const StateSyncBatch EmptyBatch = null; + + private static readonly AccountDecoder AccountDecoder = new(); + + private readonly DetailedProgress _data; + private readonly IPendingSyncItems _pendingItems; + + private readonly Keccak _fastSyncProgressKey = Keccak.Zero; + + private DateTime _lastReview = DateTime.UtcNow; + private DateTime _currentSyncStart; + private long _currentSyncStartSecondsInSync; + + private readonly object _stateDbLock = new(); + private readonly object _codeDbLock = new(); + + private readonly Stopwatch _networkWatch = new(); + private readonly Stopwatch _handleWatch = new(); + + private Keccak _rootNode = Keccak.EmptyTreeHash; + private int _rootSaved; + + private readonly ILogger _logger; + private readonly IDb _codeDb; + private readonly IDb _stateDb; + + private readonly IBlockTree _blockTree; + + private readonly ConcurrentDictionary _pendingRequests = new(); + private Dictionary> _dependencies = new(); + private LruKeyCache _alreadySaved = new(AlreadySavedCapacity, "saved nodes"); + private readonly HashSet _codesSameAsNodes = new(); + + private BranchProgress _branchProgress; + private int _hintsToResetRoot; + private long _blockNumber; + private SyncMode _syncMode; + + public TreeSync(SyncMode syncMode, IDb codeDb,IDb stateDb, IBlockTree blockTree, ILogManager logManager) + { + _syncMode = syncMode; + _codeDb = codeDb ?? throw new ArgumentNullException(nameof(codeDb)); + _stateDb = stateDb ?? throw new ArgumentNullException(nameof(stateDb)); + _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); + + _logger = logManager.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + + byte[] progress = _codeDb.Get(_fastSyncProgressKey); + _data = new DetailedProgress(_blockTree.ChainId, progress); + _pendingItems = new PendingSyncItems(); + _branchProgress = new BranchProgress(0, _logger); + } + + public async Task PrepareRequest(SyncMode syncMode) + { + try + { + List requestHashes = _pendingItems.TakeBatch(MaxRequestSize); + LogRequestInfo(requestHashes); + + long secondsInCurrentSync = (long)(DateTime.UtcNow - _currentSyncStart).TotalSeconds; + + if (requestHashes.Count > 0) + { + StateSyncItem[] requestedNodes = requestHashes.ToArray(); + StateSyncBatch result = new(requestedNodes); + + Interlocked.Add(ref _data.RequestedNodesCount, result.RequestedNodes.Length); + Interlocked.Exchange(ref _data.SecondsInSync, _currentSyncStartSecondsInSync + secondsInCurrentSync); + + if (_logger.IsTrace) _logger.Trace($"After preparing a request of {requestHashes.Count} from ({_pendingItems.Description}) nodes | {_dependencies.Count}"); + if (_logger.IsTrace) _logger.Trace($"Adding pending request {result}"); + _pendingRequests.TryAdd(result, null); + + Interlocked.Increment(ref Metrics.StateSyncRequests); + return await Task.FromResult(result); + } + + if (requestHashes.Count == 0 && secondsInCurrentSync >= Timeouts.Eth.TotalSeconds) + { + // trying to reproduce past behaviour where we can recognize the transition time this way + Interlocked.Increment(ref _hintsToResetRoot); + } + + return await Task.FromResult(EmptyBatch); + } + catch (Exception e) + { + _logger.Error("Error when preparing a batch", e); + return await Task.FromResult(EmptyBatch); + } + } + + public SyncResponseHandlingResult HandleResponse(StateSyncBatch? batch) + { + if (batch == EmptyBatch) + { + if (_logger.IsError) _logger.Error("Received empty batch as a response"); + return SyncResponseHandlingResult.InternalError; + } + + if (_logger.IsTrace) _logger.Trace($"Removing pending request {batch}"); + if (!_pendingRequests.TryRemove(batch, out _)) + { + if (_logger.IsDebug) _logger.Debug($"Cannot remove pending request {batch}"); + return SyncResponseHandlingResult.OK; + } + + int requestLength = batch.RequestedNodes?.Length ?? 0; + int responseLength = batch.Responses?.Length ?? 0; + + void AddAgainAllItems() + { + for (int i = 0; i < requestLength; i++) + { + AddNodeToPending(batch.RequestedNodes![i], null, "missing", true); + } + } + + try + { + lock (_handleWatch) + { + if (DateTime.UtcNow - _lastReview > TimeSpan.FromSeconds(60)) + { + _lastReview = DateTime.UtcNow; + string reviewMessage = _pendingItems.RecalculatePriorities(); + if (_logger.IsDebug) _logger.Debug(reviewMessage); + } + + _handleWatch.Restart(); + + bool isMissingRequestData = batch.RequestedNodes == null; + if (isMissingRequestData) + { + _hintsToResetRoot++; + + AddAgainAllItems(); + if (_logger.IsWarn) _logger.Warn("Batch response had invalid format"); + Interlocked.Increment(ref _data.InvalidFormatCount); + return isMissingRequestData ? SyncResponseHandlingResult.InternalError : SyncResponseHandlingResult.NotAssigned; + } + + if (batch.Responses == null) + { + AddAgainAllItems(); + if (_logger.IsTrace) _logger.Trace("Batch was not assigned to any peer."); + Interlocked.Increment(ref _data.NotAssignedCount); + return SyncResponseHandlingResult.NotAssigned; + } + + if (_logger.IsTrace) _logger.Trace($"Received node data - {responseLength} items in response to {requestLength}"); + int nonEmptyResponses = 0; + int invalidNodes = 0; + for (int i = 0; i < batch.RequestedNodes!.Length; i++) + { + StateSyncItem currentStateSyncItem = batch.RequestedNodes[i]; + + /* if the peer has limit on number of requests in a batch then the response will possibly be + shorter than the request */ + if (batch.Responses.Length < i + 1) + { + AddNodeToPending(currentStateSyncItem, null, "missing", true); + continue; + } + + /* if the peer does not have details of this particular node */ + byte[] currentResponseItem = batch.Responses[i]; + if (currentResponseItem == null) + { + AddNodeToPending(batch.RequestedNodes[i], null, "missing", true); + continue; + } + + /* node sent data that is not consistent with its hash - it happens surprisingly often */ + if (!ValueKeccak.Compute(currentResponseItem).BytesAsSpan.SequenceEqual(currentStateSyncItem.Hash.Bytes)) + { + AddNodeToPending(currentStateSyncItem, null, "missing", true); + if (_logger.IsTrace) _logger.Trace($"Peer sent invalid data (batch {requestLength}->{responseLength}) of length {batch.Responses[i]?.Length} of type {batch.RequestedNodes[i].NodeDataType} at level {batch.RequestedNodes[i].Level} of type {batch.RequestedNodes[i].NodeDataType} Keccak({batch.Responses[i].ToHexString()}) != {batch.RequestedNodes[i].Hash}"); + invalidNodes++; + continue; + } + + nonEmptyResponses++; + NodeDataType nodeDataType = currentStateSyncItem.NodeDataType; + if (nodeDataType == NodeDataType.Code) + { + SaveNode(currentStateSyncItem, currentResponseItem); + continue; + } + + HandleTrieNode(currentStateSyncItem, currentResponseItem, ref invalidNodes); + } + + Interlocked.Add(ref _data.ConsumedNodesCount, nonEmptyResponses); + StoreProgressInDb(); + + if (_logger.IsTrace) _logger.Trace($"After handling response (non-empty responses {nonEmptyResponses}) of {batch.RequestedNodes.Length} from ({_pendingItems.Description}) nodes"); + + /* magic formula is ratio of our desired batch size - 1024 to Geth max batch size 384 times some missing nodes ratio */ + bool isEmptish = (decimal)nonEmptyResponses / Math.Max(requestLength, 1) < 384m / 1024m * 0.75m; + if (isEmptish) + { + Interlocked.Increment(ref _hintsToResetRoot); + Interlocked.Increment(ref _data.EmptishCount); + } + else + { + Interlocked.Exchange(ref _hintsToResetRoot, 0); + } + + /* here we are very forgiving for Geth nodes that send bad data fast */ + bool isBadQuality = nonEmptyResponses > 64 && (decimal)invalidNodes / Math.Max(requestLength, 1) > 0.50m; + if (isBadQuality) Interlocked.Increment(ref _data.BadQualityCount); + + bool isEmpty = nonEmptyResponses == 0 && !isBadQuality; + if (isEmpty) + { + if (_logger.IsDebug) _logger.Debug($"Peer sent no data in response to a request of length {batch.RequestedNodes.Length}"); + return SyncResponseHandlingResult.NoProgress; + } + + if (!isEmptish && !isBadQuality) + { + Interlocked.Increment(ref _data.OkCount); + } + + SyncResponseHandlingResult result = isEmptish + ? SyncResponseHandlingResult.Emptish + : isBadQuality + ? SyncResponseHandlingResult.LesserQuality + : SyncResponseHandlingResult.OK; + + _data.DisplayProgressReport(_pendingRequests.Count, _branchProgress, _logger); + + long total = _handleWatch.ElapsedMilliseconds + _networkWatch.ElapsedMilliseconds; + if (total != 0) + { + // calculate averages + if (_logger.IsTrace) _logger.Trace($"Prepare batch {_networkWatch.ElapsedMilliseconds}ms ({(decimal)_networkWatch.ElapsedMilliseconds / total:P0}) - Handle {_handleWatch.ElapsedMilliseconds}ms ({(decimal)_handleWatch.ElapsedMilliseconds / total:P0})"); + } + + if (_handleWatch.ElapsedMilliseconds > 250) + { + if (_logger.IsDebug) _logger.Debug($"Handle watch {_handleWatch.ElapsedMilliseconds}, DB reads {_data.DbChecks - _data.LastDbReads}, ratio {(decimal)_handleWatch.ElapsedMilliseconds / Math.Max(1, _data.DbChecks - _data.LastDbReads)}"); + } + + _data.LastDbReads = _data.DbChecks; + _data.AverageTimeInHandler = (_data.AverageTimeInHandler * (_data.ProcessedRequestsCount - 1) + _handleWatch.ElapsedMilliseconds) / _data.ProcessedRequestsCount; + Interlocked.Add(ref _data.HandledNodesCount, nonEmptyResponses); + return result; + } + } + catch (Exception e) + { + _logger.Error("Error when handling state sync response", e); + return SyncResponseHandlingResult.InternalError; + } + finally + { + _handleWatch.Stop(); + } + } + + public (bool continueProcessing, bool finishSyncRound) ValidatePrepareRequest(SyncMode currentSyncMode) + { + if (_rootSaved == 1) + { + VerifyPostSyncCleanUp(); + return (false, true); + } + + if ((currentSyncMode & _syncMode) != _syncMode) + { + return (false, false); + } + + if (_rootNode == Keccak.EmptyTreeHash) + { + if (_logger.IsDebug) _logger.Debug("Falling asleep - root is empty tree"); + return (false, true); + } + + if (_hintsToResetRoot >= 32) + { + if (_logger.IsDebug) _logger.Debug("Falling asleep - many missing responses"); + return (false, true); + } + + bool rootNodeKeyExists; + lock (_stateDbLock) + { + try + { + // it finished downloading + rootNodeKeyExists = _stateDb.KeyExists(_rootNode); + } + catch (ObjectDisposedException) + { + return (false, false); + } + } + + if (rootNodeKeyExists) + { + try + { + _logger.Info($"STATE SYNC FINISHED:{Metrics.StateSyncRequests}, {Metrics.SyncedStateTrieNodes}"); + + VerifyPostSyncCleanUp(); + return (false, true); + } + catch (ObjectDisposedException) + { + return (false, false); + } + } + + return (true, false); + } + + public void ResetStateRoot(SyncFeedState currentState) + { + ResetStateRoot(_blockNumber, _rootNode, currentState); + } + + public void ResetStateRootToBestSuggested(SyncFeedState currentState) + { + BlockHeader bestSuggested = _blockTree.BestSuggestedHeader; + if (bestSuggested == null || bestSuggested.Number == 0) + { + return; + } + + if (_logger.IsInfo) _logger.Info($"Starting the node data sync from the {bestSuggested.ToString(BlockHeader.Format.Short)} {bestSuggested.StateRoot} root"); + + ResetStateRoot(bestSuggested.Number, bestSuggested.StateRoot!, currentState); + } + + public void ResetStateRoot(long blockNumber, Keccak stateRoot, SyncFeedState currentState) + { + if (currentState != SyncFeedState.Dormant) + { + throw new InvalidOperationException("Cannot reset state sync on an active feed"); + } + + Interlocked.Exchange(ref _hintsToResetRoot, 0); + + if (_logger.IsInfo) _logger.Info($"Setting state sync state root to {blockNumber} {stateRoot}"); + _currentSyncStart = DateTime.UtcNow; + _currentSyncStartSecondsInSync = _data.SecondsInSync; + + _data.LastReportTime = (DateTime.UtcNow, DateTime.UtcNow); + _data.LastSavedNodesCount = _data.SavedNodesCount; + _data.LastRequestedNodesCount = _data.RequestedNodesCount; + if (_rootNode != stateRoot) + { + _branchProgress = new BranchProgress(blockNumber, _logger); + _blockNumber = blockNumber; + _rootNode = stateRoot; + lock (_dependencies) _dependencies.Clear(); + lock (_codesSameAsNodes) _codesSameAsNodes.Clear(); + + if (_logger.IsDebug) _logger.Debug($"Clearing node stacks ({_pendingItems.Description})"); + _pendingItems.Clear(); + Interlocked.Exchange(ref _rootSaved, 0); + } + else + { + foreach ((StateSyncBatch pendingRequest, _) in _pendingRequests) + { + // re-add the pending request + for (int i = 0; i < pendingRequest.RequestedNodes.Length; i++) + { + AddNodeToPending(pendingRequest.RequestedNodes[i], null, "pending request", true); + } + } + } + + _pendingRequests.Clear(); + + bool hasOnlyRootNode = false; + + if (_rootNode != Keccak.EmptyTreeHash) + { + if (_pendingItems.Count == 1) + { + // state root can only be located on state stream + StateSyncItem? potentialRoot = _pendingItems.PeekState(); + if (potentialRoot?.Hash == _rootNode) + { + hasOnlyRootNode = true; + } + } + + if (!hasOnlyRootNode) + { + AddNodeToPending(new StateSyncItem(_rootNode, null, null, NodeDataType.State), null, "initial"); + } + } + } + + public DetailedProgress GetDetailedProgress() + { + return _data; + } + + private AddNodeResult AddNodeToPending(StateSyncItem syncItem, DependentItem? dependentItem, string reason, bool missing = false) + { + if (!missing) + { + if (syncItem.Level <= 2) + { + _branchProgress.ReportSynced(syncItem, NodeProgressState.Requested); + } + + if (_alreadySaved.Get(syncItem.Hash)) + { + Interlocked.Increment(ref _data.CheckWasCached); + if (_logger.IsTrace) _logger.Trace($"Node already in the DB - skipping {syncItem.Hash}"); + _branchProgress.ReportSynced(syncItem, NodeProgressState.AlreadySaved); + return AddNodeResult.AlreadySaved; + } + + object lockToTake = syncItem.NodeDataType == NodeDataType.Code ? _codeDbLock : _stateDbLock; + lock (lockToTake) + { + IDb dbToCheck = syncItem.NodeDataType == NodeDataType.Code ? _codeDb : _stateDb; + Interlocked.Increment(ref _data.DbChecks); + bool keyExists = dbToCheck.KeyExists(syncItem.Hash); + + if (keyExists) + { + if (_logger.IsTrace) _logger.Trace($"Node already in the DB - skipping {syncItem.Hash}"); + _alreadySaved.Set(syncItem.Hash); + Interlocked.Increment(ref _data.StateWasThere); + _branchProgress.ReportSynced(syncItem, NodeProgressState.AlreadySaved); + return AddNodeResult.AlreadySaved; + } + + Interlocked.Increment(ref _data.StateWasNotThere); + } + + bool isAlreadyRequested; + lock (_dependencies) + { + isAlreadyRequested = _dependencies.ContainsKey(syncItem.Hash); + if (dependentItem != null) + { + if (_logger.IsTrace) _logger.Trace($"Adding dependency {syncItem.Hash} -> {dependentItem.SyncItem.Hash}"); + AddDependency(syncItem.Hash, dependentItem); + } + } + + /* same items can have same hashes and we only need them once + * there is an issue when we have an item, we add it to dependencies, then we request it and the request times out + * and we never request it again because it was already on the dependencies list */ + if (isAlreadyRequested) + { + Interlocked.Increment(ref _data.CheckWasInDependencies); + if (_logger.IsTrace) _logger.Trace($"Node already requested - skipping {syncItem.Hash}"); + return AddNodeResult.AlreadyRequested; + } + } + + _pendingItems.PushToSelectedStream(syncItem, _branchProgress.LastProgress); + if (_logger.IsTrace) _logger.Trace($"Added a node {syncItem.Hash} - {reason}"); + return AddNodeResult.Added; + } + + private void PossiblySaveDependentNodes(Keccak hash) + { + List nodesToSave = new(); + lock (_dependencies) + { + if (_dependencies.ContainsKey(hash)) + { + HashSet dependentItems = _dependencies[hash]; + + if (_logger.IsTrace) + { + string nodeNodes = dependentItems.Count == 1 ? "node" : "nodes"; + _logger.Trace($"{dependentItems.Count} {nodeNodes} dependent on {hash}"); + } + + foreach (DependentItem dependentItem in dependentItems) + { + dependentItem.Counter--; + + if (dependentItem.Counter == 0) + { + nodesToSave.Add(dependentItem); + } + } + + _dependencies.Remove(hash); + } + else + { + if (_logger.IsTrace) _logger.Trace($"No nodes dependent on {hash}"); + } + } + + foreach (DependentItem dependentItem in nodesToSave) + { + if (dependentItem.IsAccount) Interlocked.Increment(ref _data.SavedAccounts); + SaveNode(dependentItem.SyncItem, dependentItem.Value); + } + } + + private void SaveNode(StateSyncItem syncItem, byte[] data) + { + if (_logger.IsTrace) _logger.Trace($"SAVE {new string('+', syncItem.Level * 2)}{syncItem.NodeDataType.ToString().ToUpperInvariant()} {syncItem.Hash}"); + Interlocked.Increment(ref _data.SavedNodesCount); + switch (syncItem.NodeDataType) + { + case NodeDataType.State: + { + Interlocked.Increment(ref _data.SavedStateCount); + lock (_stateDbLock) + { + Interlocked.Add(ref _data.DataSize, data.Length); + Interlocked.Increment(ref Metrics.SyncedStateTrieNodes); + _stateDb.Set(syncItem.Hash, data); + } + + break; + } + case NodeDataType.Storage: + { + lock (_codesSameAsNodes) + { + if (_codesSameAsNodes.Contains(syncItem.Hash)) + { + lock (_codeDbLock) + { + Interlocked.Add(ref _data.DataSize, data.Length); + Interlocked.Increment(ref Metrics.SyncedCodes); + _codeDb.Set(syncItem.Hash, data); + } + + _codesSameAsNodes.Remove(syncItem.Hash); + } + } + + Interlocked.Increment(ref _data.SavedStorageCount); + lock (_stateDbLock) + { + Interlocked.Add(ref _data.DataSize, data.Length); + Interlocked.Increment(ref Metrics.SyncedStorageTrieNodes); + _stateDb.Set(syncItem.Hash, data); + } + + break; + } + case NodeDataType.Code: + { + Interlocked.Increment(ref _data.SavedCode); + lock (_codeDbLock) + { + Interlocked.Add(ref _data.DataSize, data.Length); + Interlocked.Increment(ref Metrics.SyncedCodes); + _codeDb.Set(syncItem.Hash, data); + } + + break; + } + } + + if (syncItem.IsRoot) + { + if (_logger.IsInfo) _logger.Info($"Saving root {syncItem.Hash} of {_branchProgress.CurrentSyncBlock}"); + + Interlocked.Exchange(ref _rootSaved, 1); + } + + _branchProgress.ReportSynced(syncItem.Level, syncItem.ParentBranchChildIndex, syncItem.BranchChildIndex, syncItem.NodeDataType, NodeProgressState.Saved); + PossiblySaveDependentNodes(syncItem.Hash); + } + + private void VerifyPostSyncCleanUp() + { + lock (_dependencies) + { + if (_dependencies.Count != 0) + { + if (_logger.IsError) _logger.Error($"POSSIBLE FAST SYNC CORRUPTION | Dependencies hanging after the root node saved - count: {_dependencies.Count}, first: {_dependencies.Keys.First()}"); + } + + _dependencies = new Dictionary>(); + // _alreadySaved = new LruKeyCache(AlreadySavedCapacity, "saved nodes"); + } + + if (_pendingItems.Count != 0) + { + if (_logger.IsError) _logger.Error($"POSSIBLE FAST SYNC CORRUPTION | Nodes left after the root node saved - count: {_pendingItems.Count}"); + } + } + + private void StoreProgressInDb() + { + byte[] serializedData = _data.Serialize(); + lock (_stateDbLock) + { + lock (_codeDbLock) + { + _codeDb[_fastSyncProgressKey.Bytes] = serializedData; + } + } + } + + private void HandleTrieNode(StateSyncItem currentStateSyncItem, byte[] currentResponseItem, ref int invalidNodes) + { + NodeDataType nodeDataType = currentStateSyncItem.NodeDataType; + TrieNode trieNode = new(NodeType.Unknown, currentResponseItem); + trieNode.ResolveNode(NullTrieNodeResolver.Instance); // TODO: will this work now? + switch (trieNode.NodeType) + { + case NodeType.Unknown: + invalidNodes++; + if (_logger.IsError) _logger.Error($"Node {currentStateSyncItem.Hash} resolved to {nameof(NodeType.Unknown)}"); + break; + case NodeType.Branch: + DependentItem dependentBranch = new(currentStateSyncItem, currentResponseItem, 0); + + // children may have the same hashes (e.g. a set of accounts with the same code at different addresses) + HashSet alreadyProcessedChildHashes = new(); + + Span branchChildPath = stackalloc byte[currentStateSyncItem.PathNibbles.Length + 1]; + currentStateSyncItem.PathNibbles.CopyTo(branchChildPath.Slice(0, currentStateSyncItem.PathNibbles.Length)); + + for (int childIndex = 15; childIndex >= 0; childIndex--) + { + Keccak? childHash = trieNode.GetChildHash(childIndex); + if (childHash != null && + alreadyProcessedChildHashes.Contains(childHash)) + { + continue; + } + + alreadyProcessedChildHashes.Add(childHash); + + if (childHash != null) + { + branchChildPath[currentStateSyncItem.PathNibbles.Length] = (byte)childIndex; + + AddNodeResult addChildResult = AddNodeToPending(new StateSyncItem(childHash, currentStateSyncItem.AccountPathNibbles, branchChildPath.ToArray(), nodeDataType, currentStateSyncItem.Level + 1, CalculateRightness(trieNode.NodeType, currentStateSyncItem, childIndex)) { BranchChildIndex = (short)childIndex, ParentBranchChildIndex = currentStateSyncItem.BranchChildIndex }, dependentBranch, "branch child"); + if (addChildResult != AddNodeResult.AlreadySaved) + { + dependentBranch.Counter++; + } + else + { + _branchProgress.ReportSynced(currentStateSyncItem.Level + 1, currentStateSyncItem.BranchChildIndex, childIndex, currentStateSyncItem.NodeDataType, NodeProgressState.AlreadySaved); + } + } + else + { + _branchProgress.ReportSynced(currentStateSyncItem.Level + 1, currentStateSyncItem.BranchChildIndex, childIndex, currentStateSyncItem.NodeDataType, NodeProgressState.Empty); + } + } + + if (dependentBranch.Counter == 0) + { + SaveNode(currentStateSyncItem, currentResponseItem); + } + + break; + case NodeType.Extension: + Keccak? next = trieNode.GetChild(NullTrieNodeResolver.Instance, 0)?.Keccak; + if (next != null) + { + DependentItem dependentItem = new(currentStateSyncItem, currentResponseItem, 1); + + Span childPath = stackalloc byte[currentStateSyncItem.PathNibbles.Length + trieNode.Path!.Length]; + currentStateSyncItem.PathNibbles.CopyTo(childPath.Slice(0, currentStateSyncItem.PathNibbles.Length)); + trieNode.Path!.CopyTo(childPath.Slice(currentStateSyncItem.PathNibbles.Length)); + + AddNodeResult addResult = AddNodeToPending( + new StateSyncItem( + next, + currentStateSyncItem.AccountPathNibbles, + childPath.ToArray(), + nodeDataType, + currentStateSyncItem.Level + trieNode.Path!.Length, + CalculateRightness(trieNode.NodeType, currentStateSyncItem, 0)) + { ParentBranchChildIndex = currentStateSyncItem.BranchChildIndex }, + dependentItem, + "extension child"); + + if (addResult == AddNodeResult.AlreadySaved) + { + SaveNode(currentStateSyncItem, currentResponseItem); + } + } + else + { + /* this happens when we have a short RLP format of the node + * that would not be stored as Keccak but full RLP*/ + SaveNode(currentStateSyncItem, currentResponseItem); + } + + break; + case NodeType.Leaf: + if (nodeDataType == NodeDataType.State) + { + _pendingItems.MaxStateLevel = 64; + DependentItem dependentItem = new(currentStateSyncItem, currentResponseItem, 0, true); + (Keccak codeHash, Keccak storageRoot) = AccountDecoder.DecodeHashesOnly(new RlpStream(trieNode.Value)); + if (codeHash != Keccak.OfAnEmptyString) + { + // prepare a branch without the code DB + // this only protects against being same as storage root? + if (codeHash == storageRoot) + { + lock (_codesSameAsNodes) + { + _codesSameAsNodes.Add(codeHash); + } + } + else + { + AddNodeResult addCodeResult = AddNodeToPending(new StateSyncItem(codeHash, null, null, NodeDataType.Code, 0, currentStateSyncItem.Rightness), dependentItem, "code"); + if (addCodeResult != AddNodeResult.AlreadySaved) dependentItem.Counter++; + } + } + + if (storageRoot != Keccak.EmptyTreeHash) + { + AddNodeResult addStorageNodeResult = AddNodeToPending(new StateSyncItem(storageRoot, currentStateSyncItem.PathNibbles, null, NodeDataType.Storage, 0, currentStateSyncItem.Rightness), dependentItem, "storage"); + if (addStorageNodeResult != AddNodeResult.AlreadySaved) dependentItem.Counter++; + } + + if (dependentItem.Counter == 0) + { + Interlocked.Increment(ref _data.SavedAccounts); + SaveNode(currentStateSyncItem, currentResponseItem); + } + } + else + { + _pendingItems.MaxStorageLevel = 64; + SaveNode(currentStateSyncItem, currentResponseItem); + } + + break; + default: + if (_logger.IsError) _logger.Error($"Unknown value {currentStateSyncItem.NodeDataType} of {nameof(NodeDataType)} at {currentStateSyncItem.Hash}"); + invalidNodes++; + break; + } + } + + private static uint CalculateRightness(NodeType nodeType, StateSyncItem currentStateSyncItem, int childIndex) + { + if (nodeType == NodeType.Branch) + { + return currentStateSyncItem.Rightness + (uint)Math.Pow(16, Math.Max(0, 7 - currentStateSyncItem.Level)) * (uint)childIndex; + } + + if (nodeType == NodeType.Extension) + { + return currentStateSyncItem.Rightness + (uint)Math.Pow(16, Math.Max(0, 7 - currentStateSyncItem.Level)) * 16 - 1; + } + + throw new InvalidOperationException($"Not designed for {nodeType}"); + } + + /// + /// Stores items that cannot be yet persisted. These items will be persisted as soon as all their descendants + /// get persisted. + /// + /// Sync item that this item is dependent on. + /// Item that can only be persisted if all its dependenies are persisted + private void AddDependency(Keccak dependency, DependentItem dependentItem) + { + lock (_dependencies) + { + if (!_dependencies.ContainsKey(dependency)) + { + _dependencies[dependency] = new HashSet(DependentItemComparer.Instance); + } + + _dependencies[dependency].Add(dependentItem); + } + } + + private void LogRequestInfo(List requestHashes) + { + int requestSize = requestHashes.Count; + if (requestSize < MaxRequestSize) + { + if (_logger.IsDebug) _logger.Debug($"Sending limited size request {requestSize} at level {_pendingItems.MaxStateLevel}"); + } + + if (_logger.IsTrace) _logger.Trace($"Preparing a request of length {requestSize} from ({_pendingItems.Description}) nodes"); + if (_logger.IsTrace) + { + foreach (StateSyncItem stateSyncItem in requestHashes) + { + _logger.Trace($"Requesting {stateSyncItem.Hash}"); + } + } + } + + private enum AddNodeResult + { + AlreadySaved, + AlreadyRequested, + Added + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/Metrics.cs b/src/Nethermind/Nethermind.Synchronization/Metrics.cs index 5d6d40219f7..a7899d55cf0 100644 --- a/src/Nethermind/Nethermind.Synchronization/Metrics.cs +++ b/src/Nethermind/Nethermind.Synchronization/Metrics.cs @@ -43,9 +43,24 @@ public static class Metrics [Description("Synced bytecodes")] public static long SyncedCodes; + + [Description("State synced via SNAP Sync in bytes")] + public static long SnapStateSynced; + + [Description("Synced accounts via SNAP Sync")] + public static long SnapSyncedAccounts; + + [Description("Synced storage slots via SNAP Sync")] + public static long SnapSyncedStorageSlots; + + [Description("Synced bytecodes via SNAP Sync")] + public static long SnapSyncedCodes; [Description("Number of sync peers.")] public static long SyncPeers; + + [Description("Number of priority peers.")] + public static long PriorityPeers; [Description("State branch progress (percentage of completed branches at second level).")] public static long StateBranchProgress; diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncFeed.cs index 7172097da34..0661e785aad 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncFeed.cs @@ -26,7 +26,7 @@ public interface ISyncFeed SyncFeedState CurrentState { get; } event EventHandler StateChanged; Task PrepareRequest(); - SyncResponseHandlingResult HandleResponse(T response); + SyncResponseHandlingResult HandleResponse(T response, PeerInfo peer = null); /// /// Multifeed can prepare and handle multiple requests concurrently. diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncProgressResolver.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncProgressResolver.cs index 0aa046bb32f..815315fdae3 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncProgressResolver.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/ISyncProgressResolver.cs @@ -40,7 +40,10 @@ public interface ISyncProgressResolver bool IsLoadingBlocksFromDb(); long FindBestProcessedBlock(); - + + bool IsSnapGetRangesFinished(); + + UInt256 ChainDifficulty { get; } UInt256? GetTotalDifficulty(Keccak blockHash); diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs index 894849d5e18..e1620191110 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs @@ -22,6 +22,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.State.Snap; using Nethermind.Synchronization.Peers; namespace Nethermind.Synchronization.ParallelSync @@ -48,6 +49,7 @@ public class MultiSyncModeSelector : ISyncModeSelector, IDisposable private long PivotNumber; private bool FastSyncEnabled => _syncConfig.FastSync; + private bool SnapSyncEnabled => FastSyncEnabled && _syncConfig.SnapSync; private bool FastBlocksEnabled => _syncConfig.FastSync && _syncConfig.FastBlocks; private bool FastBodiesEnabled => FastBlocksEnabled && _syncConfig.DownloadBodiesInFastSync; private bool FastReceiptsEnabled => FastBlocksEnabled && _syncConfig.DownloadReceiptsInFastSync; @@ -63,7 +65,7 @@ public class MultiSyncModeSelector : ISyncModeSelector, IDisposable private long FastSyncCatchUpHeightDelta => _syncConfig.FastSyncCatchUpHeightDelta ?? FastSyncLag; private bool NotNeedToWaitForHeaders => !_needToWaitForHeaders || FastBlocksHeadersFinished; - + internal long? LastBlockThatEnabledFullSync { get; set; } private Timer _timer; @@ -90,7 +92,7 @@ public MultiSyncModeSelector( _betterPeerStrategy = betterPeerStrategy ?? throw new ArgumentNullException(nameof(betterPeerStrategy)); _syncProgressResolver = syncProgressResolver ?? throw new ArgumentNullException(nameof(syncProgressResolver)); _needToWaitForHeaders = needToWaitForHeaders; - + if (syncConfig.FastSyncCatchUpHeightDelta <= FastSyncLag) { if (_logger.IsWarn) @@ -168,20 +170,25 @@ public void Update() try { best.IsInFastSync = ShouldBeInFastSyncMode(best); - best.IsInStateSync = ShouldBeInStateNodesMode(best); + best.IsInStateSync = ShouldBeInStateSyncMode(best); best.IsInFullSync = ShouldBeInFullSyncMode(best); best.IsInFastHeaders = ShouldBeInFastHeadersMode(best); best.IsInFastBodies = ShouldBeInFastBodiesMode(best); best.IsInFastReceipts = ShouldBeInFastReceiptsMode(best); best.IsInDisconnected = ShouldBeInDisconnectedMode(best); best.IsInWaitingForBlock = ShouldBeInWaitingForBlockMode(best); + bool canBeInSnapRangesPhase = CanBeInSnapRangesPhase(best); + newModes = SyncMode.None; CheckAddFlag(best.IsInFastHeaders, SyncMode.FastHeaders, ref newModes); CheckAddFlag(best.IsInFastBodies, SyncMode.FastBodies, ref newModes); CheckAddFlag(best.IsInFastReceipts, SyncMode.FastReceipts, ref newModes); CheckAddFlag(best.IsInFastSync, SyncMode.FastSync, ref newModes); CheckAddFlag(best.IsInFullSync, SyncMode.Full, ref newModes); - CheckAddFlag(best.IsInStateSync, SyncMode.StateNodes, ref newModes); + CheckAddFlag(best.IsInStateSync && !canBeInSnapRangesPhase, SyncMode.StateNodes, + ref newModes); + CheckAddFlag(best.IsInStateSync && canBeInSnapRangesPhase, SyncMode.SnapSync, + ref newModes); CheckAddFlag(best.IsInDisconnected, SyncMode.Disconnected, ref newModes); CheckAddFlag(best.IsInWaitingForBlock, SyncMode.WaitingForBlock, ref newModes); if (IsTheModeSwitchWorthMentioning(newModes)) @@ -197,7 +204,6 @@ public void Update() reason = "Snapshot Misalignment"; } } - if ((newModes & (SyncMode.Full | SyncMode.WaitingForBlock)) != SyncMode.None @@ -359,7 +365,7 @@ private bool ShouldBeInFullSyncMode(Snapshot best) bool notInFastSync = !best.IsInFastSync; bool notInStateSync = !best.IsInStateSync; bool notNeedToWaitForHeaders = NotNeedToWaitForHeaders; - + bool result = desiredPeerKnown && postPivotPeerAvailable && hasFastSyncBeenActive && @@ -430,7 +436,7 @@ private bool ShouldBeInFastReceiptsMode(Snapshot best) // fast blocks receipts can run if there are peers until it is done // fast blocks receipts can run in parallel with full sync when bodies are finished bool result = fastReceiptsNotFinished && fastBodiesFinished && notInStateSync && stateSyncFinished; - + if (_logger.IsTrace) { LogDetailedSyncModeChecks("RECEIPTS", @@ -457,7 +463,7 @@ private bool ShouldBeInDisconnectedMode(Snapshot best) best.PeerDifficulty == UInt256.Zero; } - private bool ShouldBeInStateNodesMode(Snapshot best) + private bool ShouldBeInStateSyncMode(Snapshot best) { bool fastSyncEnabled = FastSyncEnabled; bool hasFastSyncBeenActive = best.Header >= PivotNumber; @@ -470,15 +476,16 @@ private bool ShouldBeInStateNodesMode(Snapshot best) bool notInAStickyFullSync = !IsInAStickyFullSyncMode(best); bool notHasJustStartedFullSync = !HasJustStartedFullSync(best); + bool result = fastSyncEnabled && hasFastSyncBeenActive && hasAnyPostPivotPeer && (notInFastSync || stickyStateNodes) && stateNotDownloadedYet && notHasJustStartedFullSync && - notInAStickyFullSync && + notInAStickyFullSync && notNeedToWaitForHeaders; - + if (_logger.IsTrace) { LogDetailedSyncModeChecks("STATE", @@ -495,6 +502,24 @@ private bool ShouldBeInStateNodesMode(Snapshot best) return result; } + private bool CanBeInSnapRangesPhase(Snapshot best) + { + bool isCloseToHead = best.PeerBlock >= best.Header && (best.PeerBlock - best.Header) < Constants.MaxDistanceFromHead; + bool snapNotFinished = !_syncProgressResolver.IsSnapGetRangesFinished(); + + if (_logger.IsTrace) + { + LogDetailedSyncModeChecks("SNAP_RANGES", + (nameof(SnapSyncEnabled), SnapSyncEnabled), + (nameof(isCloseToHead), isCloseToHead), + (nameof(snapNotFinished), snapNotFinished)); + } + + return SnapSyncEnabled + && isCloseToHead + && snapNotFinished; + } + private bool HasJustStartedFullSync(Snapshot best) => best.State > PivotNumber // we have saved some root && (best.State == best.Header || diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs index 94be14fa6d3..ed054cd1ded 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs @@ -27,7 +27,7 @@ public abstract class SyncDispatcher private object _feedStateManipulation = new(); private SyncFeedState _currentFeedState = SyncFeedState.Dormant; - private IPeerAllocationStrategyFactory PeerAllocationStrategy { get; } + private IPeerAllocationStrategyFactory PeerAllocationStrategyFactory { get; } protected ILogger Logger { get; } protected ISyncFeed Feed { get; } @@ -42,7 +42,7 @@ protected SyncDispatcher( Logger = logManager?.GetClassLogger>() ?? throw new ArgumentNullException(nameof(logManager)); Feed = syncFeed ?? throw new ArgumentNullException(nameof(syncFeed)); SyncPeerPool = syncPeerPool ?? throw new ArgumentNullException(nameof(syncPeerPool)); - PeerAllocationStrategy = peerAllocationStrategy ?? throw new ArgumentNullException(nameof(peerAllocationStrategy)); + PeerAllocationStrategyFactory = peerAllocationStrategy ?? throw new ArgumentNullException(nameof(peerAllocationStrategy)); syncFeed.StateChanged += SyncFeedOnStateChanged; } @@ -86,14 +86,14 @@ public async Task Start(CancellationToken cancellationToken) if (currentStateLocal == SyncFeedState.Dormant) { - if(Logger.IsDebug) Logger.Debug($"{GetType().Name} is going to sleep."); + if (Logger.IsDebug) Logger.Debug($"{GetType().Name} is going to sleep."); if (dormantTaskLocal == null) { if (Logger.IsWarn) Logger.Warn("Dormant task is NULL when trying to await it"); } await (dormantTaskLocal?.Task ?? Task.CompletedTask); - if(Logger.IsDebug) Logger.Debug($"{GetType().Name} got activated."); + if (Logger.IsDebug) Logger.Debug($"{GetType().Name} got activated."); } else if (currentStateLocal == SyncFeedState.Active) { @@ -102,7 +102,7 @@ public async Task Start(CancellationToken cancellationToken) { if (!Feed.IsMultiFeed) { - if(Logger.IsTrace) Logger.Trace($"{Feed.GetType().Name} enqueued a null request."); + if (Logger.IsTrace) Logger.Trace($"{Feed.GetType().Name} enqueued a null request."); } await Task.Delay(10, cancellationToken); @@ -115,7 +115,8 @@ public async Task Start(CancellationToken cancellationToken) if (allocatedPeer != null) { if (Logger.IsTrace) Logger.Trace($"SyncDispatcher request: {request}, AllocatedPeer {allocation.Current}"); - Task task = Dispatch(allocatedPeer, request, cancellationToken).ContinueWith(t => + Task task = Dispatch(allocatedPeer, request, cancellationToken) + .ContinueWith(t => { if (t.IsFaulted) { @@ -124,7 +125,7 @@ public async Task Start(CancellationToken cancellationToken) try { - SyncResponseHandlingResult result = Feed.HandleResponse(request); + SyncResponseHandlingResult result = Feed.HandleResponse(request, allocatedPeer); ReactToHandlingResult(request, result, allocatedPeer); } catch (ObjectDisposedException) @@ -142,23 +143,24 @@ public async Task Start(CancellationToken cancellationToken) Free(allocation); } }, cancellationToken); - + if (!Feed.IsMultiFeed) { - if(Logger.IsDebug) Logger.Debug($"Awaiting single dispatch from {Feed.GetType().Name} with allocated {allocatedPeer}"); + if (Logger.IsDebug) Logger.Debug($"Awaiting single dispatch from {Feed.GetType().Name} with allocated {allocatedPeer}"); await task; - if(Logger.IsDebug) Logger.Debug($"Single dispatch from {Feed.GetType().Name} with allocated {allocatedPeer} has been processed"); + if (Logger.IsDebug) Logger.Debug($"Single dispatch from {Feed.GetType().Name} with allocated {allocatedPeer} has been processed"); } } else { + Logger.Debug($"DISPATCHER - {this.GetType().Name}: peer NOT allocated"); SyncResponseHandlingResult result = Feed.HandleResponse(request); ReactToHandlingResult(request, result, null); } } else if (currentStateLocal == SyncFeedState.Finished) { - if(Logger.IsInfo) Logger.Info($"{GetType().Name} has finished work."); + if (Logger.IsInfo) Logger.Info($"{GetType().Name} has finished work."); break; } } @@ -171,7 +173,7 @@ protected virtual void Free(SyncPeerAllocation allocation) protected virtual async Task Allocate(T request) { - SyncPeerAllocation allocation = await SyncPeerPool.Allocate(PeerAllocationStrategy.Create(request), Feed.Contexts, 1000); + SyncPeerAllocation allocation = await SyncPeerPool.Allocate(PeerAllocationStrategyFactory.Create(request), Feed.Contexts, 1000); return allocation; } diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncFeed.cs index c3cf4884bc6..a9b4b29c1b6 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncFeed.cs @@ -23,7 +23,7 @@ namespace Nethermind.Synchronization.ParallelSync public abstract class SyncFeed : ISyncFeed { public abstract Task PrepareRequest(); - public abstract SyncResponseHandlingResult HandleResponse(T response); + public abstract SyncResponseHandlingResult HandleResponse(T response, PeerInfo peer = null); public abstract bool IsMultiFeed { get; } public abstract AllocationContexts Contexts { get; } @@ -47,7 +47,7 @@ public void Activate() ChangeState(SyncFeedState.Active); } - protected void Finish() + protected virtual void Finish() { if (CurrentState == SyncFeedState.Finished) { diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncMode.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncMode.cs index 11c70f7c807..e0ac4a4879a 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncMode.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncMode.cs @@ -63,6 +63,10 @@ public enum SyncMode /// Stage of fast sync that downloads headers in parallel. /// FastReceipts = FastBlocks | 1024, + /// + /// Stage of snap sync that state is being downloaded (accounts, storages, code, proofs) + /// + SnapSync = 2048, /// /// Everything is in a beacon node control. There is no need to do any sync action @@ -80,7 +84,7 @@ public enum SyncMode BeaconFullSync = 8192, All = WaitingForBlock | Disconnected | FastBlocks | FastSync | StateNodes | StateNodes | Full | DbLoad | - FastHeaders | FastBodies | FastReceipts | BeaconControlMode | BeaconHeaders + FastHeaders | FastBodies | FastReceipts | SnapSync | BeaconControlMode | BeaconHeaders } public static class SyncModeExtensions diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs index 5840ae139f2..2d8f09acf7b 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs @@ -23,6 +23,8 @@ using Nethermind.Db; using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.State.Snap; +using Nethermind.Synchronization.SnapSync; using Nethermind.Trie; using Nethermind.Trie.Pruning; @@ -39,6 +41,7 @@ public class SyncProgressResolver : ISyncProgressResolver private readonly IReceiptStorage _receiptStorage; private readonly IDb _stateDb; private readonly ITrieNodeResolver _trieNodeResolver; + private readonly ProgressTracker _progressTracker; private readonly ISyncConfig _syncConfig; // ReSharper disable once NotAccessedField.Local @@ -51,6 +54,7 @@ public SyncProgressResolver(IBlockTree blockTree, IReceiptStorage receiptStorage, IDb stateDb, ITrieNodeResolver trieNodeResolver, + ProgressTracker progressTracker, ISyncConfig syncConfig, ILogManager logManager) { @@ -59,6 +63,7 @@ public SyncProgressResolver(IBlockTree blockTree, _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); _stateDb = stateDb ?? throw new ArgumentNullException(nameof(stateDb)); _trieNodeResolver = trieNodeResolver ?? throw new ArgumentNullException(nameof(trieNodeResolver)); + _progressTracker = progressTracker ?? throw new ArgumentNullException(nameof(progressTracker)); _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); _bodiesBarrier = _syncConfig.AncientBodiesBarrierCalc; @@ -187,6 +192,8 @@ public bool IsFastBlocksReceiptsFinished() => !IsFastBlocks() || (!_syncConfig.D .LowestInsertedReceiptBlockNumber ?? long.MaxValue) <= _receiptsBarrier); + public bool IsSnapGetRangesFinished() => _progressTracker.IsSnapGetRangesFinished(); + private bool IsFastBlocks() { bool isFastBlocks = _syncConfig.FastBlocks; diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationContexts.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationContexts.cs index 86f55c597d1..5fe1d84d749 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationContexts.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationContexts.cs @@ -29,6 +29,7 @@ public enum AllocationContexts Blocks = 7, State = 8, Witness = 16, - All = Headers | Bodies | Receipts | Blocks | State | Witness + Snap = 32, + All = Headers | Bodies | Receipts | Blocks | State | Witness | Snap } } diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/SatelliteProtocolPeerAllocationStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/SatelliteProtocolPeerAllocationStrategy.cs index 96c43e48d39..a120cba9353 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/SatelliteProtocolPeerAllocationStrategy.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/SatelliteProtocolPeerAllocationStrategy.cs @@ -34,8 +34,10 @@ public SatelliteProtocolPeerAllocationStrategy(IPeerAllocationStrategy strategy, _strategy = strategy ?? throw new ArgumentNullException(nameof(strategy)); _protocol = protocol; } - - public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) => - _strategy.Allocate(currentPeer, peers.Where(p => p.SyncPeer.TryGetSatelliteProtocol(_protocol, out _)), nodeStatsManager, blockTree); + + public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) + { + return _strategy.Allocate(currentPeer, peers.Where(p => p.SyncPeer.TryGetSatelliteProtocol(_protocol, out _)), nodeStatsManager, blockTree); + } } } diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs b/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs index dff08f3dae0..184eb91cbe7 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/ISyncPeerPool.cs @@ -77,6 +77,12 @@ public interface ISyncPeerPool : IDisposable /// /// void RemovePeer(ISyncPeer syncPeer); + + /// + /// Setting given peer as priority. + /// + /// + void SetPeerPriority(PublicKey id); /// /// It is hard to track total difficulty so occasionally we send a total difficulty request to update node information. diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/PeerInfo.cs b/src/Nethermind/Nethermind.Synchronization/Peers/PeerInfo.cs index 61d2c58f283..f8a56d7275d 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/PeerInfo.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/PeerInfo.cs @@ -167,7 +167,7 @@ private void ResolveWeaknessChecks(ref int weakness, AllocationContexts singleCo private static string BuildContextString(AllocationContexts contexts) { - return $"{((contexts & AllocationContexts.Headers) == AllocationContexts.Headers ? "H" : " ")}{((contexts & AllocationContexts.Bodies) == AllocationContexts.Bodies ? "B" : " ")}{((contexts & AllocationContexts.Receipts) == AllocationContexts.Receipts ? "R" : " ")}{((contexts & AllocationContexts.State) == AllocationContexts.State ? "S" : " ")}{((contexts & AllocationContexts.Witness) == AllocationContexts.Witness ? "W" : " ")}"; + return $"{((contexts & AllocationContexts.Headers) == AllocationContexts.Headers ? "H" : " ")}{((contexts & AllocationContexts.Bodies) == AllocationContexts.Bodies ? "B" : " ")}{((contexts & AllocationContexts.Receipts) == AllocationContexts.Receipts ? "R" : " ")}{((contexts & AllocationContexts.State) == AllocationContexts.State ? "N" : " ")}{((contexts & AllocationContexts.Snap) == AllocationContexts.Snap ? "S" : " ")}{((contexts & AllocationContexts.Witness) == AllocationContexts.Witness ? "W" : " ")}"; } public override string ToString() => $"[{BuildContextString(AllocatedContexts)}][{BuildContextString(SleepingContexts)}]{SyncPeer}"; diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs index 56a42269aca..7fdb01e8e95 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs @@ -47,6 +47,7 @@ namespace Nethermind.Synchronization.Peers public class SyncPeerPool : ISyncPeerPool { private const int InitTimeout = 3000; // the Eth.Timeout hits us at 5000 (or whatever it is configured to) + public const int DefaultUpgradeIntervalInMs = 1000; private readonly IBlockTree _blockTree; private readonly ILogger _logger; @@ -74,7 +75,7 @@ private readonly ConcurrentDictionary _refre private Task? _refreshLoopTask; private readonly ManualResetEvent _signals = new(true); - private readonly TimeSpan _timeBeforeWakingShallowSleepingPeerUp = TimeSpan.FromMilliseconds(1000); + private readonly TimeSpan _timeBeforeWakingShallowSleepingPeerUp = TimeSpan.FromMilliseconds(DefaultUpgradeIntervalInMs); private Timer? _upgradeTimer; public SyncPeerPool(IBlockTree blockTree, @@ -82,7 +83,7 @@ public SyncPeerPool(IBlockTree blockTree, IBetterPeerStrategy betterPeerStrategy, int peersMaxCount, ILogManager logManager) - : this(blockTree, nodeStatsManager, betterPeerStrategy, peersMaxCount, 1000, logManager) + : this(blockTree, nodeStatsManager, betterPeerStrategy, peersMaxCount, DefaultUpgradeIntervalInMs, logManager) { } @@ -90,6 +91,17 @@ public SyncPeerPool(IBlockTree blockTree, INodeStatsManager nodeStatsManager, IBetterPeerStrategy betterPeerStrategy, int peersMaxCount, + int allocationsUpgradeIntervalInMs, + ILogManager logManager) + : this(blockTree, nodeStatsManager, betterPeerStrategy, peersMaxCount, 0, allocationsUpgradeIntervalInMs, logManager) + { + } + + public SyncPeerPool(IBlockTree blockTree, + INodeStatsManager nodeStatsManager, + IBetterPeerStrategy betterPeerStrategy, + int peersMaxCount, + int priorityPeerMaxCount, int allocationsUpgradeIntervalInMsInMs, ILogManager logManager) { @@ -97,8 +109,11 @@ public SyncPeerPool(IBlockTree blockTree, _stats = nodeStatsManager ?? throw new ArgumentNullException(nameof(nodeStatsManager)); _betterPeerStrategy = betterPeerStrategy ?? throw new ArgumentNullException(nameof(betterPeerStrategy)); PeerMaxCount = peersMaxCount; + PriorityPeerMaxCount = priorityPeerMaxCount; _allocationsUpgradeIntervalInMs = allocationsUpgradeIntervalInMsInMs; _logger = logManager.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + + if (_logger.IsDebug) _logger.Debug($"PeerMaxCount: {PeerMaxCount}, PriorityPeerMaxCount: {PriorityPeerMaxCount}"); } public void ReportNoSyncProgress(PeerInfo peerInfo, AllocationContexts allocationContexts) @@ -144,9 +159,6 @@ public void Start() TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap() .ContinueWith(t => - - - { if (t.IsFaulted) { @@ -233,8 +245,10 @@ internal IEnumerable ReplaceableAllocations } public int PeerCount => _peers.Count; + public int PriorityPeerCount = 0; public int InitializedPeersCount => InitializedPeers.Count(); public int PeerMaxCount { get; } + private int PriorityPeerMaxCount { get; } public void RefreshTotalDifficulty(ISyncPeer syncPeer, Keccak blockHash) { @@ -261,6 +275,13 @@ public void AddPeer(ISyncPeer syncPeer) _peers.TryAdd(syncPeer.Node.Id, peerInfo); Metrics.SyncPeers = _peers.Count; + if (syncPeer.IsPriority) + { + Interlocked.Increment(ref PriorityPeerCount); + Metrics.PriorityPeers = PriorityPeerCount; + } + if (_logger.IsDebug) _logger.Debug($"PeerCount: {PeerCount}, PriorityPeerCount: {PriorityPeerCount}"); + BlockHeader? header = _blockTree.FindHeader(syncPeer.HeadHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded); if (header is not null) { @@ -298,7 +319,14 @@ public void RemovePeer(ISyncPeer syncPeer) } Metrics.SyncPeers = _peers.Count; - + + if (syncPeer.IsPriority) + { + Interlocked.Decrement(ref PriorityPeerCount); + Metrics.PriorityPeers = PriorityPeerCount; + } + if (_logger.IsDebug) _logger.Debug($"PeerCount: {PeerCount}, PriorityPeerCount: {PriorityPeerCount}"); + foreach ((SyncPeerAllocation allocation, _) in _replaceableAllocations) { if (allocation.Current?.SyncPeer.Node.Id == id) @@ -314,6 +342,15 @@ public void RemovePeer(ISyncPeer syncPeer) } } + public void SetPeerPriority(PublicKey id) + { + if (_peers.TryGetValue(id, out PeerInfo peerInfo) && !peerInfo.SyncPeer.IsPriority) + { + peerInfo.SyncPeer.IsPriority = true; + Interlocked.Increment(ref PriorityPeerCount); + } + } + public async Task Allocate(IPeerAllocationStrategy peerAllocationStrategy, AllocationContexts allocationContexts = AllocationContexts.All, int timeoutMilliseconds = 0) { int tryCount = 1; @@ -519,7 +556,7 @@ internal void DropUselessPeers(bool force = false) PeerInfo? worstPeer = null; foreach (PeerInfo peerInfo in NonStaticPeers) { - if (peerInfo.HeadNumber < lowestBlockNumber) + if (peerInfo.HeadNumber < lowestBlockNumber && (!peerInfo.SyncPeer.IsPriority || PriorityPeerCount >= PriorityPeerMaxCount)) { lowestBlockNumber = peerInfo.HeadNumber; worstPeer = peerInfo; diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeersReport.cs b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeersReport.cs index 512829d073b..44204a69d49 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeersReport.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeersReport.cs @@ -110,6 +110,7 @@ private void AddPeerInfo(PeerInfo peerInfo) _stringBuilder.Append('|').Append(stats.GetPaddedAverageTransferSpeed(TransferSpeedType.Bodies)); _stringBuilder.Append('|').Append(stats.GetPaddedAverageTransferSpeed(TransferSpeedType.Receipts)); _stringBuilder.Append('|').Append(stats.GetPaddedAverageTransferSpeed(TransferSpeedType.NodeData)); + _stringBuilder.Append('|').Append(stats.GetPaddedAverageTransferSpeed(TransferSpeedType.SnapRanges)); _stringBuilder.Append(']'); _stringBuilder.Append('[').Append(peerInfo.SyncPeer.ClientId).Append(']'); } diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs new file mode 100644 index 00000000000..cd165501229 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Synchronization.SnapSync +{ + public enum AddRangeResult + { + OK, + MissingRootHashInProofs, + DifferentRootHash, + ExpiredRootHash + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapProvider.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapProvider.cs new file mode 100644 index 00000000000..37092f390db --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapProvider.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Synchronization.SnapSync +{ + public interface ISnapProvider + { + (SnapSyncBatch request, bool finished) GetNextRequest(); + + bool CanSync(); + + AddRangeResult AddAccountRange(AccountRange request, AccountsAndProofs response); + AddRangeResult AddAccountRange(long blockNumber, Keccak expectedRootHash, Keccak startingHash, PathWithAccount[] accounts, byte[][] proofs = null); + + AddRangeResult AddStorageRange(StorageRange request, SlotsAndProofs response); + AddRangeResult AddStorageRange(long blockNumber, PathWithAccount pathWithAccount, Keccak expectedRootHash, Keccak startingHash, PathWithStorageSlot[] slots, byte[][] proofs = null); + + void AddCodes(Keccak[] requestedHashes, byte[][] codes); + + void RefreshAccounts(AccountsToRefreshRequest request, byte[][] response); + + void RetryRequest(SnapSyncBatch batch); + + bool IsSnapGetRangesFinished(); + void UpdatePivot(); + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/Pivot.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/Pivot.cs new file mode 100644 index 00000000000..9a206cc55bc --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/Pivot.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Logging; +using Nethermind.State.Snap; + +namespace Nethermind.Synchronization.SnapSync +{ + internal class Pivot + { + private readonly IBlockTree _blockTree; + private BlockHeader _bestHeader; + private readonly ILogger _logger; + + public long Diff + { + get + { + return (_blockTree.BestSuggestedHeader?.Number ?? 0) - (_bestHeader?.Number ?? 0); + } + } + + public Pivot(IBlockTree blockTree, ILogManager logManager) + { + _blockTree = blockTree; + _logger = logManager.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + } + + public BlockHeader GetPivotHeader() + { + if(_bestHeader is null || _blockTree.BestSuggestedHeader?.Number - _bestHeader.Number >= Constants.MaxDistanceFromHead - 35) + { + LogPivotChanged($"distance from HEAD:{Diff}"); + _bestHeader = _blockTree.BestSuggestedHeader; + } + + if (_logger.IsDebug) + { + var currentHeader = _blockTree.FindHeader(_bestHeader.Number); + if (currentHeader.StateRoot != _bestHeader.StateRoot) + { + _logger.Warn($"SNAP - Pivot:{_bestHeader.StateRoot}, Current:{currentHeader.StateRoot}"); + } + } + + return _bestHeader; + } + + private void LogPivotChanged(string msg) + { + _logger.Info($"SNAP - {msg} - Pivot changed from {_bestHeader?.Number} to {_blockTree.BestSuggestedHeader?.Number}"); + } + + public void UpdateHeaderForcefully() + { + if (_blockTree.BestSuggestedHeader?.Number > _bestHeader.Number) + { + LogPivotChanged("to many empty responses"); + _bestHeader = _blockTree.BestSuggestedHeader; + } + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs new file mode 100644 index 00000000000..fff9873a3e3 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs @@ -0,0 +1,313 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.State.Snap; + +namespace Nethermind.Synchronization.SnapSync +{ + public class ProgressTracker + { + private const string NO_REQUEST = "NO REQUEST"; + + private const int STORAGE_BATCH_SIZE = 1_200; + private const int CODES_BATCH_SIZE = 1_000; + private readonly byte[] ACC_PROGRESS_KEY = Encoding.ASCII.GetBytes("AccountProgressKey"); + + private long _reqCount; + private int _activeAccountRequests; + private int _activeStorageRequests; + private int _activeCodeRequests; + private int _activeAccRefreshRequests; + + private readonly ILogger _logger; + private readonly IDb _db; + + public Keccak NextAccountPath { get; set; } = Keccak.Zero; + private ConcurrentQueue NextSlotRange { get; set; } = new(); + private ConcurrentQueue StoragesToRetrieve { get; set; } = new(); + private ConcurrentQueue CodesToRetrieve { get; set; } = new(); + private ConcurrentQueue AccountsToRefresh { get; set; } = new(); + + public bool MoreAccountsToRight { get; set; } = true; + + private readonly Pivot _pivot; + public event EventHandler SnapSyncFinished; + + public ProgressTracker(IBlockTree blockTree, IDb db, ILogManager logManager) + { + _logger = logManager.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + _db = db ?? throw new ArgumentNullException(nameof(db)); + + _pivot = new Pivot(blockTree, logManager); + + //TODO: maybe better to move to a init method instead of the constructor + GetSyncProgress(); + } + + public bool CanSync() + { + BlockHeader? header = _pivot.GetPivotHeader(); + if (header == null || header.Number == 0) + { + if (_logger.IsInfo) _logger.Info($"No Best Suggested Header available. Snap Sync not started."); + + return false; + } + + if (_logger.IsInfo) _logger.Info($"Starting the SNAP data sync from the {header.ToString(BlockHeader.Format.Short)} {header.StateRoot} root"); + + return true; + } + + public void UpdatePivot() + { + _pivot.UpdateHeaderForcefully(); + } + + public (SnapSyncBatch request, bool finished) GetNextRequest() + { + Interlocked.Increment(ref _reqCount); + + var pivotHeader = _pivot.GetPivotHeader(); + var rootHash = pivotHeader.StateRoot; + var blockNumber = pivotHeader.Number; + + SnapSyncBatch request = new(); + + if (AccountsToRefresh.Count > 0) + { + LogRequest($"AccountsToRefresh:{AccountsToRefresh.Count}"); + + int queueLength = AccountsToRefresh.Count; + AccountWithStorageStartingHash[] paths = new AccountWithStorageStartingHash[queueLength]; + + for (int i = 0; i < queueLength && AccountsToRefresh.TryDequeue(out var acc); i++) + { + paths[i] = acc; + } + + Interlocked.Increment(ref _activeAccRefreshRequests); + + request.AccountsToRefreshRequest = new AccountsToRefreshRequest() { RootHash = rootHash, Paths = paths }; + + return (request, false); + + } + else if (MoreAccountsToRight && _activeAccountRequests == 0 && NextSlotRange.Count < 10 && StoragesToRetrieve.Count < 5 * STORAGE_BATCH_SIZE && CodesToRetrieve.Count < 5 * CODES_BATCH_SIZE) + { + AccountRange range = new(rootHash, NextAccountPath, Keccak.MaxValue, blockNumber); + + LogRequest("AccountRange"); + + Interlocked.Increment(ref _activeAccountRequests); + + request.AccountRangeRequest = range; + + return (request, false); + } + else if (NextSlotRange.TryDequeue(out StorageRange slotRange)) + { + slotRange.RootHash = rootHash; + slotRange.BlockNumber = blockNumber; + + LogRequest($"NextSlotRange:{slotRange.Accounts.Length}"); + + Interlocked.Increment(ref _activeStorageRequests); + + request.StorageRangeRequest = slotRange; + + return (request, false); + } + else if (StoragesToRetrieve.Count > 0) + { + // TODO: optimize this + List storagesToQuery = new(STORAGE_BATCH_SIZE); + + for (int i = 0; i < STORAGE_BATCH_SIZE && StoragesToRetrieve.TryDequeue(out PathWithAccount storage); i++) + { + storagesToQuery.Add(storage); + } + + StorageRange storageRange = new() + { + RootHash = rootHash, + Accounts = storagesToQuery.ToArray(), + StartingHash = Keccak.Zero, + BlockNumber = blockNumber + }; + + LogRequest($"StoragesToRetrieve:{storagesToQuery.Count}"); + + Interlocked.Increment(ref _activeStorageRequests); + + request.StorageRangeRequest = storageRange; + + return (request, false); + } + else if (CodesToRetrieve.Count > 0) + { + // TODO: optimize this + List codesToQuery = new(CODES_BATCH_SIZE); + + for (int i = 0; i < CODES_BATCH_SIZE && CodesToRetrieve.TryDequeue(out Keccak codeHash); i++) + { + codesToQuery.Add(codeHash); + } + + LogRequest($"CodesToRetrieve:{codesToQuery.Count}"); + + Interlocked.Increment(ref _activeCodeRequests); + + request.CodesRequest = codesToQuery.ToArray(); + + return (request, false); + } + + bool rangePhaseFinished = IsSnapGetRangesFinished(); + if (rangePhaseFinished) + { + _logger.Info($"SNAP - State Ranges (Phase 1) finished."); + FinishRangePhase(); + } + + LogRequest(NO_REQUEST); + + return (null, IsSnapGetRangesFinished()); + } + + public void EnqueueCodeHashes(ICollection? codeHashes) + { + if (codeHashes is not null) + { + foreach (var hash in codeHashes) + { + CodesToRetrieve.Enqueue(hash); + } + } + } + + public void ReportCodeRequestFinished(ICollection codeHashes = null) + { + EnqueueCodeHashes(codeHashes); + + Interlocked.Decrement(ref _activeCodeRequests); + } + + public void ReportAccountRefreshFinished(AccountsToRefreshRequest accountsToRefreshRequest = null) + { + if (accountsToRefreshRequest is not null) + { + foreach (var path in accountsToRefreshRequest.Paths) + { + AccountsToRefresh.Enqueue(path); + } + } + + Interlocked.Decrement(ref _activeAccRefreshRequests); + } + + public void EnqueueAccountStorage(PathWithAccount pwa) + { + StoragesToRetrieve.Enqueue(pwa); + } + + public void EnqueueAccountRefresh(PathWithAccount pathWithAccount, Keccak startingHash) + { + AccountsToRefresh.Enqueue(new AccountWithStorageStartingHash() { PathAndAccount = pathWithAccount, StorageStartingHash = startingHash}); + } + + public void ReportFullStorageRequestFinished(PathWithAccount[] storages = null) + { + if (storages is not null) + { + for (int index = 0; index < storages.Length; index++) + { + EnqueueAccountStorage(storages[index]); + } + } + + Interlocked.Decrement(ref _activeStorageRequests); + } + + public void EnqueueStorageRange(StorageRange storageRange) + { + if (storageRange is not null) + { + NextSlotRange.Enqueue(storageRange); + } + } + + public void ReportStorageRangeRequestFinished(StorageRange storageRange = null) + { + EnqueueStorageRange(storageRange); + + Interlocked.Decrement(ref _activeStorageRequests); + } + + public void ReportAccountRequestFinished() + { + Interlocked.Decrement(ref _activeAccountRequests); + } + + public bool IsSnapGetRangesFinished() + { + return !MoreAccountsToRight + && StoragesToRetrieve.Count == 0 + && NextSlotRange.Count == 0 + && CodesToRetrieve.Count == 0 + && AccountsToRefresh.Count == 0 + && _activeAccountRequests == 0 + && _activeStorageRequests == 0 + && _activeCodeRequests == 0 + && _activeAccRefreshRequests == 0; + } + + private void GetSyncProgress() + { + byte[] progress = _db.Get(ACC_PROGRESS_KEY); + if (progress is { Length: 32 }) + { + NextAccountPath = new Keccak(progress); + _logger.Info($"SNAP - State Ranges (Phase 1) progress loaded from DB:{NextAccountPath}"); + + if (NextAccountPath == Keccak.MaxValue) + { + _logger.Info($"SNAP - State Ranges (Phase 1) is finished. Healing (Phase 2) starting..."); + MoreAccountsToRight = false; + } + } + } + + private void FinishRangePhase() + { + MoreAccountsToRight = false; + NextAccountPath = Keccak.MaxValue; + _db.Set(ACC_PROGRESS_KEY, NextAccountPath.Bytes); + + SnapSyncFinished?.Invoke(this, EventArgs.Empty); + } + + private void LogRequest(string reqType) + { + if (_reqCount % 100 == 0) + { + double progress = 100 * NextAccountPath.Bytes[0] / (double)256; + + if (_logger.IsInfo) _logger.Info($"SNAP - progress of State Ranges (Phase 1): {progress}% [{new string('*', (int)progress / 10)}{new string(' ', 10 - (int)progress / 10)}]"); + } + + if (_logger.IsTrace || _reqCount % 1000 == 0) + { + _logger.Info( + $"SNAP - ({reqType}, diff:{_pivot.Diff}) {MoreAccountsToRight}:{NextAccountPath} - Requests Account:{_activeAccountRequests} | Storage:{_activeStorageRequests} | Code:{_activeCodeRequests} | Refresh:{_activeAccRefreshRequests} - Queues Slots:{NextSlotRange.Count} | Storages:{StoragesToRetrieve.Count} | Codes:{CodesToRetrieve.Count} | Refresh:{AccountsToRefresh.Count}"); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs new file mode 100644 index 00000000000..5522bf1d043 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.State.Snap; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Synchronization.SnapSync +{ + public class SnapProvider : ISnapProvider + { + private readonly ITrieStore _store; + private readonly IDbProvider _dbProvider; + private readonly ILogManager _logManager; + private readonly ILogger _logger; + + private readonly ProgressTracker _progressTracker; + + public SnapProvider(ProgressTracker progressTracker, IDbProvider dbProvider, ILogManager logManager) + { + _dbProvider = dbProvider ?? throw new ArgumentNullException(nameof(dbProvider)); + _progressTracker = progressTracker ?? throw new ArgumentNullException(nameof(progressTracker)); + + _store = new TrieStore( + _dbProvider.StateDb, + Trie.Pruning.No.Pruning, + Persist.EveryBlock, + logManager); + + _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); + _logger = logManager.GetClassLogger(); + } + + public bool CanSync() => _progressTracker.CanSync(); + + public (SnapSyncBatch request, bool finished) GetNextRequest() => _progressTracker.GetNextRequest(); + + public AddRangeResult AddAccountRange(AccountRange request, AccountsAndProofs response) + { + AddRangeResult result; + + if (response.PathAndAccounts.Length == 0 && response.Proofs.Length == 0) + { + _logger.Trace($"SNAP - GetAccountRange - requested expired RootHash:{request.RootHash}"); + + result = AddRangeResult.ExpiredRootHash; + } + else + { + result = AddAccountRange(request.BlockNumber.Value, request.RootHash, request.StartingHash, response.PathAndAccounts, response.Proofs); + + if (result == AddRangeResult.OK) + { + Interlocked.Add(ref Metrics.SnapSyncedAccounts, response.PathAndAccounts.Length); + } + } + + _progressTracker.ReportAccountRequestFinished(); + + return result; + } + + public AddRangeResult AddAccountRange(long blockNumber, Keccak expectedRootHash, Keccak startingHash, PathWithAccount[] accounts, byte[][] proofs = null) + { + StateTree tree = new(_store, _logManager); + + (AddRangeResult result, bool moreChildrenToRight, IList accountsWithStorage, IList codeHashes) = + SnapProviderHelper.AddAccountRange(tree, blockNumber, expectedRootHash, startingHash, accounts, proofs); + + if (result == AddRangeResult.OK) + { + foreach (var item in accountsWithStorage) + { + _progressTracker.EnqueueAccountStorage(item); + } + + _progressTracker.EnqueueCodeHashes(codeHashes); + + _progressTracker.NextAccountPath = accounts[accounts.Length - 1].Path; + _progressTracker.MoreAccountsToRight = moreChildrenToRight; + } + else if(result == AddRangeResult.MissingRootHashInProofs) + { + _logger.Trace($"SNAP - AddAccountRange failed, missing root hash {tree.RootHash} in the proofs, startingHash:{startingHash}"); + } + else if(result == AddRangeResult.DifferentRootHash) + { + _logger.Trace($"SNAP - AddAccountRange failed, expected {blockNumber}:{expectedRootHash} but was {tree.RootHash}, startingHash:{startingHash}"); + } + + return result; + } + + public AddRangeResult AddStorageRange(StorageRange request, SlotsAndProofs response) + { + AddRangeResult result = AddRangeResult.OK; + + if (response.PathsAndSlots.Length == 0 && response.Proofs.Length == 0) + { + _logger.Trace($"SNAP - GetStorageRange - expired BlockNumber:{request.BlockNumber}, RootHash:{request.RootHash}, (Accounts:{request.Accounts.Count()}), {request.StartingHash}"); + + _progressTracker.ReportStorageRangeRequestFinished(request); + + return AddRangeResult.ExpiredRootHash; + } + else + { + int slotCount = 0; + + int requestLength = request.Accounts.Length; + int responseLength = response.PathsAndSlots.Length; + + for (int i = 0; i < responseLength; i++) + { + // only the last can have proofs + byte[][] proofs = null; + if (i == responseLength - 1) + { + proofs = response.Proofs; + } + + result = AddStorageRange(request.BlockNumber.Value, request.Accounts[i], request.Accounts[i].Account.StorageRoot, request.StartingHash, response.PathsAndSlots[i], proofs); + + slotCount += response.PathsAndSlots[i].Length; + } + + if (requestLength > responseLength) + { + _progressTracker.ReportFullStorageRequestFinished(request.Accounts[responseLength..requestLength]); + } + else + { + _progressTracker.ReportFullStorageRequestFinished(); + } + + if (result == AddRangeResult.OK && slotCount > 0) + { + Interlocked.Add(ref Metrics.SnapSyncedStorageSlots, slotCount); + } + } + + return result; + } + + public AddRangeResult AddStorageRange(long blockNumber, PathWithAccount pathWithAccount, Keccak expectedRootHash, Keccak? startingHash, PathWithStorageSlot[] slots, byte[][]? proofs = null) + { + StorageTree tree = new(_store, _logManager); + (AddRangeResult result, bool moreChildrenToRight) = SnapProviderHelper.AddStorageRange(tree, blockNumber, startingHash, slots, expectedRootHash, proofs); + + if (result == AddRangeResult.OK) + { + if (moreChildrenToRight) + { + StorageRange range = new() + { + Accounts = new[] { pathWithAccount }, + StartingHash = slots.Last().Path + }; + + _progressTracker.EnqueueStorageRange(range); + } + } + else if(result == AddRangeResult.MissingRootHashInProofs) + { + _logger.Trace($"SNAP - AddStorageRange failed, missing root hash {expectedRootHash} in the proofs, startingHash:{startingHash}"); + + _progressTracker.EnqueueAccountRefresh(pathWithAccount, startingHash); + } + else if(result == AddRangeResult.DifferentRootHash) + { + _logger.Trace($"SNAP - AddStorageRange failed, expected storage root hash:{expectedRootHash} but was {tree.RootHash}, startingHash:{startingHash}"); + + _progressTracker.EnqueueAccountRefresh(pathWithAccount, startingHash); + } + + return result; + } + + public void RefreshAccounts(AccountsToRefreshRequest request, byte[][] response) + { + int respLength = response.Length; + + for (int reqi = 0; reqi < request.Paths.Length; reqi++) + { + var requestedPath = request.Paths[reqi]; + + if (reqi < respLength) + { + byte[] nodeData = response[reqi]; + + if(nodeData.Length == 0) + { + RetryAccountRefresh(requestedPath); + _logger.Trace($"SNAP - Empty Account Refresh:{requestedPath.PathAndAccount.Path}"); + continue; + } + + try + { + var node = new TrieNode(NodeType.Unknown, nodeData, true); + node.ResolveNode(_store); + node.ResolveKey(_store, true); + + requestedPath.PathAndAccount.Account = requestedPath.PathAndAccount.Account.WithChangedStorageRoot(node.Keccak); + + if (requestedPath.StorageStartingHash > Keccak.Zero) + { + StorageRange range = new() + { + Accounts = new[] { requestedPath.PathAndAccount }, + StartingHash = requestedPath.StorageStartingHash + }; + + _progressTracker.EnqueueStorageRange(range); + } + else + { + _progressTracker.EnqueueAccountStorage(requestedPath.PathAndAccount); + } + } + catch (Exception exc) + { + RetryAccountRefresh(requestedPath); + _logger.Warn($"SNAP - {exc.Message}:{requestedPath.PathAndAccount.Path}:{Bytes.ToHexString(nodeData)}"); + } + } + else + { + RetryAccountRefresh(requestedPath); + } + } + + _progressTracker.ReportAccountRefreshFinished(); + } + + private void RetryAccountRefresh(AccountWithStorageStartingHash requestedPath) + { + _progressTracker.EnqueueAccountRefresh(requestedPath.PathAndAccount, requestedPath.StorageStartingHash); + } + + public void AddCodes(Keccak[] requestedHashes, byte[][] codes) + { + HashSet set = requestedHashes.ToHashSet(); + + for (int i = 0; i < codes.Length; i++) + { + byte[] code = codes[i]; + Keccak codeHash = Keccak.Compute(code); + + if (set.Remove(codeHash)) + { + Interlocked.Add(ref Metrics.SnapStateSynced, code.Length); + _dbProvider.CodeDb.Set(codeHash, code); + } + } + + Interlocked.Add(ref Metrics.SnapSyncedCodes, codes.Length); + + _progressTracker.ReportCodeRequestFinished(set); + } + + public void RetryRequest(SnapSyncBatch batch) + { + if (batch.AccountRangeRequest != null) + { + _progressTracker.ReportAccountRequestFinished(); + } + else if (batch.StorageRangeRequest != null) + { + _progressTracker.ReportStorageRangeRequestFinished(batch.StorageRangeRequest); + } + else if (batch.CodesRequest != null) + { + _progressTracker.ReportCodeRequestFinished(batch.CodesRequest); + } + else if (batch.AccountsToRefreshRequest != null) + { + _progressTracker.ReportAccountRefreshFinished(batch.AccountsToRefreshRequest); + } + } + + public bool IsSnapGetRangesFinished() => _progressTracker.IsSnapGetRangesFinished(); + + public void UpdatePivot() + { + _progressTracker.UpdatePivot(); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs new file mode 100644 index 00000000000..9eca40f0de2 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.State.Snap; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Synchronization.SnapSync +{ + internal static class SnapProviderHelper + { + private static object _syncCommit = new(); + + public static (AddRangeResult result, bool moreChildrenToRight, IList storageRoots, IList codeHashes) + AddAccountRange(StateTree tree, long blockNumber, Keccak expectedRootHash, Keccak startingHash, PathWithAccount[] accounts, byte[][] proofs = null) + { + // TODO: Check the accounts boundaries and sorting + + Keccak lastHash = accounts[^1].Path; + + (AddRangeResult result, IList sortedBoundaryList, bool moreChildrenToRight) = FillBoundaryTree(tree, startingHash, lastHash, expectedRootHash, proofs); + + if (result != AddRangeResult.OK) + { + return (result, true, null, null); + } + + IList accountsWithStorage = new List(); + IList codeHashes = new List(); + + for (var index = 0; index < accounts.Length; index++) + { + PathWithAccount account = accounts[index]; + if (account.Account.HasStorage) + { + accountsWithStorage.Add(account); + } + + if (account.Account.HasCode) + { + codeHashes.Add(account.Account.CodeHash); + } + + Rlp rlp = tree.Set(account.Path, account.Account); + if (rlp is not null) + { + Interlocked.Add(ref Metrics.SnapStateSynced, rlp.Bytes.Length); + } + } + + tree.UpdateRootHash(); + + if (tree.RootHash != expectedRootHash) + { + return (AddRangeResult.DifferentRootHash, true, null, null); + } + + StitchBoundaries(sortedBoundaryList, tree.TrieStore); + + lock (_syncCommit) + { + tree.Commit(blockNumber); + } + + return (AddRangeResult.OK, moreChildrenToRight, accountsWithStorage, codeHashes); + } + + public static (AddRangeResult result, bool moreChildrenToRight) AddStorageRange(StorageTree tree, long blockNumber, Keccak? startingHash, PathWithStorageSlot[] slots, Keccak expectedRootHash, byte[][]? proofs = null) + { + // TODO: Check the slots boundaries and sorting + + Keccak lastHash = slots.Last().Path; + + (AddRangeResult result, IList sortedBoundaryList, bool moreChildrenToRight) = FillBoundaryTree(tree, startingHash, lastHash, expectedRootHash, proofs); + + if (result != AddRangeResult.OK) + { + return (result, true); + } + + for (var index = 0; index < slots.Length; index++) + { + PathWithStorageSlot slot = slots[index]; + Interlocked.Add(ref Metrics.SnapStateSynced, slot.SlotRlpValue.Length); + tree.Set(slot.Path, slot.SlotRlpValue, false); + } + + tree.UpdateRootHash(); + + if (tree.RootHash != expectedRootHash) + { + return (AddRangeResult.DifferentRootHash, true); + } + + StitchBoundaries(sortedBoundaryList, tree.TrieStore); + + lock (_syncCommit) + { + tree.Commit(blockNumber); + } + + return (AddRangeResult.OK, moreChildrenToRight); + } + + private static (AddRangeResult result, IList sortedBoundaryList, bool moreChildrenToRight) FillBoundaryTree(PatriciaTree tree, Keccak? startingHash, Keccak endHash, Keccak expectedRootHash, byte[][]? proofs = null) + { + if (proofs is null || proofs.Length == 0) + { + return (AddRangeResult.OK, null, false); + } + + if (tree == null) + { + throw new ArgumentNullException(nameof(tree)); + } + + startingHash ??= Keccak.Zero; + List sortedBoundaryList = new(); + + Dictionary dict = CreateProofDict(proofs, tree.TrieStore); + + if(!dict.TryGetValue(expectedRootHash, out TrieNode root)) + { + return (AddRangeResult.MissingRootHashInProofs, null, true); + } + + Span leftBoundary = stackalloc byte[64]; + Nibbles.BytesToNibbleBytes(startingHash.Bytes, leftBoundary); + Span rightBoundary = stackalloc byte[64]; + Nibbles.BytesToNibbleBytes(endHash.Bytes, rightBoundary); + + Stack<(TrieNode parent, TrieNode node, int pathIndex, List path)> proofNodesToProcess = new(); + + tree.RootRef = root; + proofNodesToProcess.Push((null, root, -1, new List())); + sortedBoundaryList.Add(root); ; + + bool moreChildrenToRight = false; + + while (proofNodesToProcess.Count > 0) + { + (TrieNode parent, TrieNode node, int pathIndex, List path) = proofNodesToProcess.Pop(); + + if (node.IsExtension) + { + Keccak? childKeccak = node.GetChildHash(0); + + if (childKeccak is not null) + { + if (dict.TryGetValue(childKeccak, out TrieNode child)) + { + node.SetChild(0, child); + + pathIndex += node.Path.Length; + path.AddRange(node.Path); + proofNodesToProcess.Push((node, child, pathIndex, path)); + sortedBoundaryList.Add(child); + } + else + { + Span pathSpan = CollectionsMarshal.AsSpan(path); + if (Bytes.Comparer.Compare(pathSpan, leftBoundary[0..path.Count]) >= 0 + && parent is not null + && parent.IsBranch) + { + for (int i = 0; i < 15; i++) + { + Keccak? kec = parent.GetChildHash(i); + if (kec == node.Keccak) + { + parent.SetChild(i, null); + break; + } + } + } + } + } + } + + if (node.IsBranch) + { + pathIndex++; + + Span pathSpan = CollectionsMarshal.AsSpan(path); + int left = Bytes.Comparer.Compare(pathSpan, leftBoundary[0..path.Count]) == 0 ? leftBoundary[pathIndex] : 0; + int right = Bytes.Comparer.Compare(pathSpan, rightBoundary[0..path.Count]) == 0 ? rightBoundary[pathIndex] : 15; + + int maxIndex = moreChildrenToRight ? right : 15; + + for (int ci = left; ci <= maxIndex; ci++) + { + Keccak? childKeccak = node.GetChildHash(ci); + + moreChildrenToRight |= ci > right && childKeccak is not null; + + if (ci >= left && ci <= right) + { + node.SetChild(ci, null); + } + + if (childKeccak is not null && (ci == left || ci == right) && dict.TryGetValue(childKeccak, out TrieNode child)) + { + if (!child.IsLeaf) + { + node.SetChild(ci, child); + + // TODO: we should optimize it - copy only if there are two boundary children + List newPath = new(path); + + newPath.Add((byte)ci); + + proofNodesToProcess.Push((node, child, pathIndex, newPath)); + sortedBoundaryList.Add(child); + } + } + } + } + } + + return (AddRangeResult.OK, sortedBoundaryList, moreChildrenToRight); + } + + private static Dictionary CreateProofDict(byte[][] proofs, ITrieStore store) + { + Dictionary dict = new(); + + for (int i = 0; i < proofs.Length; i++) + { + byte[] proof = proofs[i]; + var node = new TrieNode(NodeType.Unknown, proof, true); + node.IsBoundaryProofNode = true; + node.ResolveNode(store); + node.ResolveKey(store, i == 0); + + dict[node.Keccak] = node; + } + + return dict; + } + + private static void StitchBoundaries(IList sortedBoundaryList, ITrieStore store) + { + if (sortedBoundaryList == null || sortedBoundaryList.Count == 0) + { + return; + } + + for (int i = sortedBoundaryList.Count - 1; i >= 0; i--) + { + TrieNode node = sortedBoundaryList[i]; + + if (!node.IsPersisted) + { + if (node.IsExtension) + { + if (IsChildPersisted(node, 1, store)) + { + node.IsBoundaryProofNode = false; + } + } + + if (node.IsBranch) + { + bool isBoundaryProofNode = false; + for (int ci = 0; ci <= 15; ci++) + { + if (!IsChildPersisted(node, ci, store)) + { + isBoundaryProofNode = true; + break; + } + } + + node.IsBoundaryProofNode = isBoundaryProofNode; + } + } + } + } + + private static bool IsChildPersisted(TrieNode node, int childIndex, ITrieStore store) + { + TrieNode data = node.GetData(childIndex) as TrieNode; + if (data != null) + { + + return data.IsBoundaryProofNode == false; + } + + Keccak childKeccak = node.GetChildHash(childIndex); + if (childKeccak is null) + { + return true; + } + + return store.IsPersisted(childKeccak); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncAllocationStrategyFactory.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncAllocationStrategyFactory.cs new file mode 100644 index 00000000000..5373f55ee27 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncAllocationStrategyFactory.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using Nethermind.Blockchain.Synchronization; +using Nethermind.Stats; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers.AllocationStrategies; + +namespace Nethermind.Synchronization.SnapSync +{ + public class SnapSyncAllocationStrategyFactory : StaticPeerAllocationStrategyFactory + { + + private static readonly IPeerAllocationStrategy DefaultStrategy = + new SatelliteProtocolPeerAllocationStrategy(new TotalDiffStrategy(new BySpeedStrategy(TransferSpeedType.SnapRanges, true), TotalDiffStrategy.TotalDiffSelectionType.CanBeSlightlyWorse), "snap"); + + public SnapSyncAllocationStrategyFactory() : base(DefaultStrategy) + { + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncBatch.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncBatch.cs new file mode 100644 index 00000000000..dd3f8723350 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncBatch.cs @@ -0,0 +1,37 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using Nethermind.Core.Crypto; +using Nethermind.State.Snap; + +namespace Nethermind.Synchronization.SnapSync +{ + public class SnapSyncBatch + { + public AccountRange? AccountRangeRequest { get; set; } + public AccountsAndProofs? AccountRangeResponse { get; set; } + + public StorageRange? StorageRangeRequest { get; set; } + public SlotsAndProofs? StorageRangeResponse { get; set; } + + public Keccak[]? CodesRequest { get; set; } + public byte[][]? CodesResponse { get; set; } + + public AccountsToRefreshRequest? AccountsToRefreshRequest { get; set; } + public byte[][]? AccountsToRefreshResponse { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncDispatcher.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncDispatcher.cs new file mode 100644 index 00000000000..808ac51060d --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncDispatcher.cs @@ -0,0 +1,129 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Logging; +using Nethermind.State.Snap; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; + +namespace Nethermind.Synchronization.SnapSync +{ + public class SnapSyncDispatcher : SyncDispatcher + { + public SnapSyncDispatcher(ISyncFeed? syncFeed, ISyncPeerPool? syncPeerPool, IPeerAllocationStrategyFactory? peerAllocationStrategy, ILogManager? logManager) + : base(syncFeed, syncPeerPool, peerAllocationStrategy, logManager) + { + } + + protected override async Task Dispatch(PeerInfo peerInfo, SnapSyncBatch batch, CancellationToken cancellationToken) + { + ISyncPeer peer = peerInfo.SyncPeer; + + //TODO: replace with a constant "snap" + if (peer.TryGetSatelliteProtocol("snap", out var handler)) + { + if (batch.AccountRangeRequest is not null) + { + Task task = handler.GetAccountRange(batch.AccountRangeRequest, cancellationToken); + + await task.ContinueWith( + (t, state) => + { + if (t.IsFaulted) + { + if (Logger.IsTrace) + Logger.Error("DEBUG/ERROR Error after dispatching the snap sync request", t.Exception); + } + + SnapSyncBatch batchLocal = (SnapSyncBatch)state!; + if (t.IsCompletedSuccessfully) + { + batchLocal.AccountRangeResponse = t.Result; + } + }, batch); + } + else if (batch.StorageRangeRequest is not null) + { + Task task = handler.GetStorageRange(batch.StorageRangeRequest, cancellationToken); + + await task.ContinueWith( + (t, state) => + { + if (t.IsFaulted) + { + if (Logger.IsTrace) + Logger.Error("DEBUG/ERROR Error after dispatching the snap sync request", t.Exception); + } + + SnapSyncBatch batchLocal = (SnapSyncBatch)state!; + if (t.IsCompletedSuccessfully) + { + batchLocal.StorageRangeResponse = t.Result; + } + }, batch); + } + else if (batch.CodesRequest is not null) + { + Task task = handler.GetByteCodes(batch.CodesRequest, cancellationToken); + + await task.ContinueWith( + (t, state) => + { + if (t.IsFaulted) + { + if (Logger.IsTrace) + Logger.Error("DEBUG/ERROR Error after dispatching the snap sync request", t.Exception); + } + + SnapSyncBatch batchLocal = (SnapSyncBatch)state!; + if (t.IsCompletedSuccessfully) + { + batchLocal.CodesResponse = t.Result; + } + }, batch); + } + else if (batch.AccountsToRefreshRequest is not null) + { + Task task = handler.GetTrieNodes(batch.AccountsToRefreshRequest, cancellationToken); + + await task.ContinueWith( + (t, state) => + { + if (t.IsFaulted) + { + if (Logger.IsTrace) + Logger.Error("DEBUG/ERROR Error after dispatching the snap sync request", t.Exception); + } + + SnapSyncBatch batchLocal = (SnapSyncBatch)state!; + if (t.IsCompletedSuccessfully) + { + batchLocal.AccountsToRefreshResponse = t.Result; + } + }, batch); + } + } + + await Task.CompletedTask; + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncFeed.cs new file mode 100644 index 00000000000..92b73c25461 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapSyncFeed.cs @@ -0,0 +1,228 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Linq; +using Nethermind.Blockchain; +using Nethermind.Logging; +using Nethermind.State.Snap; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; +using Nethermind.Core.Crypto; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; + +namespace Nethermind.Synchronization.SnapSync +{ + public class SnapSyncFeed : SyncFeed, IDisposable + { + private readonly object _syncLock = new (); + + private const int AllowedInvalidResponses = 5; + private readonly LinkedList<(PeerInfo peer, AddRangeResult result)> _resultLog = new(); + + private const SnapSyncBatch EmptyBatch = null; + + private readonly ISyncModeSelector _syncModeSelector; + private readonly ISnapProvider _snapProvider; + + private readonly ILogger _logger; + public override bool IsMultiFeed => true; + public override AllocationContexts Contexts => AllocationContexts.Snap; + + public SnapSyncFeed(ISyncModeSelector syncModeSelector, ISnapProvider snapProvider, IBlockTree blockTree, ILogManager logManager) + { + _syncModeSelector = syncModeSelector; + _snapProvider = snapProvider; + _logger = logManager.GetClassLogger(); + + _syncModeSelector.Changed += SyncModeSelectorOnChanged; + } + + public override Task PrepareRequest() + { + try + { + (SnapSyncBatch request, bool finished) = _snapProvider.GetNextRequest(); + + if (request == null) + { + if (finished) + { + Finish(); + } + + return Task.FromResult(EmptyBatch); + } + + return Task.FromResult(request); + } + catch (Exception e) + { + _logger.Error("Error when preparing a batch", e); + return Task.FromResult(EmptyBatch); + } + } + + public override SyncResponseHandlingResult HandleResponse(SnapSyncBatch? batch, PeerInfo peer) + { + if (batch == null) + { + if (_logger.IsError) _logger.Error("Received empty batch as a response"); + return SyncResponseHandlingResult.InternalError; + } + + AddRangeResult result = AddRangeResult.OK; + + if (batch.AccountRangeResponse is not null) + { + result = _snapProvider.AddAccountRange(batch.AccountRangeRequest, batch.AccountRangeResponse); + } + else if (batch.StorageRangeResponse is not null) + { + result = _snapProvider.AddStorageRange(batch.StorageRangeRequest, batch.StorageRangeResponse); + } + else if (batch.CodesResponse is not null) + { + _snapProvider.AddCodes(batch.CodesRequest, batch.CodesResponse); + } + else if (batch.AccountsToRefreshResponse is not null) + { + _snapProvider.RefreshAccounts(batch.AccountsToRefreshRequest, batch.AccountsToRefreshResponse); + } + else + { + _snapProvider.RetryRequest(batch); + + if (peer == null) + { + return SyncResponseHandlingResult.NotAssigned; + } + else + { + _logger.Trace($"SNAP - timeout {peer}"); + return SyncResponseHandlingResult.LesserQuality; + } + } + + return AnalyzeResponsePerPeer(result, peer); + } + + public SyncResponseHandlingResult AnalyzeResponsePerPeer(AddRangeResult result, PeerInfo peer) + { + if(peer == null) + { + return SyncResponseHandlingResult.OK; + } + + int maxSize = 10 * AllowedInvalidResponses; + while (_resultLog.Count > maxSize) + { + lock (_syncLock) + { + if (_resultLog.Count > 0) + { + _resultLog.RemoveLast(); + } + } + } + + lock (_syncLock) + { + _resultLog.AddFirst((peer, result)); + } + + if (result == AddRangeResult.OK) + { + return SyncResponseHandlingResult.OK; + } + else + { + int allLastSuccess = 0; + int allLastFailures = 0; + int peerLastFailures = 0; + + lock(_syncLock) + { + foreach (var item in _resultLog) + { + if (item.result == AddRangeResult.OK) + { + allLastSuccess++; + + if (item.peer == peer) + { + break; + } + } + else + { + allLastFailures++; + + if (item.peer == peer) + { + peerLastFailures++; + + if (peerLastFailures > AllowedInvalidResponses) + { + if (allLastFailures == peerLastFailures) + { + _logger.Trace($"SNAP - peer to be punished:{peer}"); + return SyncResponseHandlingResult.LesserQuality; + } + + if (allLastSuccess == 0 && allLastFailures > peerLastFailures) + { + _snapProvider.UpdatePivot(); + + _resultLog.Clear(); + + break; + } + } + } + } + } + } + + return SyncResponseHandlingResult.OK; + } + } + + public void Dispose() + { + _syncModeSelector.Changed -= SyncModeSelectorOnChanged; + } + + private void SyncModeSelectorOnChanged(object? sender, SyncModeChangedEventArgs e) + { + if (CurrentState == SyncFeedState.Dormant) + { + if ((e.Current & SyncMode.SnapSync) == SyncMode.SnapSync) + { + if (_snapProvider.CanSync()) + { + Activate(); + } + } + } + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs b/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs index 27c13e163fa..cb6a0d31a80 100644 --- a/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs +++ b/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs @@ -25,6 +25,7 @@ using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Logging; +using Nethermind.State.Snap; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization.Blocks; @@ -33,7 +34,9 @@ using Nethermind.Synchronization.ParallelSync; using Nethermind.Synchronization.Peers; using Nethermind.Synchronization.Reporting; +using Nethermind.Synchronization.SnapSync; using Nethermind.Synchronization.StateSync; +using Nethermind.Trie.Pruning; namespace Nethermind.Synchronization { @@ -48,6 +51,7 @@ public class Synchronizer : ISynchronizer protected readonly ILogger _logger; protected readonly IBlockTree _blockTree; protected readonly ISyncConfig _syncConfig; + protected readonly ISnapProvider _snapProvider; protected readonly ISyncPeerPool _syncPeerPool; protected readonly ILogManager _logManager; protected readonly ISyncReport _syncReport; @@ -62,6 +66,7 @@ public class Synchronizer : ISynchronizer protected readonly ISyncModeSelector _syncMode; private FastSyncFeed? _fastSyncFeed; private StateSyncFeed? _stateSyncFeed; + private SnapSyncFeed? _snapSyncFeed; private FullSyncFeed? _fullSyncFeed; private HeadersSyncFeed? _headersFeed; private BodiesSyncFeed? _bodiesFeed; @@ -77,6 +82,7 @@ public Synchronizer( INodeStatsManager nodeStatsManager, ISyncModeSelector syncModeSelector, ISyncConfig syncConfig, + ISnapProvider snapProvider, IBlockDownloaderFactory blockDownloaderFactory, IPivot pivot, ILogManager logManager) @@ -88,6 +94,7 @@ public Synchronizer( _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); + _snapProvider = snapProvider ?? throw new ArgumentNullException(nameof(snapProvider)); _blockDownloaderFactory = blockDownloaderFactory ?? throw new ArgumentNullException(nameof(blockDownloaderFactory)); _pivot = pivot ?? throw new ArgumentNullException(nameof(pivot)); _syncPeerPool = peerPool ?? throw new ArgumentNullException(nameof(peerPool)); @@ -105,6 +112,7 @@ public virtual void Start() } StartFullSyncComponents(); + if (_syncConfig.FastSync) { if (_syncConfig.FastBlocks) @@ -113,6 +121,12 @@ public virtual void Start() } StartFastSyncComponents(); + + if (_syncConfig.SnapSync) + { + StartSnapSyncComponents(); + } + StartStateSyncComponents(); } } @@ -143,7 +157,8 @@ private void StartFullSyncComponents() private void StartStateSyncComponents() { - _stateSyncFeed = new StateSyncFeed(_dbProvider.CodeDb, _dbProvider.StateDb, _syncMode, _blockTree, _logManager); + TreeSync treeSync = new(SyncMode.StateNodes, _dbProvider.CodeDb, _dbProvider.StateDb, _blockTree, _logManager); + _stateSyncFeed = new StateSyncFeed(_syncMode, treeSync, _logManager); StateSyncDispatcher stateSyncDispatcher = new(_stateSyncFeed!, _syncPeerPool, new StateSyncAllocationStrategyFactory(), _logManager); Task syncDispatcherTask = stateSyncDispatcher.Start(_syncCancellation.Token).ContinueWith(t => { @@ -158,6 +173,24 @@ private void StartStateSyncComponents() }); } + private void StartSnapSyncComponents() + { + _snapSyncFeed = new SnapSyncFeed(_syncMode, _snapProvider, _blockTree, _logManager); + SnapSyncDispatcher dispatcher = new(_snapSyncFeed!, _syncPeerPool, new SnapSyncAllocationStrategyFactory(), _logManager); + + Task _ = dispatcher.Start(_syncCancellation.Token).ContinueWith(t => + { + if (t.IsFaulted) + { + if (_logger.IsError) _logger.Error("State sync failed", t.Exception); + } + else + { + if (_logger.IsInfo) _logger.Info("State sync task completed."); + } + }); + } + private void StartFastBlocksComponents() { FastBlocksPeerAllocationStrategyFactory fastFactory = new(); diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs index 9092eb5ceb0..0b346532304 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs @@ -24,6 +24,7 @@ public class TrieTests { private ILogger _logger; private ILogManager _logManager; + private Random _random = new(); [SetUp] public void SetUp() @@ -236,7 +237,7 @@ public void Test_add_many(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); patriciaTree.Set(key.Bytes, value); } @@ -247,7 +248,7 @@ public void Test_add_many(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); checkTree.Get(key.Bytes).Should().BeEquivalentTo(value, $@"{i} {j}"); } } @@ -261,7 +262,7 @@ public void Test_try_delete_and_read_missing_nodes(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); patriciaTree.Set(key.Bytes, value); } @@ -281,7 +282,7 @@ public void Test_try_delete_and_read_missing_nodes(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); checkTree.Get(key.Bytes).Should().BeEquivalentTo(value, $@"{i} {j}"); } @@ -302,14 +303,14 @@ public void Test_update_many(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); patriciaTree.Set(key.Bytes, value); } for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j + 1); + byte[] value = TestItem.GenerateIndexedAccountRlp(j + 1); patriciaTree.Set(key.Bytes, value); } @@ -320,7 +321,7 @@ public void Test_update_many(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j + 1); + byte[] value = TestItem.GenerateIndexedAccountRlp(j + 1); checkTree.Get(key.Bytes).Should().BeEquivalentTo(value, $@"{i} {j}"); } } @@ -334,7 +335,7 @@ public void Test_update_many_next_block(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); patriciaTree.Set(key.Bytes, value); } @@ -343,7 +344,7 @@ public void Test_update_many_next_block(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j + 1); + byte[] value = TestItem.GenerateIndexedAccountRlp(j + 1); patriciaTree.Set(key.Bytes, value); _logger.Trace($"Setting {key.Bytes.ToHexString()} = {value.ToHexString()}"); } @@ -355,7 +356,7 @@ public void Test_update_many_next_block(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j + 1); + byte[] value = TestItem.GenerateIndexedAccountRlp(j + 1); _logger.Trace($"Checking {key.Bytes.ToHexString()} = {value.ToHexString()}"); checkTree.Get(key.Bytes).Should().BeEquivalentTo(value, $@"{i} {j}"); @@ -372,7 +373,7 @@ public void Test_add_and_delete_many_same_block(int i) { _logger.Trace($" set {j}"); Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); patriciaTree.Set(key.Bytes, value); } @@ -403,7 +404,7 @@ public void Test_add_and_delete_many_next_block(int i) for (int j = 0; j < i; j++) { Keccak key = TestItem.Keccaks[j]; - byte[] value = GenerateIndexedAccountRlp(j); + byte[] value = TestItem.GenerateIndexedAccountRlp(j); patriciaTree.Set(key.Bytes, value); } @@ -676,9 +677,6 @@ public void When_two_branches_with_two_same_children_change_one_and_change_back_ checkTree.Get(_keyD).Should().BeEquivalentTo(_longLeaf1); } - private AccountDecoder _accountDecoder = new(); - private Random _random = new(); - // [TestCase(256, 128, 128, 32)] [TestCase(128, 128, 8, 8)] // [TestCase(4, 16, 4, 4)] @@ -718,7 +716,7 @@ public void Fuzz_accounts( } else { - randomValues[i] = GenerateRandomAccountRlp(); + randomValues[i] = TestItem.GenerateRandomAccountRlp(); } } @@ -791,42 +789,6 @@ public void Fuzz_accounts( } } - private byte[] GenerateRandomAccountRlp() - { - Account account = GenerateRandomAccount(); - byte[] value = _accountDecoder.Encode(account).Bytes; - return value; - } - - private Account GenerateRandomAccount() - { - Account account = new( - (UInt256) _random.Next(1000), - (UInt256) _random.Next(1000), - Keccak.EmptyTreeHash, - Keccak.OfAnEmptyString); - - return account; - } - - private Account GenerateIndexedAccount(int index) - { - Account account = new( - (UInt256) index, - (UInt256) index, - Keccak.EmptyTreeHash, - Keccak.OfAnEmptyString); - - return account; - } - - private byte[] GenerateIndexedAccountRlp(int index) - { - Account account = GenerateIndexedAccount(index); - byte[] value = _accountDecoder.Encode(account).Bytes; - return value; - } - // [TestCase(256, 128, 128, 32)] // [TestCase(128, 128, 8, 8)] [TestCase(4, 16, 4, 4, null)] @@ -872,7 +834,7 @@ public void Fuzz_accounts_with_reorganizations( } else { - randomValues[i] = GenerateRandomAccountRlp(); + randomValues[i] = TestItem.GenerateRandomAccountRlp(); } } @@ -1027,7 +989,7 @@ public void Fuzz_accounts_with_storage( } else { - accounts[i] = GenerateRandomAccount(); + accounts[i] = TestItem.GenerateRandomAccount(); } addresses[i] = TestItem.GetRandomAddress(_random); diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index ac2a751d27b..ac853924c7f 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -56,7 +56,7 @@ public class PatriciaTree private readonly ConcurrentQueue? _currentCommit; - protected readonly ITrieStore TrieStore; + public readonly ITrieStore TrieStore; private readonly bool _parallelBranches; @@ -64,7 +64,7 @@ public class PatriciaTree private Keccak _rootHash = Keccak.EmptyTreeHash; - internal TrieNode? RootRef; + public TrieNode? RootRef; /// /// Only used in EthereumTests diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs index da7c62b51f4..c79b23a4f4b 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs @@ -16,6 +16,7 @@ using System; using Nethermind.Core; +using Nethermind.Core.Crypto; namespace Nethermind.Trie.Pruning { @@ -25,6 +26,8 @@ public interface ITrieStore : ITrieNodeResolver, IDisposable void FinishBlockCommit(TrieType trieType, long blockNumber, TrieNode? root); + bool IsPersisted(Keccak keccak); + void HackPersistOnShutdown(); IReadOnlyTrieStore AsReadOnly(IKeyValueStore? keyValueStore); diff --git a/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs index e3de487a6a1..6f6ba9aeb57 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs @@ -52,7 +52,9 @@ public byte[] LoadRlp(Keccak hash) { return Array.Empty(); } - + + public bool IsPersisted(Keccak keccak) => true; + public void Dispose() { } } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs index 6e1f96fe536..58876a8c6ed 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs @@ -40,6 +40,8 @@ public TrieNode FindCachedOrUnknown(Keccak hash) => public byte[] LoadRlp(Keccak hash) => _trieStore.LoadRlp(hash, _readOnlyStore); + public bool IsPersisted(Keccak keccak) => _trieStore.IsPersisted(keccak); + public IReadOnlyTrieStore AsReadOnly(IKeyValueStore keyValueStore) { return new ReadOnlyTrieStore(_trieStore, keyValueStore); @@ -56,7 +58,6 @@ public event EventHandler ReorgBoundaryReached add { } remove { } } - public void Dispose() { } } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 0be0c980da9..2071d9f52f1 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -219,7 +219,7 @@ public void CommitNode(long blockNumber, NodeCommitInfo nodeCommitInfo) EnsureCommitSetExistsForBlock(blockNumber); if (_logger.IsTrace) _logger.Trace($"Committing {nodeCommitInfo} at {blockNumber}"); - if (!nodeCommitInfo.IsEmptyBlockMarker) + if (!nodeCommitInfo.IsEmptyBlockMarker && !nodeCommitInfo.Node.IsBoundaryProofNode) { TrieNode node = nodeCommitInfo.Node!; @@ -345,6 +345,20 @@ internal byte[] LoadRlp(Keccak keccak, IKeyValueStore? keyValueStore) public byte[] LoadRlp(Keccak keccak) => LoadRlp(keccak, null); + public bool IsPersisted(Keccak keccak) + { + byte[]? rlp = _currentBatch?[keccak.Bytes] ?? _keyValueStore[keccak.Bytes]; + + if (rlp is null) + { + return false; + } + + Metrics.LoadedFromDbNodesCount++; + + return true; + } + public IReadOnlyTrieStore AsReadOnly(IKeyValueStore? keyValueStore) { return new ReadOnlyTrieStore(this, keyValueStore); diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index 049ffa9994b..4de47779997 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -38,6 +38,7 @@ public partial class TrieNode public int Id = Interlocked.Increment(ref _idCounter); #endif + public bool IsBoundaryProofNode { get; set; } private TrieNode? _storageRoot; private static object _nullNode = new(); @@ -65,7 +66,7 @@ public partial class TrieNode public Keccak? Keccak { get; internal set; } - public byte[]? FullRlp { get; private set; } + public byte[]? FullRlp { get; internal set; } public NodeType NodeType { get; private set; } @@ -194,10 +195,11 @@ public TrieNode(NodeType nodeType, Keccak keccak) } } - public TrieNode(NodeType nodeType, byte[] rlp) + public TrieNode(NodeType nodeType, byte[] rlp, bool isDirty = false) { NodeType = nodeType; FullRlp = rlp; + IsDirty = isDirty; _rlpStream = rlp.AsRlpStream(); } @@ -290,6 +292,7 @@ public void ResolveNode(ITrieNodeResolver tree) // a hack to set internally and still verify attempts from the outside // after the code is ready we should just add proper access control for methods from the outside and inside + bool isDirtyActual = IsDirty; IsDirty = true; if (isExtension) @@ -304,7 +307,7 @@ public void ResolveNode(ITrieNodeResolver tree) Value = _rlpStream.DecodeByteArray(); } - IsDirty = false; + IsDirty = isDirtyActual; } else { @@ -342,6 +345,29 @@ public void ResolveKey(ITrieNodeResolver tree, bool isRoot) } } + public bool TryResolveStorageRootHash(ITrieNodeResolver resolver, out Keccak? storageRootHash) + { + storageRootHash = null; + + if (IsLeaf) + { + try + { + storageRootHash = _accountDecoder.DecodeStorageRootOnly(Value.AsRlpStream()); + if (storageRootHash is not null && storageRootHash != Keccak.EmptyTreeHash) + { + return true; + } + } + catch + { + return false; + } + } + + return false; + } + internal byte[] RlpEncode(ITrieNodeResolver tree) { byte[] rlp = _nodeDecoder.Encode(tree, this); @@ -355,6 +381,16 @@ internal byte[] RlpEncode(ITrieNodeResolver tree) return rlp; } + public object GetData(int index) + { + if(index > _data.Length - 1) + { + return null; + } + + return _data[index]; + } + public Keccak? GetChildHash(int i) { if (_rlpStream is null) diff --git a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs index 8ca32418785..551179f39f8 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs @@ -15,6 +15,7 @@ // along with the Nethermind. If not, see . using FluentAssertions; +using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Specs; @@ -23,6 +24,7 @@ using Nethermind.Db; using Nethermind.Logging; using Nethermind.Specs; +using NSubstitute; using NUnit.Framework; namespace Nethermind.TxPool.Test @@ -35,6 +37,7 @@ public class ReceiptStorageTests private ISpecProvider _specProvider; private IEthereumEcdsa _ethereumEcdsa; private IReceiptStorage _persistentStorage; + private IReceiptFinder _receiptFinder; private IReceiptStorage _inMemoryStorage; public ReceiptStorageTests(bool useEip2718) @@ -49,6 +52,7 @@ public void Setup() _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId, LimboLogs.Instance); ReceiptsRecovery receiptsRecovery = new(_ethereumEcdsa, _specProvider); _persistentStorage = new PersistentReceiptStorage(new MemColumnsDb(), _specProvider, receiptsRecovery); + _receiptFinder = new FullInfoReceiptFinder(_persistentStorage, receiptsRecovery, Substitute.For()); _inMemoryStorage = new InMemoryReceiptStorage(); } @@ -70,16 +74,32 @@ public void should_not_update_lowest_when_not_needed_in_memory() [Test] public void should_add_and_fetch_receipt_from_in_memory_storage() - => TestAddAndGetReceipt(_inMemoryStorage, false); + => TestAddAndGetReceipt(_inMemoryStorage); [Test] public void should_add_and_fetch_receipt_from_persistent_storage() - => TestAddAndGetReceipt(_persistentStorage, true); + => TestAddAndGetReceipt(_persistentStorage, _receiptFinder); [Test] public void should_add_and_fetch_receipt_from_persistent_storage_with_eip_658() => TestAddAndGetReceiptEip658(_persistentStorage); + [Test] + public void should_not_throw_if_receiptFinder_asked_for_not_existing_receipts_by_block() + { + Block block = Build.A.Block.WithNumber(0).WithTransactions(5, _specProvider).TestObject; + TxReceipt[] receipts = _receiptFinder.Get(block); + receipts.Should().BeEmpty(); + } + + [Test] + public void should_not_throw_if_receiptFinder_asked_for_not_existing_receipts_by_hash() + { + Block block = Build.A.Block.WithNumber(0).WithTransactions(5, _specProvider).TestObject; + TxReceipt[] receipts = _receiptFinder.Get(block.Hash); + receipts.Should().BeEmpty(); + } + private void TestAddAndCheckLowest(IReceiptStorage storage, bool updateLowest) { var transaction = GetSignedTransaction(); @@ -94,8 +114,11 @@ private void TestAddAndCheckLowest(IReceiptStorage storage, bool updateLowest) storage.LowestInsertedReceiptBlockNumber.Should().Be(updateLowest ? (long?)0 : null); } - private void TestAddAndGetReceipt(IReceiptStorage storage, bool recoverSender) + private void TestAddAndGetReceipt(IReceiptStorage storage, IReceiptFinder receiptFinder = null) { + bool recoverSender = receiptFinder is not null; + receiptFinder ??= storage; + var transaction = GetSignedTransaction(); transaction.SenderAddress = null; var block = GetBlock(transaction); @@ -103,7 +126,7 @@ private void TestAddAndGetReceipt(IReceiptStorage storage, bool recoverSender) storage.Insert(block, receipt); var blockHash = storage.FindBlockHash(transaction.Hash); blockHash.Should().Be(block.Hash); - var fetchedReceipt = storage.Get(block).ForTransaction(transaction.Hash); + var fetchedReceipt = receiptFinder.Get(block).ForTransaction(transaction.Hash); receipt.StatusCode.Should().Be(fetchedReceipt.StatusCode); receipt.PostTransactionState.Should().Be(fetchedReceipt.PostTransactionState); receipt.TxHash.Should().Be(transaction.Hash); diff --git a/src/int256 b/src/int256 index 548d4aee373..89ab8c0ea74 160000 --- a/src/int256 +++ b/src/int256 @@ -1 +1 @@ -Subproject commit 548d4aee373b48c68609ef1eb909673a76cde715 +Subproject commit 89ab8c0ea74c8cdea305afb97abbb16ad5e62c28 diff --git a/src/rocksdb-sharp b/src/rocksdb-sharp index fe9c977e6a7..dda0e1e32c0 160000 --- a/src/rocksdb-sharp +++ b/src/rocksdb-sharp @@ -1 +1 @@ -Subproject commit fe9c977e6a7752cb1ea8013832c87a271c7245bf +Subproject commit dda0e1e32c04cb1a1dfbd6232ab7b65c5d046caa