From 82b816a5bc334793b13deb4833e413a01791b642 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 11 Dec 2023 07:59:43 +0000 Subject: [PATCH] Improved Json Serialization (#6152) * System.Text.Json.Serialization * System.Text.Deserialization * Fixes * lint * Fixes * Fix * Fixes * Fixes * Lint * Fix invalid Json * u8 * Fixes * Fixes * Fix * Don't go via exception for short reverts * Fix * Fix * Multi-doc batch * Lint * Merge conflict * Fixes * Lint * Fix tests * Fix test * Tidy up * Turn of sync IO * Async * Don't double Advance * Async wrapper stream use * Fix build break * Merge issues * Compile issues * File scoped namespaces * Formatting * Merge fixes * Test Merge fixes * lang ver * Purge Newtonsoft * Fix evm tests * Fix parentBeaconBlockRootString serialization * Change type for serialization * Ignore syncing * Capitalised bools are invalid json * Capitalised bools are invalid json * Fix merge conflict * Fix data serialization * Fix trace entry * Merge conflicts * Formatting * Missed merge * whitespace * Fix EthereumTests Json * Ethereum tests * Fix hex prefix * Fix difficultly test * Add serializer options * Fix abi tests * Merge conflict * Feedback * revert to previous * Remove Netwonsoft * cosmetics * Pipeline Json responses * Merge fixes * Whitespace * fix test * Send multiple messages * Optional stream closing * Case insensitive * Update Nethermind.Serialization.Json.csproj * Whitespace * Update JavaScriptObjectConverter.cs * Feedback * Undo AI * Put orders back * Feedback * Whitespace * Use static constructor * Remove irrelevant test parameter * Add BigIntegerConverterTests back * Use FrozenDictionary * Deserialize collection to ArrayPoolList using GetArrayLength for initial capacity. Dispose it later. Move WhiteSpace to utf8 * simplify * Throw more explicit exception * name cleanup * Merge conflicts * Merge conflict * Add comments * more comments * Consolidate Disposable * Use FrozenDictionary for RpcLookup * Case sensitive * Be consistent on parsing type * Use correct type in test * Optimizations * Optimizations * Optimize * Optimize * Vectorize FromUtf8 hex * Optimization * Optimize * Parallel deserialize * Revert "Parallel deserialize" This reverts commit 94b08e3a69d9d6129440c30cb1e850d5345b9c0b. * Use using * Fix check * Parallel deserialize * Increase parallelThreshold * Use correct length * Cache Reflection Parameters * Fix tracing output * Match geth output * Turn AllowSynchronousIO back on * Revert object type arrays --------- Co-authored-by: lukasz.rozmej --- src/Nethermind/Directory.Packages.props | 2 +- src/Nethermind/Ethereum.Abi.Test/AbiTest.cs | 8 +- src/Nethermind/Ethereum.Abi.Test/Tests.cs | 11 +- .../Ethereum.Basic.Test/TransactionTests.cs | 2 + ...hereum.Blockchain.Block.Legacy.Test.csproj | 1 - .../Ethereum.Blockchain.Legacy.Test.csproj | 1 - .../TransactionJsonTest.cs | 2 +- .../DifficultyTestHexJson.cs | 2 + .../DifficultyTestJson.cs | 2 + .../Ethereum.HexPrefix.Test/HexPrefixTests.cs | 7 +- .../KeyAddressTests.cs | 5 +- .../KeyStoreJsonTests.cs | 6 +- .../Ethereum.PoW.Test/EthashTests.cs | 11 +- .../Ethereum.Test.Base/AccessListJson.cs | 3 - .../Ethereum.Test.Base/EthereumTestResult.cs | 3 +- .../GeneralStateTestJson.cs | 4 +- .../Ethereum.Test.Base/TestBlockJson.cs | 5 +- .../Ethereum.Test.Base/TestLoader.cs | 46 +- .../TransactionTests.cs | 5 +- .../Ethereum.Trie.Test/TrieTests.cs | 2 + src/Nethermind/Ethereum.VM.Test/AbiTests.cs | 1 + .../Json/AbiParameterConverterTests.cs | 36 +- .../Nethermind.Abi.Test.csproj | 1 - src/Nethermind/Nethermind.Abi/AbiAddress.cs | 12 +- src/Nethermind/Nethermind.Abi/AbiArray.cs | 15 +- .../Nethermind.Abi/AbiBaseDescription.cs | 5 + src/Nethermind/Nethermind.Abi/AbiBool.cs | 5 - src/Nethermind/Nethermind.Abi/AbiBytes.cs | 7 + .../Nethermind.Abi/AbiDefinition.cs | 4 + .../Nethermind.Abi/AbiDefinitionConverter.cs | 86 + .../Nethermind.Abi/AbiDescriptionType.cs | 5 + .../Nethermind.Abi/AbiDynamicBytes.cs | 12 +- .../Nethermind.Abi/AbiEventParameter.cs | 4 + src/Nethermind/Nethermind.Abi/AbiInt.cs | 10 +- src/Nethermind/Nethermind.Abi/AbiParameter.cs | 54 + .../Nethermind.Abi/AbiParameterConverter.cs | 178 ++ src/Nethermind/Nethermind.Abi/AbiString.cs | 5 - src/Nethermind/Nethermind.Abi/AbiTuple.cs | 8 + .../Nethermind.Abi/AbiType.TypeMapping.cs | 49 +- src/Nethermind/Nethermind.Abi/AbiType.cs | 133 ++ src/Nethermind/Nethermind.Abi/AbiUInt.cs | 16 +- .../Nethermind.Abi/StateMutability.cs | 4 + ...sts.TestAccountAbstractionRpcBlockchain.cs | 3 +- .../AccountAbstractionRpcModuleTests.cs | 810 ++++----- .../UserOperationSubscribeTests.cs | 9 +- .../AccountAbstractionPlugin.cs | 3 +- .../UserOperationSubscriptionParam.cs | 8 +- .../Validators/ContractBasedValidatorTests.cs | 1161 ++++++------- .../Specs/empty_accounts_and_storages.json | 1 - .../Contracts/Json/AbiDefinitionConverter.cs | 89 - .../Contracts/Json/AbiDefinitionParser.cs | 60 +- .../Json/AbiDefinitionParserExtensions.cs | 13 - .../Contracts/Json/AbiParameterConverter.cs | 171 -- .../Contracts/Json/AbiTypeConverter.cs | 24 - .../Contracts/Json/IAbiTypeFactory.cs | 12 - .../Data/FileLocalDataSource.cs | 13 +- .../Find/BlockParameter.cs | 242 +++ .../FullPruning/PruningStatus.cs | 7 +- .../Nethermind.Blockchain/LogTraceDumper.cs | 13 +- src/Nethermind/Nethermind.Cli/Program.cs | 12 - .../Nethermind.Config.Test.csproj | 1 - .../Nethermind.Config/ConfigSourceHelper.cs | 7 +- .../Nethermind.Config/JsonConfigSource.cs | 38 +- .../Nethermind.Config.csproj | 1 - .../Nethermind.Consensus.Test.csproj | 1 - .../Nethermind.Core.Test/BlockHeaderTests.cs | 6 +- .../Nethermind.Core.Test/Builders/TestItem.cs | 9 +- .../Json/BigIntegerConverterTests.cs | 52 +- .../Json/ByteArrayConverterTests.cs | 12 +- .../Json/ConverterTestBase.cs | 22 +- ...erterTests.cs => Hash256ConverterTests.cs} | 14 +- .../Json/LongConverterTests.cs | 52 +- .../Json/NullableBigIntegerConverterTests.cs | 47 +- .../Json/NullableLongConverterTests.cs | 57 +- .../Json/NullableUInt256ConverterTests.cs | 29 +- .../Json/NullableUlongConverterTests.cs | 56 +- .../Json/UInt256ConverterTests.cs | 54 +- src/Nethermind/Nethermind.Core/Address.cs | 4 + .../Nethermind.Core/AddressConverter.cs | 30 + src/Nethermind/Nethermind.Core/Bloom.cs | 4 + .../Nethermind.Core/BloomConverter.cs | 29 + .../Nethermind.Core/ByteArrayConverter.cs | 117 ++ .../Collections/ThrowHelper.cs | 37 +- .../Nethermind.Core/Crypto/Hash256.cs | 3 + .../Crypto/Hash256Converter.cs | 29 + .../Nethermind.Core/Crypto/PublicKey.cs | 4 + .../Crypto/PublicKeyConverter.cs | 41 + .../Nethermind.Core/Extensions/Bytes.cs | 150 +- .../Extensions/HexConverter.cs | 85 + .../Extensions/Int64Extensions.cs | 22 +- .../LowerCaseJsonStringEnumConverter.cs | 24 + .../Nethermind.Core/Nethermind.Core.csproj | 1 - .../Nethermind.Core/TypeDiscovery.cs | 1 - src/Nethermind/Nethermind.Core/Withdrawal.cs | 4 +- .../Nethermind.EthStats.csproj | 1 - .../Senders/MessageSender.cs | 11 +- .../Tracing/GethLikeJavaScriptTracerTests.cs | 3 +- .../Tracing/GethLikeTxFileTracerTests.cs | 28 +- .../Tracing/GethStyle/GethLikeTxTrace.cs | 6 +- .../GethStyle}/GethLikeTxTraceConverter.cs | 51 +- .../GethLikeTxTraceJsonLinesConverter.cs | 2 +- .../Tracing/GethStyle/GethTraceOptions.cs | 27 +- .../GethStyle/GethTxMemoryTraceEntry.cs | 16 +- .../Tracing/GethStyle/GethTxTraceEntry.cs | 24 +- .../Tracing/GethStyle/JavaScript/Engine.cs | 6 +- .../JavaScript/GethLikeJavaScriptTrace.cs | 5 + .../GethLikeJavaScriptTraceConverter.cs | 16 +- .../ParityStyle/ParityAccountStateChange.cs | 3 + .../ParityAccountStateChangeJsonConverter.cs | 169 ++ .../Tracing/ParityStyle/ParityAction.cs | 3 + .../Tracing/ParityStyle/ParityLikeTxTrace.cs | 4 + .../ParityStyle/ParityTraceActionConverter.cs | 207 +++ .../{ParityResult.cs => ParityTraceResult.cs} | 3 + .../ParityStyle/ParityTraceResultConverter.cs | 87 + .../ParityStyle/ParityVmOperationTrace.cs | 3 + .../ParityVmOperationTraceConverter.cs | 83 + .../Tracing/ParityStyle/ParityVmTrace.cs | 15 +- .../ParityStyle/ParityVmTraceConverter.cs | 27 + .../Nethermind.Evm/Tracing/TraceMemory.cs | 10 +- .../Nethermind.Evm/Tracing/TraceStack.cs | 8 +- .../Nethermind.Facade/Eth/SyncingResult.cs | 4 +- .../Eth/SyncingResultJsonConverter.cs | 42 + .../Filters/FilterManager.cs | 8 +- .../Nethermind.Facade/Filters/LogFinder.cs | 6 +- .../Nethermind.Init/InitializeStateDb.cs | 5 +- .../Steps/RegisterRpcModules.cs | 1 + ...nsusHelperTests.FileConsensusDataSource.cs | 1 + .../ConsensusHelperTests.JsonRpcDataSource.cs | 104 +- ...s.ParityLikeBlockTraceJsonRpcDataSource.cs | 1 + .../ConsensusHelperTests.cs | 82 +- .../Data/BlockParameterConverterTests.cs | 42 +- .../Data/IdConverterTests.cs | 12 +- .../Data/SerializationTestBase.cs | 21 +- .../JsonRpcProcessorExtensions.cs | 5 +- .../JsonRpcProcessorTests.cs | 628 +++---- .../JsonRpcServiceTests.cs | 355 ++-- .../JsonRpcSocketsClientTests.cs | 6 +- .../Modules/AdminModuleTests.cs | 13 +- .../Modules/DebugModuleTests.cs | 65 +- .../Modules/Eth/EthRpcModuleTests.cs | 14 +- .../Modules/Eth/FilterTests.cs | 14 +- .../Modules/SubscribeModuleTests.cs | 57 +- .../Modules/TestRpcBlockchain.cs | 6 +- .../Modules/TestRpcModuleProvider.cs | 9 +- .../ParityAccountChangeConverterTests.cs | 25 +- .../Trace/ParityTraceAddressConverterTests.cs | 2 +- .../ParityTxTraceFromReplayConverterTest.cs | 2 +- .../Modules/TraceRpcModuleTests.cs | 1537 ++++++++--------- .../Nethermind.JsonRpc.Test.csproj | 1 - .../Nethermind.JsonRpc.Test/RpcTest.cs | 74 +- .../TraceSerializerTests.cs | 6 +- .../ParityLikeTraceSerializer.cs | 2 +- .../ParityTraceActionCreationConverter.cs | 12 - .../Client/JsonRpcResponse.cs | 18 +- .../Converters/TxReceiptConverter.cs | 22 +- .../Data/AccessListForRpc.cs | 2 +- .../Data/AccessListItemForRpc.cs | 9 +- .../Data/BlockParameterConverter.cs | 149 -- .../Nethermind.JsonRpc/Data/ProofConverter.cs | 95 - .../Nethermind.JsonRpc/Data/ReceiptForRpc.cs | 45 +- .../Data/TransactionForRpc.cs | 46 +- .../Data/TransactionForRpcWithTraceTypes.cs | 36 + src/Nethermind/Nethermind.JsonRpc/Error.cs | 9 +- .../Nethermind.JsonRpc/IJsonRpcParam.cs | 4 +- .../Nethermind.JsonRpc/IJsonRpcProcessor.cs | 12 +- .../Nethermind.JsonRpc/IJsonRpcService.cs | 15 +- .../Nethermind.JsonRpc/JsonRpcLocalStats.cs | 2 +- .../Nethermind.JsonRpc/JsonRpcProcessor.cs | 499 +++--- .../Nethermind.JsonRpc/JsonRpcRequest.cs | 10 +- .../Nethermind.JsonRpc/JsonRpcResponse.cs | 43 +- .../Nethermind.JsonRpc/JsonRpcService.cs | 239 +-- .../Nethermind.JsonRpc/JsonRpcUrl.cs | 3 +- .../Modules/Admin/AdminRpcModule.cs | 2 +- .../Modules/Admin/EthProtocolInfo.cs | 13 +- .../Modules/Admin/IAdminRpcModule.cs | 101 +- .../Modules/Admin/NodeInfo.cs | 95 +- .../Modules/Admin/PortsInfo.cs | 7 +- .../Modules/DebugModule/DebugModuleFactory.cs | 7 +- .../DebugModule/JavaScriptObjectConverter.cs | 66 - .../Modules/Eth/BlockForRpc.cs | 25 +- .../Modules/Eth/EthModuleFactory.cs | 11 +- .../Modules/Eth/EthRpcModule.cs | 5 +- .../Eth/FeeHistory/FeeHistoryOracle.cs | 2 +- .../Nethermind.JsonRpc/Modules/Eth/Filter.cs | 74 +- .../Modules/Eth/SyncingResultConverter.cs | 33 - .../Modules/Eth/TransactionsOption.cs | 45 +- .../Modules/IRpcModuleFactory.cs | 5 +- .../Modules/IRpcModuleProvider.cs | 11 +- .../Modules/ModuleFactoryBase.cs | 11 +- .../Modules/NullModuleProvider.cs | 10 +- .../Modules/Parity/EthProtocolInfo.cs | 9 +- .../Modules/Parity/ParityNetPeers.cs | 11 +- .../Modules/Parity/ParityTransaction.cs | 15 +- .../Modules/Parity/PeerInfo.cs | 13 +- .../Modules/Parity/PeerNetworkInfo.cs | 7 +- .../Modules/Proof/ProofModuleFactory.cs | 10 +- .../Modules/RpcMethodFilter.cs | 7 +- .../Modules/RpcModuleProvider.cs | 60 +- .../Subscribe/JsonRpcSubscriptionResponse.cs | 19 +- .../Subscribe/JsonRpcSubscriptionResult.cs | 13 +- .../Modules/Subscribe/SubscribeRpcModule.cs | 5 +- .../Modules/Subscribe/SubscriptionFactory.cs | 11 +- .../Modules/Subscribe/SyncingSubscription.cs | 3 + .../Modules/Trace/JsonWriterExtensions.cs | 22 - .../ParityAccountStateChangeConverter.cs | 169 -- .../Trace/ParityTraceActionConverter.cs | 86 - .../Trace/ParityTraceAddressConverter.cs | 48 - .../Trace/ParityTraceResultConverter.cs | 37 - .../Modules/Trace/ParityTxTraceFromReplay.cs | 135 ++ .../Trace/ParityTxTraceFromReplayConverter.cs | 101 -- .../Modules/Trace/ParityTxTraceFromStore.cs | 5 +- .../Trace/ParityVmOperationTraceConverter.cs | 90 - .../Modules/Trace/ParityVmTraceConverter.cs | 27 - .../Modules/Trace/TraceFilterForRpc.cs | 7 +- .../Modules/Trace/TraceModuleFactory.cs | 15 +- ...ransactionForRpcWithTraceTypesConverter.cs | 29 - .../Nethermind.JsonRpc.csproj | 3 +- .../Nethermind.JsonRpc/StringExtensions.cs | 12 - .../Nethermind.JsonRpc/Utils/JTokenUtils.cs | 22 - .../WebSockets/JsonRpcSocketsClient.cs | 267 ++- .../WebSockets/JsonRpcWebSocketsModule.cs | 2 +- .../Nethermind.KeyStore/CipherParams.cs | 5 +- src/Nethermind/Nethermind.KeyStore/Crypto.cs | 14 +- .../Nethermind.KeyStore/KdfParams.cs | 21 +- .../Nethermind.KeyStore/KeyStoreItem.cs | 11 +- .../EngineModuleTests.V1.cs | 11 +- .../EngineModuleTests.V3.cs | 15 +- .../PayloadPreparationService.cs | 2 +- .../Data/ExecutionPayload.cs | 8 +- .../Data/ExecutionPayloadBodyV1Result.cs | 8 +- .../Data/ExecutionPayloadV3.cs | 2 - .../Data/ForkchoiceUpdatedV1Result.cs | 5 +- .../Data/PayloadStatusV1.cs | 8 +- .../Handlers/ExchangeCapabilitiesHandler.cs | 2 +- .../Nethermind.Mev.Test.csproj | 1 - .../StaticNodes/StaticNodesManager.cs | 8 +- .../Nethermind.Overseer.Test/AuRaTest.cs | 1 + .../Framework/TestContextBase.cs | 5 +- .../EthereumRunnerTests.cs | 2 + .../Ethereum/Steps/StartRpc.cs | 10 +- .../JsonRpc/JsonRpcIpcRunner.cs | 3 + .../Nethermind.Runner/JsonRpc/Startup.cs | 84 +- .../AddressConverter.cs | 24 - .../BigIntegerConverter.cs | 78 +- .../BloomConverter.cs | 24 - .../BooleanConverter.cs | 62 + .../BufferSegment.cs | 138 ++ .../BufferSegmentStack.cs | 87 + .../ByteArray32Converter.cs | 52 +- .../ByteArrayConverter.cs | 35 - .../DictionaryAddressKey.cs | 124 ++ .../DoubleConverter.cs | 79 + .../EthereumJsonSerializer.cs | 232 +-- .../ForcedNumberConversion.cs | 2 +- .../IJsonSerializer.cs | 15 +- .../IdConverter.cs | 60 +- .../IntConverter.cs | 76 + .../JavaScriptObjectConverter.cs | 80 + .../KeccakConverter.cs | 31 - .../LongConverter.cs | 131 +- .../LongRawJsonConverter.cs | 44 + .../LowerCaseNamingStrategy.cs | 12 - .../MemoryByteConverter.cs | 25 +- .../Nethermind.Serialization.Json.csproj | 9 +- .../NullableBigIntegerConverter.cs | 28 +- .../NullableIntConverter.cs | 43 + .../NullableLongConvertercs.cs | 63 +- .../NullableUInt256Converter.cs | 39 +- .../NullableULongConverter.cs | 41 +- .../PublicKeyConverter.cs | 23 - .../StorageCellIndexConverter.cs | 62 +- .../StreamPipeWriter.cs | 428 +++++ .../TxTypeConverter.cs | 30 +- .../UInt256Converter.cs | 149 +- .../ULongConverter.cs | 141 +- .../WebSocketExtensionsTests.cs | 13 +- .../Nethermind.Sockets/SocketClient.cs | 13 +- .../ChainSpecStyle/ChainSpecLoader.cs | 29 +- .../Json/BlockRewardJsonConverter.cs | 53 +- .../ChainSpecStyle/Json/BuiltInJson.cs | 6 +- .../ChainSpecStyle/Json/ChainSpecJson.cs | 311 ++-- .../Json/ChainSpecParamsJson.cs | 6 +- .../Json/StepDurationJsonConverter.cs | 54 +- .../Nethermind.State/Proofs/AccountProof.cs | 88 + .../Nethermind.State/Proofs/StorageProof.cs | 2 +- .../Nethermind.Test.Runner.csproj | 1 - .../StateTestTxTraceResult.cs | 10 +- .../StateTestTxTraceState.cs | 5 +- .../StateTxTraceEntry.cs | 24 +- 289 files changed, 8510 insertions(+), 6291 deletions(-) create mode 100644 src/Nethermind/Nethermind.Abi/AbiDefinitionConverter.cs create mode 100644 src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs delete mode 100644 src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionConverter.cs delete mode 100644 src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParserExtensions.cs delete mode 100644 src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiParameterConverter.cs delete mode 100644 src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiTypeConverter.cs delete mode 100644 src/Nethermind/Nethermind.Blockchain/Contracts/Json/IAbiTypeFactory.cs rename src/Nethermind/Nethermind.Core.Test/Json/{KeccakConverterTests.cs => Hash256ConverterTests.cs} (53%) create mode 100644 src/Nethermind/Nethermind.Core/AddressConverter.cs create mode 100644 src/Nethermind/Nethermind.Core/BloomConverter.cs create mode 100644 src/Nethermind/Nethermind.Core/ByteArrayConverter.cs create mode 100644 src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs create mode 100644 src/Nethermind/Nethermind.Core/Crypto/PublicKeyConverter.cs create mode 100644 src/Nethermind/Nethermind.Core/LowerCaseJsonStringEnumConverter.cs rename src/Nethermind/{Nethermind.JsonRpc/Modules/DebugModule => Nethermind.Evm/Tracing/GethStyle}/GethLikeTxTraceConverter.cs (67%) create mode 100644 src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChangeJsonConverter.cs create mode 100644 src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceActionConverter.cs rename src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/{ParityResult.cs => ParityTraceResult.cs} (80%) create mode 100644 src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceResultConverter.cs create mode 100644 src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTraceConverter.cs create mode 100644 src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTraceConverter.cs create mode 100644 src/Nethermind/Nethermind.Facade/Eth/SyncingResultJsonConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityTraceActionCreationConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Data/BlockParameterConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Data/ProofConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/JavaScriptObjectConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SyncingResultConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/JsonWriterExtensions.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityAccountStateChangeConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceActionConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceAddressConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceResultConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplayConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmOperationTraceConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmTraceConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TransactionForRpcWithTraceTypesConverter.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/StringExtensions.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Utils/JTokenUtils.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/AddressConverter.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/BloomConverter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/BufferSegment.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/BufferSegmentStack.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/ByteArrayConverter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/DictionaryAddressKey.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/JavaScriptObjectConverter.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/KeccakConverter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/LowerCaseNamingStrategy.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/NullableIntConverter.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/PublicKeyConverter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/StreamPipeWriter.cs diff --git a/src/Nethermind/Directory.Packages.props b/src/Nethermind/Directory.Packages.props index cc43c69dd51..8e45df73b9f 100644 --- a/src/Nethermind/Directory.Packages.props +++ b/src/Nethermind/Directory.Packages.props @@ -53,7 +53,6 @@ - @@ -79,6 +78,7 @@ + diff --git a/src/Nethermind/Ethereum.Abi.Test/AbiTest.cs b/src/Nethermind/Ethereum.Abi.Test/AbiTest.cs index d6ec838ba9a..0ef38c423b9 100644 --- a/src/Nethermind/Ethereum.Abi.Test/AbiTest.cs +++ b/src/Nethermind/Ethereum.Abi.Test/AbiTest.cs @@ -1,19 +1,19 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Ethereum.Abi.Test { public class AbiTest { - [JsonProperty(PropertyName = "args")] + [JsonPropertyName("args")] public object[] Args { get; set; } - [JsonProperty(PropertyName = "result")] + [JsonPropertyName("result")] public string Result { get; set; } - [JsonProperty(PropertyName = "types")] + [JsonPropertyName("types")] public string[] Types { get; set; } } } diff --git a/src/Nethermind/Ethereum.Abi.Test/Tests.cs b/src/Nethermind/Ethereum.Abi.Test/Tests.cs index c8943bed550..3edf18e0567 100644 --- a/src/Nethermind/Ethereum.Abi.Test/Tests.cs +++ b/src/Nethermind/Ethereum.Abi.Test/Tests.cs @@ -5,11 +5,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; + using FluentAssertions; using Nethermind.Abi; using Nethermind.Core.Extensions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NUnit.Framework; namespace Ethereum.Abi.Test @@ -56,7 +57,7 @@ public void Test_abi_encoding() } } - Dictionary tests = JsonConvert.DeserializeObject>(text); + Dictionary tests = JsonSerializer.Deserialize>(text); foreach ((string testName, AbiTest abiTest) in tests) { AbiSignature signature = new( @@ -71,9 +72,9 @@ public void Test_abi_encoding() public object JsonToObject(object jsonObject) { - if (jsonObject is JArray array) + if (jsonObject is JsonArray array) { - return array.Select(t => t.Value()).ToArray(); + return array.Select(t => t.GetValue()).ToArray(); } return jsonObject; diff --git a/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs index 3d4fdb9fa1f..1ce9cf6f735 100644 --- a/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs +++ b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs @@ -7,6 +7,8 @@ using System.IO; using System.Linq; using System.Numerics; +using System.Text.Json.Serialization; + using Ethereum.Test.Base; using Nethermind.Core; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/Ethereum.Blockchain.Block.Legacy.Test.csproj b/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/Ethereum.Blockchain.Block.Legacy.Test.csproj index 5d835930958..02030aabeba 100644 --- a/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/Ethereum.Blockchain.Block.Legacy.Test.csproj +++ b/src/Nethermind/Ethereum.Blockchain.Block.Legacy.Test/Ethereum.Blockchain.Block.Legacy.Test.csproj @@ -10,7 +10,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - all diff --git a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Ethereum.Blockchain.Legacy.Test.csproj b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Ethereum.Blockchain.Legacy.Test.csproj index 0a8fff7774d..02030aabeba 100644 --- a/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Ethereum.Blockchain.Legacy.Test.csproj +++ b/src/Nethermind/Ethereum.Blockchain.Legacy.Test/Ethereum.Blockchain.Legacy.Test.csproj @@ -15,7 +15,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/src/Nethermind/Ethereum.Blockchain.Test/TransactionJsonTest.cs b/src/Nethermind/Ethereum.Blockchain.Test/TransactionJsonTest.cs index 21ab7535186..1467ff8ed39 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/TransactionJsonTest.cs +++ b/src/Nethermind/Ethereum.Blockchain.Test/TransactionJsonTest.cs @@ -19,7 +19,7 @@ public class TransactionJsonTest : GeneralStateTestBase public void Can_load_access_lists() { const string lists = - "{\"accessLists\": [[{address: \"0x0001020304050607080900010203040506070809\", storageKeys: [\"0x00\", \"0x01\"]}]]}"; + "{\"accessLists\": [[{\"address\": \"0x0001020304050607080900010203040506070809\", \"storageKeys\": [\"0x00\", \"0x01\"]}]]}"; EthereumJsonSerializer serializer = new EthereumJsonSerializer(); TransactionJson txJson = serializer.Deserialize(lists); diff --git a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestHexJson.cs b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestHexJson.cs index 217872a15fd..fc37faf2cae 100644 --- a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestHexJson.cs +++ b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestHexJson.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + namespace Ethereum.Difficulty.Test { public class DifficultyTestHexJson diff --git a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs index fa85071ab54..9e1108731cb 100644 --- a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs +++ b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyTestJson.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + namespace Ethereum.Difficulty.Test { public class DifficultyTestJson diff --git a/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs b/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs index 4eecc33ccab..ceb9e13da4d 100644 --- a/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs +++ b/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; + using Ethereum.Test.Base; using Nethermind.Core.Extensions; + using NUnit.Framework; namespace Ethereum.HexPrefix.Test @@ -18,7 +21,7 @@ public static IEnumerable LoadTests() { return TestLoader.LoadFromFile, HexPrefixTest>( "hexencodetest.json", - c => c.Select(p => new HexPrefixTest(p.Key, p.Value.Seq, p.Value.Term, p.Value.Out))); + c => c.Select(p => new HexPrefixTest(p.Key, p.Value.Seq.Select(x => (byte)x).ToArray(), p.Value.Term, p.Value.Out))); } [TestCaseSource(nameof(LoadTests))] @@ -36,7 +39,7 @@ public void Test(HexPrefixTest test) private class HexPrefixTestJson { - public byte[] Seq { get; set; } + public int[] Seq { get; set; } public bool Term { get; set; } public string Out { get; set; } } diff --git a/src/Nethermind/Ethereum.KeyAddress.Test/KeyAddressTests.cs b/src/Nethermind/Ethereum.KeyAddress.Test/KeyAddressTests.cs index 7ad178fa662..0384a6b5a4b 100644 --- a/src/Nethermind/Ethereum.KeyAddress.Test/KeyAddressTests.cs +++ b/src/Nethermind/Ethereum.KeyAddress.Test/KeyAddressTests.cs @@ -5,13 +5,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json.Serialization; + using Ethereum.Test.Base; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Crypto; using Nethermind.Int256; using Nethermind.Logging; -using Newtonsoft.Json; using NUnit.Framework; namespace Ethereum.KeyAddress.Test @@ -96,7 +97,7 @@ private class SigOfEmptyString private class KeyAddressTestJson { - [JsonProperty("sig_of_emptystring")] + [JsonPropertyName("sig_of_emptystring")] public SigOfEmptyString Signature { get; set; } public string Seed { get; set; } diff --git a/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs b/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs index 540bff172d9..dd4baea543d 100644 --- a/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs +++ b/src/Nethermind/Ethereum.KeyStore.Test/KeyStoreJsonTests.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Security; +using System.Text.Json.Serialization; + using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Crypto; @@ -12,7 +14,7 @@ using Nethermind.KeyStore.Config; using Nethermind.Logging; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Ethereum.KeyStore.Test @@ -129,7 +131,7 @@ private class KeyStoreTestsModel private class KeyStoreTestModel { - [JsonProperty(PropertyName = "Json")] + [JsonPropertyName("Json")] public KeyStoreItem KeyData { get; set; } public string Password { get; set; } public string Priv { get; set; } diff --git a/src/Nethermind/Ethereum.PoW.Test/EthashTests.cs b/src/Nethermind/Ethereum.PoW.Test/EthashTests.cs index 0d6d02f8b26..065ec1da9a1 100644 --- a/src/Nethermind/Ethereum.PoW.Test/EthashTests.cs +++ b/src/Nethermind/Ethereum.PoW.Test/EthashTests.cs @@ -7,6 +7,8 @@ using System.IO; using System.Linq; using System.Numerics; +using System.Text.Json.Serialization; + using Ethereum.Test.Base; using Nethermind.Consensus.Ethash; using Nethermind.Core; @@ -15,7 +17,6 @@ using Nethermind.Crypto; using Nethermind.Logging; using Nethermind.Serialization.Rlp; -using Newtonsoft.Json; using NUnit.Framework; namespace Ethereum.PoW.Test @@ -103,16 +104,16 @@ private class EthashTestJson public string Header { get; set; } public string Seed { get; set; } - [JsonProperty("cache_size")] + [JsonPropertyName("cache_size")] public int CacheSize { get; set; } - [JsonProperty("full_size")] + [JsonPropertyName("full_size")] public int FullSize { get; set; } - [JsonProperty("header_hash")] + [JsonPropertyName("header_hash")] public string HeaderHash { get; set; } - [JsonProperty("cache_hash")] + [JsonPropertyName("cache_hash")] public string CacheHash { get; set; } public string Result { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/AccessListJson.cs b/src/Nethermind/Ethereum.Test.Base/AccessListJson.cs index e022630f935..71e43a03691 100644 --- a/src/Nethermind/Ethereum.Test.Base/AccessListJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/AccessListJson.cs @@ -2,16 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; -using Newtonsoft.Json; namespace Ethereum.Test.Base { public class AccessListItemJson { - [JsonProperty("address")] public Address Address { get; set; } - [JsonProperty("storageKeys")] public byte[][] StorageKeys { get; set; } } } diff --git a/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs b/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs index 16fd3908bdb..893a3e391d8 100644 --- a/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs +++ b/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs @@ -1,8 +1,9 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + using Nethermind.Core.Crypto; -using Newtonsoft.Json; namespace Ethereum.Test.Base { diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralStateTestJson.cs b/src/Nethermind/Ethereum.Test.Base/GeneralStateTestJson.cs index f0347cf4425..40e779f109d 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralStateTestJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralStateTestJson.cs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Ethereum.Test.Base { public class GeneralStateTestJson { - [JsonProperty("_info")] + [JsonPropertyName("_info")] public GeneralStateTestInfoJson? Info { get; set; } public GeneralStateTestEnvJson? Env { get; set; } public Dictionary? Post { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs b/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs index aa907fe16d7..2454b75e495 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestBlockJson.cs @@ -1,7 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Ethereum.Test.Base { @@ -11,7 +12,7 @@ public class TestBlockJson public TestBlockHeaderJson[]? UncleHeaders { get; set; } public string? Rlp { get; set; } public LegacyTransactionJson[]? Transactions { get; set; } - [JsonProperty("expectException")] + [JsonPropertyName("expectException")] public string? ExpectedException { get; set; } } } diff --git a/src/Nethermind/Ethereum.Test.Base/TestLoader.cs b/src/Nethermind/Ethereum.Test.Base/TestLoader.cs index 7efd3420313..a3670fe5cba 100644 --- a/src/Nethermind/Ethereum.Test.Base/TestLoader.cs +++ b/src/Nethermind/Ethereum.Test.Base/TestLoader.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + using NUnit.Framework; namespace Ethereum.Test.Base @@ -17,30 +19,33 @@ public static class TestLoader { public static object PrepareInput(object input) { - string s = input as string; - if (s != null && s.StartsWith("#")) + if (input is string s && s.StartsWith("#")) { BigInteger bigInteger = BigInteger.Parse(s.Substring(1)); input = bigInteger; } - if (input is JArray) - { - input = ((JArray)input).Select(PrepareInput).ToArray(); - } + JsonElement token = (JsonElement)input; - JToken token = input as JToken; - if (token != null) + if (token.ValueKind == JsonValueKind.Array) { - if (token.Type == JTokenType.String) + object[] array = new object[token.GetArrayLength()]; + for (int i = 0; i < array.Length; i++) { - return token.Value(); + array[i] = PrepareInput(token[i]); } - if (token.Type == JTokenType.Integer) - { - return token.Value(); - } + input = array; + } + + if (token.ValueKind == JsonValueKind.String) + { + return token.GetString()!; + } + + if (token.ValueKind == JsonValueKind.Number) + { + return token.GetInt64(); } return input; @@ -58,6 +63,13 @@ public static IEnumerable LoadFromFile( throw new ArgumentException($"Cannot find test resource: {testFileName}"); } + var jsonOptions = new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + NumberHandling = JsonNumberHandling.AllowReadingFromString + }; using (Stream stream = assembly.GetManifestResourceStream(resourceName)) { Assert.NotNull(stream); @@ -65,7 +77,7 @@ public static IEnumerable LoadFromFile( { string testJson = reader.ReadToEnd(); TContainer testSpecs = - JsonConvert.DeserializeObject(testJson); + JsonSerializer.Deserialize(testJson, jsonOptions); return testExtractor(testSpecs); } } diff --git a/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs index dc872261d15..9d005329538 100644 --- a/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs +++ b/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; + using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -15,7 +17,6 @@ using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.Specs.Forks; -using Newtonsoft.Json; using NUnit.Framework; namespace Ethereum.Transaction.Test @@ -41,7 +42,7 @@ private static IEnumerable LoadTests(string testSet) foreach (string testFile in testFiles) { string json = File.ReadAllText(testFile); - Dictionary testsInFile = JsonConvert.DeserializeObject>(json); + Dictionary testsInFile = JsonSerializer.Deserialize>(json); foreach (KeyValuePair namedTest in testsInFile) { testJsons[testDir].Add(namedTest.Key, namedTest.Value); diff --git a/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs b/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs index 2d49692bf4d..113a3c6f88e 100644 --- a/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Json.Serialization; + using Ethereum.Test.Base; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Ethereum.VM.Test/AbiTests.cs b/src/Nethermind/Ethereum.VM.Test/AbiTests.cs index 63260248894..c9294d276a2 100644 --- a/src/Nethermind/Ethereum.VM.Test/AbiTests.cs +++ b/src/Nethermind/Ethereum.VM.Test/AbiTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; + using Ethereum.Test.Base; using Nethermind.Abi; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs b/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs index a9ac0fbebe3..707060364a5 100644 --- a/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs +++ b/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs @@ -5,9 +5,12 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Text; +using System.Text.Json; + using FluentAssertions; using Nethermind.Blockchain.Contracts.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Abi.Test.Json @@ -66,25 +69,24 @@ object[] GetTestDataWithException(string type, Exception exception, object[] com [TestCaseSource(nameof(TypeTestCases))] public void Can_read_json(string type, AbiType expectedType, Exception expectedException, object[] components) { - List abiTypeFactories = new List() { new AbiTypeFactory(new AbiTuple()) }; - var converter = new AbiParameterConverter(abiTypeFactories); + AbiParameterConverter.RegisterFactory(new AbiTypeFactory(new AbiTuple())); + + var converter = new AbiParameterConverter(); var model = new { name = "theName", type, components }; - string json = JsonConvert.SerializeObject(model); - using (var jsonReader = new JsonTextReader(new StringReader(json))) + byte[] json = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(model)); + Utf8JsonReader jsonReader = new Utf8JsonReader(json); + try { - try - { - var result = converter.ReadJson(jsonReader, typeof(AbiParameter), null, false, new JsonSerializer()); - var expectation = new AbiParameter() { Name = "theName", Type = expectedType }; - expectedException.Should().BeNull(); - result.Should().BeEquivalentTo(expectation); - } - catch (Exception e) + var result = converter.Read(ref jsonReader, typeof(AbiParameter), JsonSerializerOptions.Default); + var expectation = new AbiParameter() { Name = "theName", Type = expectedType }; + expectedException.Should().BeNull(); + result.Should().BeEquivalentTo(expectation); + } + catch (Exception e) + { + if (e.GetType() != expectedException?.GetType()) { - if (e.GetType() != expectedException?.GetType()) - { - throw; - } + throw; } } } diff --git a/src/Nethermind/Nethermind.Abi.Test/Nethermind.Abi.Test.csproj b/src/Nethermind/Nethermind.Abi.Test/Nethermind.Abi.Test.csproj index f8ff896efd9..5f5434473d3 100644 --- a/src/Nethermind/Nethermind.Abi.Test/Nethermind.Abi.Test.csproj +++ b/src/Nethermind/Nethermind.Abi.Test/Nethermind.Abi.Test.csproj @@ -11,7 +11,6 @@ - diff --git a/src/Nethermind/Nethermind.Abi/AbiAddress.cs b/src/Nethermind/Nethermind.Abi/AbiAddress.cs index f6b98cefb93..877d4c4f640 100644 --- a/src/Nethermind/Nethermind.Abi/AbiAddress.cs +++ b/src/Nethermind/Nethermind.Abi/AbiAddress.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; + using Nethermind.Core; using Nethermind.Core.Extensions; @@ -11,11 +13,6 @@ public class AbiAddress : AbiUInt { public static readonly AbiAddress Instance = new(); - static AbiAddress() - { - RegisterMapping
(Instance); - } - private AbiAddress() : base(160) { } @@ -38,6 +35,11 @@ public override byte[] Encode(object? arg, bool packed) arg = new Address(stringInput); continue; } + case JsonElement element when element.ValueKind == JsonValueKind.String: + { + arg = new Address(element.GetString()!); + continue; + } default: { throw new AbiException(AbiEncodingExceptionMessage); diff --git a/src/Nethermind/Nethermind.Abi/AbiArray.cs b/src/Nethermind/Nethermind.Abi/AbiArray.cs index 5cb26465c53..3329ebc4169 100644 --- a/src/Nethermind/Nethermind.Abi/AbiArray.cs +++ b/src/Nethermind/Nethermind.Abi/AbiArray.cs @@ -6,6 +6,9 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Text.Json; +using System.Text.Json.Nodes; + using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -30,8 +33,7 @@ public AbiArray(AbiType elementType) public override (object, int) Decode(byte[] data, int position, bool packed) { - UInt256 length; - (length, position) = UInt256.DecodeUInt(data, position, packed); + (UInt256 length, position) = UInt256.DecodeUInt(data, position, packed); return DecodeSequence(ElementType.CSharpType, (int)length, ElementTypes, data, packed, position); } @@ -49,6 +51,15 @@ public override byte[] Encode(object? arg, bool packed) length = list.Count; encodedItems = EncodeSequence(length, ElementTypes, list.Cast(), packed, 1); break; + case JsonElement element when element.ValueKind == JsonValueKind.Array: + length = element.GetArrayLength(); + object[] jArray = new object[length]; + for (int i = 0; i < length; i++) + { + jArray[i] = element[i]; + } + encodedItems = EncodeSequence(length, ElementTypes, jArray, packed, 1); + break; default: throw new AbiException(AbiEncodingExceptionMessage); } diff --git a/src/Nethermind/Nethermind.Abi/AbiBaseDescription.cs b/src/Nethermind/Nethermind.Abi/AbiBaseDescription.cs index 970fd847ef2..fe18f9d9ed9 100644 --- a/src/Nethermind/Nethermind.Abi/AbiBaseDescription.cs +++ b/src/Nethermind/Nethermind.Abi/AbiBaseDescription.cs @@ -3,10 +3,15 @@ using System; using System.Linq; +using System.Text.Json.Serialization; + using Nethermind.Core.Crypto; namespace Nethermind.Abi { + [JsonDerivedType(typeof(AbiEventDescription))] + [JsonDerivedType(typeof(AbiFunctionDescription))] + [JsonDerivedType(typeof(AbiErrorDescription))] public abstract class AbiBaseDescription { public AbiDescriptionType Type { get; set; } = AbiDescriptionType.Function; diff --git a/src/Nethermind/Nethermind.Abi/AbiBool.cs b/src/Nethermind/Nethermind.Abi/AbiBool.cs index 5bae2f2394c..a6a8faab710 100644 --- a/src/Nethermind/Nethermind.Abi/AbiBool.cs +++ b/src/Nethermind/Nethermind.Abi/AbiBool.cs @@ -10,11 +10,6 @@ public class AbiBool : AbiUInt { public static readonly AbiBool Instance = new(); - static AbiBool() - { - RegisterMapping(Instance); - } - private AbiBool() : base(8) { } diff --git a/src/Nethermind/Nethermind.Abi/AbiBytes.cs b/src/Nethermind/Nethermind.Abi/AbiBytes.cs index 88cfedba1aa..2813255ea54 100644 --- a/src/Nethermind/Nethermind.Abi/AbiBytes.cs +++ b/src/Nethermind/Nethermind.Abi/AbiBytes.cs @@ -3,6 +3,8 @@ using System; using System.Text; +using System.Text.Json; + using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -59,6 +61,11 @@ public override byte[] Encode(object? arg, bool packed) return Encode(Encoding.ASCII.GetBytes(stringInput), packed); } + if (arg is JsonElement element && element.ValueKind == JsonValueKind.String) + { + return Encode(Encoding.ASCII.GetBytes(element.GetString()!), packed); + } + if (arg is Hash256 hash && Length == 32) { return Encode(hash.Bytes.ToArray(), packed); diff --git a/src/Nethermind/Nethermind.Abi/AbiDefinition.cs b/src/Nethermind/Nethermind.Abi/AbiDefinition.cs index 1adcccdfc8b..e06f1effd07 100644 --- a/src/Nethermind/Nethermind.Abi/AbiDefinition.cs +++ b/src/Nethermind/Nethermind.Abi/AbiDefinition.cs @@ -2,9 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json.Serialization; + +using Nethermind.Blockchain.Contracts.Json; namespace Nethermind.Abi { + [JsonConverter(typeof(AbiDefinitionConverter))] public class AbiDefinition { private readonly List _constructors = new(); diff --git a/src/Nethermind/Nethermind.Abi/AbiDefinitionConverter.cs b/src/Nethermind/Nethermind.Abi/AbiDefinitionConverter.cs new file mode 100644 index 00000000000..fcda3d156d3 --- /dev/null +++ b/src/Nethermind/Nethermind.Abi/AbiDefinitionConverter.cs @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +using FastEnumUtility; + +using Nethermind.Abi; +using Nethermind.Core.Extensions; + +namespace Nethermind.Blockchain.Contracts.Json; + +public class AbiDefinitionConverter : JsonConverter +{ + public override void Write(Utf8JsonWriter writer, AbiDefinition value, JsonSerializerOptions op) + { + writer.WriteStartArray(); + foreach (AbiBaseDescription item in value.Items) + { + JsonSerializer.Serialize(writer, item, op); + } + + writer.WriteEndArray(); + } + + public override AbiDefinition? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions op) + { + AbiDefinition value = new(); + if (!JsonDocument.TryParseValue(ref reader, out JsonDocument? document)) + return null; + JsonElement abiToken; + JsonElement topLevelToken = document.RootElement; + if (topLevelToken.ValueKind == JsonValueKind.Object) + { + abiToken = topLevelToken.GetProperty("abi"u8); + if (topLevelToken.TryGetProperty("bytecode"u8, out JsonElement bytecodeBase64)) + { + value.SetBytecode(Bytes.FromHexString(bytecodeBase64.GetString()!)); + } + + if (topLevelToken.TryGetProperty("deployedBytecode"u8, out JsonElement deployedBytecodeBase64)) + { + value.SetDeployedBytecode(Bytes.FromHexString(deployedBytecodeBase64.GetString()!)); + } + } + else + { + abiToken = topLevelToken; + } + + foreach (JsonElement definitionToken in abiToken.EnumerateArray()) + { + if (!definitionToken.TryGetProperty("type"u8, out JsonElement typeToken)) + { + continue; + } + + AbiDescriptionType type = FastEnum.Parse(typeToken.GetString(), true); + switch (type) + { + case AbiDescriptionType.Event: + AbiEventDescription? eventDescription = definitionToken.Deserialize(op); + if (eventDescription != null) + value.Add(eventDescription); + break; + case AbiDescriptionType.Error: + AbiErrorDescription? errorDescription = definitionToken.Deserialize(op); + if (errorDescription != null) + value.Add(errorDescription); + break; + default: + AbiFunctionDescription? functionDescription = definitionToken.Deserialize(op); + if (functionDescription != null) + value.Add(functionDescription); + break; + } + } + + return value; + } +} diff --git a/src/Nethermind/Nethermind.Abi/AbiDescriptionType.cs b/src/Nethermind/Nethermind.Abi/AbiDescriptionType.cs index 5c8b16ae7dd..076128bc9b4 100644 --- a/src/Nethermind/Nethermind.Abi/AbiDescriptionType.cs +++ b/src/Nethermind/Nethermind.Abi/AbiDescriptionType.cs @@ -1,8 +1,13 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Serialization.Json; + namespace Nethermind.Abi { + [JsonConverter(typeof(LowerCaseJsonStringEnumConverter))] public enum AbiDescriptionType { Function, diff --git a/src/Nethermind/Nethermind.Abi/AbiDynamicBytes.cs b/src/Nethermind/Nethermind.Abi/AbiDynamicBytes.cs index 38da8069728..af07c6d9aa8 100644 --- a/src/Nethermind/Nethermind.Abi/AbiDynamicBytes.cs +++ b/src/Nethermind/Nethermind.Abi/AbiDynamicBytes.cs @@ -4,6 +4,8 @@ using System; using System.Numerics; using System.Text; +using System.Text.Json; + using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -13,11 +15,6 @@ public class AbiDynamicBytes : AbiType { public static readonly AbiDynamicBytes Instance = new(); - static AbiDynamicBytes() - { - RegisterMapping(Instance); - } - private AbiDynamicBytes() { } @@ -48,6 +45,11 @@ public override byte[] Encode(object? arg, bool packed) return Encode(Encoding.ASCII.GetBytes(stringInput), packed); } + if (arg is JsonElement element && element.ValueKind == JsonValueKind.String) + { + return Encode(Encoding.ASCII.GetBytes(element.GetString()!), packed); + } + throw new AbiException(AbiEncodingExceptionMessage); } diff --git a/src/Nethermind/Nethermind.Abi/AbiEventParameter.cs b/src/Nethermind/Nethermind.Abi/AbiEventParameter.cs index 0ddee84781b..aa82940b514 100644 --- a/src/Nethermind/Nethermind.Abi/AbiEventParameter.cs +++ b/src/Nethermind/Nethermind.Abi/AbiEventParameter.cs @@ -1,8 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; +using Nethermind.Blockchain.Contracts.Json; + namespace Nethermind.Abi { + [JsonConverter(typeof(AbiEventParameterConverter))] public class AbiEventParameter : AbiParameter { public bool Indexed { get; set; } diff --git a/src/Nethermind/Nethermind.Abi/AbiInt.cs b/src/Nethermind/Nethermind.Abi/AbiInt.cs index 3ff214b8a99..8a8c282ef4f 100644 --- a/src/Nethermind/Nethermind.Abi/AbiInt.cs +++ b/src/Nethermind/Nethermind.Abi/AbiInt.cs @@ -7,6 +7,7 @@ namespace Nethermind.Abi { + using Nethermind.Int256; public class AbiInt : AbiType { private const int MaxSize = 256; @@ -19,15 +20,6 @@ public class AbiInt : AbiType public static new readonly AbiInt Int96 = new(96); public static new readonly AbiInt Int256 = new(256); - static AbiInt() - { - RegisterMapping(Int8); - RegisterMapping(Int16); - RegisterMapping(Int32); - RegisterMapping(Int64); - RegisterMapping(Int256); - } - public AbiInt(int length) { if (length % 8 != 0) diff --git a/src/Nethermind/Nethermind.Abi/AbiParameter.cs b/src/Nethermind/Nethermind.Abi/AbiParameter.cs index 0cc54f90b88..e85429611e5 100644 --- a/src/Nethermind/Nethermind.Abi/AbiParameter.cs +++ b/src/Nethermind/Nethermind.Abi/AbiParameter.cs @@ -1,11 +1,65 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + +using Nethermind.Blockchain.Contracts.Json; + namespace Nethermind.Abi { + [JsonConverter(typeof(AbiParameterConverter))] + [JsonDerivedType(typeof(AbiEventParameter))] public class AbiParameter { public string Name { get; set; } = string.Empty; public AbiType Type { get; set; } = AbiType.UInt256; } } + +namespace Nethermind.Blockchain.Contracts.Json +{ + using Nethermind.Abi; + + public interface IAbiTypeFactory + { + AbiType? Create(string abiTypeSignature); + } + + internal static partial class AbiParameterConverterStatics + { + internal const string TypeGroup = "T"; + internal const string TypeLengthGroup = "M"; + internal const string PrecisionGroup = "N"; + internal const string ArrayGroup = "A"; + internal const string LengthGroup = "L"; + + /// + /// Groups: + /// T - type or base type if array + /// M - length of type https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html#types + /// N - precision of type https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html#types + /// A - if matched type is array + /// L - if matched, denotes length of fixed length array + /// + internal static readonly Regex TypeExpression = TypeExpressionRegex(); + + internal static readonly Dictionary> SimpleTypeFactories = new Dictionary>(StringComparer.InvariantCultureIgnoreCase) + { + {"int", (m, n) => new AbiInt(m ?? 256)}, + {"uint", (m, n) => new AbiUInt(m ?? 256)}, + {"address", (m, n) => AbiType.Address}, + {"bool", (m, n) => AbiType.Bool}, + {"fixed", (m, n) => new AbiFixed(m ?? 128, n ?? 18)}, + {"ufixed", (m, n) => new AbiUFixed(m ?? 128, n ?? 18)}, + {"bytes", (m, n) => m.HasValue ? new AbiBytes(m.Value) : AbiType.DynamicBytes}, + {"function", (m, n) => AbiType.Function}, + {"string", (m, n) => AbiType.String} + }; + + [GeneratedRegex("^(?u?int(?\\d{1,3})?|address|bool|u?fixed((?\\d{1,3})x(?\\d{1,2}))?|bytes(?\\d{1,3})?|function|string|tuple)(?\\[(?\\d+)?\\])?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + private static partial Regex TypeExpressionRegex(); + } +} diff --git a/src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs b/src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs new file mode 100644 index 00000000000..98e5b341525 --- /dev/null +++ b/src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs @@ -0,0 +1,178 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.RegularExpressions; + +using Nethermind.Abi; + +namespace Nethermind.Blockchain.Contracts.Json; + +public abstract class AbiParameterConverterBase : JsonConverter where T : AbiParameter, new() +{ + private static readonly object _registerLock = new(); + private static IList _abiTypeFactories = Array.Empty(); + + public static bool IsFactoryRegistered() + where TFactory : IAbiTypeFactory + { + IList abiTypeFactories = _abiTypeFactories; + foreach (var factory in abiTypeFactories) + { + if (factory is TFactory) + { + return true; + } + } + + return false; + } + + public static void RegisterFactory(TFactory factory) + where TFactory : IAbiTypeFactory + { + lock (_registerLock) + { + if (IsFactoryRegistered()) + { + return; + } + + List abiTypeFactories = new(_abiTypeFactories) + { + factory + }; + + _abiTypeFactories = abiTypeFactories; + } + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions op) + { + writer.WriteStartObject(); + writer.WriteString("name"u8, value.Name); + writer.WriteString("type"u8, value.Type.Name); + if (value is AbiEventParameter eventParameter) + { + writer.WriteBoolean("indexed"u8, eventParameter.Indexed); + } + writer.WriteEndObject(); + } + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions op) + { + JsonElement token = JsonElement.ParseValue(ref reader); + T value = new(); + Populate(value, token); + return value; + } + + protected virtual void Populate(T item, JsonElement token) + { + item.Name = GetName(token); + item.Type = GetAbiType(token); + } + + private AbiType GetAbiType(JsonElement token) + { + return token.TryGetProperty("components"u8, out JsonElement components) + ? GetParameterType(token.GetProperty(TypePropertyName).GetString()!, components) + : GetParameterType(token.GetProperty(TypePropertyName).GetString()!, null); + } + + private static string TypePropertyName => nameof(AbiParameter.Type).ToLowerInvariant(); + + private static string GetName(JsonElement token) => + token.GetProperty(NamePropertyName).GetString()!; + + private static string NamePropertyName => nameof(AbiParameter.Name).ToLowerInvariant(); + + private AbiType GetParameterType(string type, JsonElement? components) + { + var match = AbiParameterConverterStatics.TypeExpression.Match(type); + if (match.Success) + { + var baseType = new string(match.Groups[AbiParameterConverterStatics.TypeGroup].Value.TakeWhile(char.IsLetter).ToArray()); + var baseAbiType = GetBaseType(baseType, match, components); + return match.Groups[AbiParameterConverterStatics.ArrayGroup].Success + ? match.Groups[AbiParameterConverterStatics.LengthGroup].Success + ? new AbiFixedLengthArray(baseAbiType, int.Parse(match.Groups[AbiParameterConverterStatics.LengthGroup].Value)) + : new AbiArray(baseAbiType) + : baseAbiType; + } + else + { + throw new ArgumentException($"Invalid contract ABI json. Unknown type {type}."); + } + } + + private AbiType GetBaseType(string baseType, Match match, JsonElement? components) + { + string GetAbiTypeName() + { + string name = baseType; + if (components is not null && components.Value.ValueKind != JsonValueKind.Null) + { + IEnumerable innerTypes = components.Value.EnumerateArray() + .Select(c => c.GetProperty(TypePropertyName).GetString())!; + name = $"({string.Join(",", innerTypes)})"; + } + + return name; + } + + string abiTypeName = GetAbiTypeName(); + + foreach (IAbiTypeFactory factory in _abiTypeFactories) + { + AbiType? abiType = factory.Create(abiTypeName); + if (abiType is not null) + { + return abiType; + } + } + + if (AbiParameterConverterStatics.SimpleTypeFactories.TryGetValue(baseType, out var simpleTypeFactory)) + { + int? m = match.Groups[AbiParameterConverterStatics.TypeLengthGroup].Success ? int.Parse(match.Groups[AbiParameterConverterStatics.TypeLengthGroup].Value) : (int?)null; + int? n = match.Groups[AbiParameterConverterStatics.PrecisionGroup].Success ? int.Parse(match.Groups[AbiParameterConverterStatics.PrecisionGroup].Value) : (int?)null; + return simpleTypeFactory(m, n); + } + else if (baseType == "tuple") + { + IEnumerable children = components!.Value.EnumerateArray().ToArray(); + return new AbiTuple(children.Select(GetAbiType).ToArray(), children.Select(GetName).ToArray()); + } + else + { + throw new NotSupportedException($"Abi doesn't support type '{baseType}'"); + } + } +} + +public class AbiParameterConverter : AbiParameterConverterBase +{ + public AbiParameterConverter() : base() + { + } +} + +public class AbiEventParameterConverter : AbiParameterConverterBase +{ + public AbiEventParameterConverter() : base() + { + } + + protected override void Populate(AbiEventParameter item, JsonElement token) + { + base.Populate(item, token); + if (token.TryGetProperty("indexed"u8, out JsonElement property)) + { + item.Indexed = property.GetBoolean(); + } + } +} diff --git a/src/Nethermind/Nethermind.Abi/AbiString.cs b/src/Nethermind/Nethermind.Abi/AbiString.cs index 959ae97e166..00bc57c81d5 100644 --- a/src/Nethermind/Nethermind.Abi/AbiString.cs +++ b/src/Nethermind/Nethermind.Abi/AbiString.cs @@ -10,11 +10,6 @@ public class AbiString : AbiType { public static readonly AbiString Instance = new(); - static AbiString() - { - RegisterMapping(Instance); - } - private AbiString() { } diff --git a/src/Nethermind/Nethermind.Abi/AbiTuple.cs b/src/Nethermind/Nethermind.Abi/AbiTuple.cs index 8ca698200f9..3a6ec98590c 100644 --- a/src/Nethermind/Nethermind.Abi/AbiTuple.cs +++ b/src/Nethermind/Nethermind.Abi/AbiTuple.cs @@ -76,6 +76,14 @@ private static Type GetCSharpType(AbiType[] elements) public class AbiTuple : AbiType where T : new() { + static AbiTuple() + { + if (!IsMappingRegistered()) + { + AbiType.RegisterMapping(new AbiTuple()); + } + } + private readonly PropertyInfo[] _properties; private readonly AbiType[] _elements; public override string Name { get; } diff --git a/src/Nethermind/Nethermind.Abi/AbiType.TypeMapping.cs b/src/Nethermind/Nethermind.Abi/AbiType.TypeMapping.cs index f5f230c52c5..2939dc3db05 100644 --- a/src/Nethermind/Nethermind.Abi/AbiType.TypeMapping.cs +++ b/src/Nethermind/Nethermind.Abi/AbiType.TypeMapping.cs @@ -3,13 +3,18 @@ using System; using System.Collections.Generic; +using System.Collections.Frozen; + +using Nethermind.Core; using Nethermind.Core.Extensions; namespace Nethermind.Abi { + using Nethermind.Int256; public partial class AbiType { - private static readonly IDictionary _typeMappings = new Dictionary(); + private static readonly object _registerLock = new(); + private static FrozenDictionary _typeMappings = CreateTypeMappings(); protected static AbiType GetForCSharpType(Type type) { @@ -44,19 +49,45 @@ protected static AbiType GetForCSharpType(Type type) } } + protected static bool IsMappingRegistered() + { + return _typeMappings.ContainsKey(typeof(T)); + } + protected static void RegisterMapping(AbiType abiType) { - _typeMappings[typeof(T)] = abiType; + lock (_registerLock) + { + Dictionary typeMappings = new(_typeMappings) + { + [typeof(T)] = abiType + }; + + _typeMappings = typeMappings.ToFrozenDictionary(); + } } - static AbiType() + private static FrozenDictionary CreateTypeMappings() { - AbiType type = AbiAddress.Instance; - type = AbiBool.Instance; - type = AbiDynamicBytes.Instance; - type = AbiInt.Int8; - type = AbiString.Instance; - type = AbiUInt.UInt8; + Dictionary typeMappings = new() + { + [typeof(bool)] = AbiBool.Instance, + [typeof(byte)] = AbiUInt.UInt8, + [typeof(sbyte)] = AbiInt.Int8, + [typeof(ushort)] = AbiUInt.UInt16, + [typeof(short)] = AbiInt.Int16, + [typeof(uint)] = AbiUInt.UInt32, + [typeof(int)] = AbiInt.Int32, + [typeof(ulong)] = AbiUInt.UInt64, + [typeof(long)] = AbiInt.Int64, + [typeof(UInt256)] = AbiUInt.UInt256, + [typeof(Int256)] = AbiInt.Int256, + [typeof(Address)] = AbiAddress.Instance, + [typeof(string)] = AbiString.Instance, + [typeof(byte[])] = AbiDynamicBytes.Instance + }; + + return typeMappings.ToFrozenDictionary(); } } } diff --git a/src/Nethermind/Nethermind.Abi/AbiType.cs b/src/Nethermind/Nethermind.Abi/AbiType.cs index 5fca5286b24..8c683bcaae5 100644 --- a/src/Nethermind/Nethermind.Abi/AbiType.cs +++ b/src/Nethermind/Nethermind.Abi/AbiType.cs @@ -2,9 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json.Serialization; +using System.Text.Json; +using Nethermind.Blockchain.Contracts.Json; namespace Nethermind.Abi { + [JsonConverter(typeof(AbiTypeConverter))] public abstract partial class AbiType { public static AbiDynamicBytes DynamicBytes => AbiDynamicBytes.Instance; @@ -47,3 +51,132 @@ public abstract partial class AbiType public abstract Type CSharpType { get; } } } + +namespace Nethermind.Blockchain.Contracts.Json +{ + using Nethermind.Abi; + + [JsonDerivedType(typeof(AbiDynamicBytes))] + [JsonDerivedType(typeof(AbiBytes))] + [JsonDerivedType(typeof(AbiAddress))] + [JsonDerivedType(typeof(AbiFunction))] + [JsonDerivedType(typeof(AbiBool))] + [JsonDerivedType(typeof(AbiInt))] + [JsonDerivedType(typeof(AbiUInt))] + [JsonDerivedType(typeof(AbiString))] + [JsonDerivedType(typeof(AbiFixed))] + [JsonDerivedType(typeof(AbiUFixed))] + public class AbiTypeConverter : JsonConverter + { + public override AbiType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? type = reader.GetString()!; + return ParseAbiType(type); + + static AbiType ParseAbiType(string type) + { + bool isArray = false; + if (type.EndsWith("[]")) + { + isArray = true; + type = type[..^2]; + } + + if (type == "tuple") + { + return new AbiTuple(); + } + if (type.StartsWith('(') && type.EndsWith(')')) + { + string[] types = type[1..^1].Split(','); + return ParseTuple(types); + } + if (type.StartsWith("tuple")) + { + string[] types = type.Trim()[6..^1].Split(','); + return ParseTuple(types); + } + + AbiType value = GetType(type); + + return isArray ? new AbiArray(value) : value; + + } + + static AbiType ParseTuple(string[] types) + { + AbiType[] abiTypes = new AbiType[types.Length]; + for (int i = 0; i < types.Length; i++) + { + abiTypes[i] = ParseAbiType(types[i].Trim()); + } + + return new AbiTuple(abiTypes); + } + + static AbiType ParseFixed(string type) + { + (int length, int precision) = Parse(type.AsSpan(5)); + return new AbiFixed(length, precision); + } + + static AbiType ParseUFixed(string type) + { + (int length, int precision) = Parse(type.AsSpan(6)); + return new AbiUFixed(length, precision); + } + + static (int length, int precision) Parse(ReadOnlySpan chars) + { + int xPos = chars.IndexOf('x'); + if (xPos == -1) + { + return (-1, -1); + } + + int length = int.Parse(chars.Slice(0, xPos)); + int precision = int.Parse(chars.Slice(xPos + 1)); + + return (length, precision); + } + + static AbiType GetType(string? type) + { + return type switch + { + "address" => AbiType.Address, + "function" => AbiType.Function, + "bool" => AbiType.Bool, + "int8" => AbiType.Int8, + "int16" => AbiType.Int16, + "int32" => AbiType.Int32, + "int64" => AbiType.Int64, + "int96" => AbiType.Int96, + "int256" => AbiType.Int256, + { } when type.StartsWith("int") => new AbiInt(int.Parse(type.AsSpan(3))), + "uint8" => AbiType.UInt8, + "uint16" => AbiType.UInt16, + "uint32" => AbiType.UInt32, + "uint64" => AbiType.UInt64, + "uint96" => AbiType.UInt96, + "uint256" => AbiType.UInt256, + { } when type.StartsWith("uint") => new AbiUInt(int.Parse(type.AsSpan(4))), + "string" => AbiType.String, + "bytes" => AbiType.DynamicBytes, + "bytes32" => AbiType.Bytes32, + { } when type.StartsWith("bytes") => new AbiBytes(int.Parse(type.AsSpan(5))), + "fixed128x18" => AbiType.Fixed, + "ufixed128x18" => AbiType.UFixed, + { } when type.StartsWith("fixed") => ParseFixed(type), + { } when type.StartsWith("ufixed") => ParseUFixed(type), + _ => throw new NotSupportedException($"ABI type {type} is not supported.") + }; + } + } + + public override void Write(Utf8JsonWriter writer, AbiType value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Name); + } + } +} diff --git a/src/Nethermind/Nethermind.Abi/AbiUInt.cs b/src/Nethermind/Nethermind.Abi/AbiUInt.cs index 0a40a9a9d2c..00ed8328b1b 100644 --- a/src/Nethermind/Nethermind.Abi/AbiUInt.cs +++ b/src/Nethermind/Nethermind.Abi/AbiUInt.cs @@ -4,6 +4,8 @@ using System; using System.Buffers.Binary; using System.Numerics; +using System.Text.Json; + using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -21,15 +23,6 @@ public class AbiUInt : AbiType public static new readonly AbiUInt UInt96 = new(96); public static new readonly AbiUInt UInt256 = new(256); - static AbiUInt() - { - RegisterMapping(UInt8); - RegisterMapping(UInt16); - RegisterMapping(UInt32); - RegisterMapping(UInt64); - RegisterMapping(UInt256); - } - public AbiUInt(int length) { if (length % 8 != 0) @@ -128,6 +121,11 @@ public override byte[] Encode(object? arg, bool packed) bytes = new byte[2]; BinaryPrimitives.WriteUInt16BigEndian(bytes, ushortInput); } + else if (arg is JsonElement element && element.ValueKind == JsonValueKind.Number) + { + bytes = new byte[8]; + BinaryPrimitives.WriteInt64BigEndian(bytes, element.GetInt64()); + } else { throw new AbiException(AbiEncodingExceptionMessage); diff --git a/src/Nethermind/Nethermind.Abi/StateMutability.cs b/src/Nethermind/Nethermind.Abi/StateMutability.cs index 5ef079eac73..f84ee3f9a58 100644 --- a/src/Nethermind/Nethermind.Abi/StateMutability.cs +++ b/src/Nethermind/Nethermind.Abi/StateMutability.cs @@ -1,8 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; +using Nethermind.Serialization.Json; + namespace Nethermind.Abi { + [JsonConverter(typeof(LowerCaseJsonStringEnumConverter))] public enum StateMutability { Pure, diff --git a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs index 556fdf6efbb..3e8f29490ab 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs @@ -192,8 +192,9 @@ protected override BlockProcessor CreateBlockProcessor() NullWitnessCollector.Instance, LogManager); + AbiParameterConverter.RegisterFactory(new AbiTypeFactory(new AbiTuple())); + var parser = new AbiDefinitionParser(); - parser.RegisterAbiTypeFactory(new AbiTuple()); var json = parser.LoadContract(typeof(EntryPoint)); EntryPointContractAbi = parser.Parse(json); diff --git a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs index 8d73ae952fe..9adf2ceb376 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.cs @@ -22,498 +22,500 @@ using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Test.Modules; using Nethermind.Logging; + +using Newtonsoft.Json.Linq; using NUnit.Framework; -namespace Nethermind.AccountAbstraction.Test +namespace Nethermind.AccountAbstraction.Test; + +[TestFixture] +public partial class AccountAbstractionRpcModuleTests { - [TestFixture] - public partial class AccountAbstractionRpcModuleTests + private Contracts _contracts = new(); + private AbiEncoder _encoder = new(); + private static int entryPointNum = 2; + + public class Contracts { - private Contracts _contracts = new(); + internal AbiDefinition SingletonFactory; + internal AbiDefinition[] EntryPointAbi = new AbiDefinition[entryPointNum]; + internal AbiDefinition SimpleWalletAbi; + internal AbiDefinition TestCounterAbi; + internal AbiDefinition TokenPaymasterAbi; + private AbiEncoder _encoder = new(); - private static int entryPointNum = 2; - public class Contracts + public Contracts() { - internal AbiDefinition SingletonFactory; - internal AbiDefinition[] EntryPointAbi = new AbiDefinition[entryPointNum]; - internal AbiDefinition SimpleWalletAbi; - internal AbiDefinition TestCounterAbi; - internal AbiDefinition TokenPaymasterAbi; - - private AbiEncoder _encoder = new(); + SingletonFactory = LoadContract(typeof(SingletonFactory)); + // TODO: Implement a way to loop over the file names also + EntryPointAbi[0] = LoadContract(typeof(EntryPoint)); + EntryPointAbi[1] = LoadContract(typeof(EntryPoint_2)); + SimpleWalletAbi = LoadContract(typeof(SimpleWallet)); + TestCounterAbi = LoadContract(typeof(TestCounter)); + TokenPaymasterAbi = LoadContract(typeof(TokenPaymaster)); + } - public Contracts() - { - SingletonFactory = LoadContract(typeof(SingletonFactory)); - // TODO: Implement a way to loop over the file names also - EntryPointAbi[0] = LoadContract(typeof(EntryPoint)); - EntryPointAbi[1] = LoadContract(typeof(EntryPoint_2)); - SimpleWalletAbi = LoadContract(typeof(SimpleWallet)); - TestCounterAbi = LoadContract(typeof(TestCounter)); - TokenPaymasterAbi = LoadContract(typeof(TokenPaymaster)); - } + private AbiDefinition LoadContract(Type contractType) + { + AbiParameterConverter.RegisterFactory(new AbiTypeFactory(new AbiTuple())); - private AbiDefinition LoadContract(Type contractType) - { - var parser = new AbiDefinitionParser(); - parser.RegisterAbiTypeFactory(new AbiTuple()); - var json = parser.LoadContract(contractType); - return parser.Parse(json); - } + var parser = new AbiDefinitionParser(); + var json = parser.LoadContract(contractType); + return parser.Parse(json); + } - public static long LargeGasLimit = 5_000_000; + public static long LargeGasLimit = 5_000_000; - public static PrivateKey ContractCreatorPrivateKey = TestItem.PrivateKeyC; - public static PrivateKey WalletOwnerPrivateKey = TestItem.PrivateKeyA; - public static Address WalletOwner = WalletOwnerPrivateKey.Address; + public static PrivateKey ContractCreatorPrivateKey = TestItem.PrivateKeyC; + public static PrivateKey WalletOwnerPrivateKey = TestItem.PrivateKeyA; + public static Address WalletOwner = WalletOwnerPrivateKey.Address; - public UInt256 GetCount(TestRpcBlockchain chain, Address counter, Address wallet) - { - Transaction getCountTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(counter) - .WithGasLimit(100_000) - .WithValue(0) - .WithData(_encoder.Encode(AbiEncodingStyle.IncludeSignature, TestCounterAbi.Functions["counters"].GetCallInfo().Signature, wallet)) - .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject; + public UInt256 GetCount(TestRpcBlockchain chain, Address counter, Address wallet) + { + Transaction getCountTransaction = Core.Test.Builders.Build.A.Transaction + .WithTo(counter) + .WithGasLimit(100_000) + .WithValue(0) + .WithData(_encoder.Encode(AbiEncodingStyle.IncludeSignature, TestCounterAbi.Functions["counters"].GetCallInfo().Signature, wallet)) + .SignedAndResolved(TestItem.PrivateKeyA) + .TestObject; - UInt256 count = new UInt256(Bytes.FromHexString(chain.EthRpcModule.eth_call(new TransactionForRpc(getCountTransaction)).Data), true); + UInt256 count = new UInt256(Bytes.FromHexString(chain.EthRpcModule.eth_call(new TransactionForRpc(getCountTransaction)).Data), true); - return count; - } - - public Address GetAccountAddress(TestRpcBlockchain chain, Address entryPointAddress, byte[] bytecode, UInt256 salt, int epNum) - { - Transaction getAccountAddressTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(entryPointAddress) - .WithGasLimit(1_000_000) - .WithValue(0) - .WithData(_encoder.Encode(AbiEncodingStyle.IncludeSignature, EntryPointAbi[epNum].Functions["getSenderAddress"].GetCallInfo().Signature, bytecode, salt)) - .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject; + return count; + } - Address accountAddress = new(Bytes.FromHexString(chain.EthRpcModule.eth_call(new TransactionForRpc(getAccountAddressTransaction)).Data).SliceWithZeroPaddingEmptyOnError(12, 20)); + public Address GetAccountAddress(TestRpcBlockchain chain, Address entryPointAddress, byte[] bytecode, UInt256 salt, int epNum) + { + Transaction getAccountAddressTransaction = Core.Test.Builders.Build.A.Transaction + .WithTo(entryPointAddress) + .WithGasLimit(1_000_000) + .WithValue(0) + .WithData(_encoder.Encode(AbiEncodingStyle.IncludeSignature, EntryPointAbi[epNum].Functions["getSenderAddress"].GetCallInfo().Signature, bytecode, salt)) + .SignedAndResolved(TestItem.PrivateKeyA) + .TestObject; - return accountAddress; - } + Address accountAddress = new(Bytes.FromHexString(chain.EthRpcModule.eth_call(new TransactionForRpc(getAccountAddressTransaction)).Data).SliceWithZeroPaddingEmptyOnError(12, 20)); - public byte[] GetWalletConstructor(Address entryPointAddress) - { - byte[] walletConstructorBytes = _encoder.Encode(AbiEncodingStyle.None, SimpleWalletAbi.Constructors[0].GetCallInfo().Signature, entryPointAddress, WalletOwner); - return Bytes.Concat(SimpleWalletAbi.Bytecode!, walletConstructorBytes); - } + return accountAddress; + } - public async Task<(Address[], Address?[], Address?[])> Deploy(TestAccountAbstractionRpcBlockchain chain, byte[]? miscContractCode = null) - { - Transaction singletonFactoryTx = Core.Test.Builders.Build.A.Transaction.WithCode(SingletonFactory.Bytecode!).WithGasLimit(6_000_000).WithNonce(0).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject; - await chain.AddBlock(true, singletonFactoryTx); - Address singletonFactoryAddress = chain.Bridge.GetReceipt(singletonFactoryTx.Hash!).ContractAddress!; + public byte[] GetWalletConstructor(Address entryPointAddress) + { + byte[] walletConstructorBytes = _encoder.Encode(AbiEncodingStyle.None, SimpleWalletAbi.Constructors[0].GetCallInfo().Signature, entryPointAddress, WalletOwner); + return Bytes.Concat(SimpleWalletAbi.Bytecode!, walletConstructorBytes); + } - Address[] computedAddresses = new Address[entryPointNum]; - Address?[] createWalletTxReceiptContractAddresses = new Address[entryPointNum]; - Address?[] miscContractTxReceiptContractAddresses = new Address[entryPointNum]; + public async Task<(Address[], Address?[], Address?[])> Deploy(TestAccountAbstractionRpcBlockchain chain, byte[]? miscContractCode = null) + { + Transaction singletonFactoryTx = Core.Test.Builders.Build.A.Transaction.WithCode(SingletonFactory.Bytecode!).WithGasLimit(6_000_000).WithNonce(0).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject; + await chain.AddBlock(true, singletonFactoryTx); + Address singletonFactoryAddress = chain.Bridge.GetReceipt(singletonFactoryTx.Hash!).ContractAddress!; - for (int i = 0; i < entryPointNum; i++) - { + Address[] computedAddresses = new Address[entryPointNum]; + Address?[] createWalletTxReceiptContractAddresses = new Address[entryPointNum]; + Address?[] miscContractTxReceiptContractAddresses = new Address[entryPointNum]; - byte[] entryPointConstructorBytes = Bytes.Concat(EntryPointAbi[i].Bytecode!, _encoder.Encode(AbiEncodingStyle.None, EntryPointAbi[i].Constructors[0].GetCallInfo().Signature, singletonFactoryAddress, 0, 2)); - byte[] createEntryPointBytes = _encoder.Encode(AbiEncodingStyle.IncludeSignature, SingletonFactory.Functions["deploy"].GetCallInfo().Signature, entryPointConstructorBytes, Bytes.Zero32); + for (int i = 0; i < entryPointNum; i++) + { - Transaction entryPointTx = Core.Test.Builders.Build.A.Transaction.WithTo(singletonFactoryAddress).WithData(createEntryPointBytes).WithGasLimit(6_000_000).WithNonce(chain.State.GetNonce(ContractCreatorPrivateKey.Address)).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject; - await chain.AddBlock(true, entryPointTx); + byte[] entryPointConstructorBytes = Bytes.Concat(EntryPointAbi[i].Bytecode!, _encoder.Encode(AbiEncodingStyle.None, EntryPointAbi[i].Constructors[0].GetCallInfo().Signature, singletonFactoryAddress, 0, 2)); + byte[] createEntryPointBytes = _encoder.Encode(AbiEncodingStyle.IncludeSignature, SingletonFactory.Functions["deploy"].GetCallInfo().Signature, entryPointConstructorBytes, Bytes.Zero32); - Address computedAddress = new(Keccak.Compute(Bytes.Concat(Bytes.FromHexString("0xff"), singletonFactoryAddress.Bytes, Bytes.Zero32, Keccak.Compute(entryPointConstructorBytes).Bytes)).BytesToArray().TakeLast(20).ToArray()); + Transaction entryPointTx = Core.Test.Builders.Build.A.Transaction.WithTo(singletonFactoryAddress).WithData(createEntryPointBytes).WithGasLimit(6_000_000).WithNonce(chain.State.GetNonce(ContractCreatorPrivateKey.Address)).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject; + await chain.AddBlock(true, entryPointTx); - TxReceipt createEntryPointTxReceipt = chain.Bridge.GetReceipt(entryPointTx.Hash!); - createEntryPointTxReceipt.Error.Should().BeNullOrEmpty($"Contract transaction {computedAddress!} was not deployed."); - chain.State.GetCode(computedAddress).Should().NotBeNullOrEmpty(); + Address computedAddress = new(Keccak.Compute(Bytes.Concat(Bytes.FromHexString("0xff"), singletonFactoryAddress.Bytes, Bytes.Zero32, Keccak.Compute(entryPointConstructorBytes).Bytes)).BytesToArray().TakeLast(20).ToArray()); - bool createMiscContract = miscContractCode is not null; - IList transactionsToInclude = new List(); + TxReceipt createEntryPointTxReceipt = chain.Bridge.GetReceipt(entryPointTx.Hash!); + createEntryPointTxReceipt.Error.Should().BeNullOrEmpty($"Contract transaction {computedAddress!} was not deployed."); + chain.State.GetCode(computedAddress).Should().NotBeNullOrEmpty(); - Transaction? walletTx = Core.Test.Builders.Build.A.Transaction.WithCode(GetWalletConstructor(computedAddress)).WithGasLimit(LargeGasLimit).WithNonce(chain.State.GetNonce(ContractCreatorPrivateKey.Address)).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject; - transactionsToInclude.Add(walletTx!); + bool createMiscContract = miscContractCode is not null; + IList transactionsToInclude = new List(); - Transaction? miscContractTx = createMiscContract ? Core.Test.Builders.Build.A.Transaction.WithCode(miscContractCode!).WithGasLimit(LargeGasLimit).WithNonce(chain.State.GetNonce(ContractCreatorPrivateKey.Address) + 1).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject : null; - if (createMiscContract) transactionsToInclude.Add(miscContractTx!); + Transaction? walletTx = Core.Test.Builders.Build.A.Transaction.WithCode(GetWalletConstructor(computedAddress)).WithGasLimit(LargeGasLimit).WithNonce(chain.State.GetNonce(ContractCreatorPrivateKey.Address)).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject; + transactionsToInclude.Add(walletTx!); - await chain.AddBlock(true, transactionsToInclude.ToArray()); + Transaction? miscContractTx = createMiscContract ? Core.Test.Builders.Build.A.Transaction.WithCode(miscContractCode!).WithGasLimit(LargeGasLimit).WithNonce(chain.State.GetNonce(ContractCreatorPrivateKey.Address) + 1).WithValue(0).SignedAndResolved(ContractCreatorPrivateKey).TestObject : null; + if (createMiscContract) transactionsToInclude.Add(miscContractTx!); - TxReceipt createWalletTxReceipt = chain.Bridge.GetReceipt(walletTx.Hash!); - TxReceipt? miscContractTxReceipt = createMiscContract ? chain.Bridge.GetReceipt(miscContractTx!.Hash!) : null; - createWalletTxReceipt?.ContractAddress.Should().NotBeNull($"Contract transaction {walletTx?.Hash!} was not deployed."); - miscContractTxReceipt?.ContractAddress.Should().NotBeNull($"Contract transaction {miscContractTx?.Hash!} was not deployed."); + await chain.AddBlock(true, transactionsToInclude.ToArray()); - chain.State.GetCode(createWalletTxReceipt?.ContractAddress!).Should().NotBeNullOrEmpty(); - if (createMiscContract) chain.State.GetCode(miscContractTxReceipt?.ContractAddress!).Should().NotBeNullOrEmpty(); + TxReceipt createWalletTxReceipt = chain.Bridge.GetReceipt(walletTx.Hash!); + TxReceipt? miscContractTxReceipt = createMiscContract ? chain.Bridge.GetReceipt(miscContractTx!.Hash!) : null; + createWalletTxReceipt?.ContractAddress.Should().NotBeNull($"Contract transaction {walletTx?.Hash!} was not deployed."); + miscContractTxReceipt?.ContractAddress.Should().NotBeNull($"Contract transaction {miscContractTx?.Hash!} was not deployed."); - computedAddresses[i] = computedAddress; - createWalletTxReceiptContractAddresses[i] = createWalletTxReceipt?.ContractAddress!; - miscContractTxReceiptContractAddresses[i] = miscContractTxReceipt?.ContractAddress!; - } + chain.State.GetCode(createWalletTxReceipt?.ContractAddress!).Should().NotBeNullOrEmpty(); + if (createMiscContract) chain.State.GetCode(miscContractTxReceipt?.ContractAddress!).Should().NotBeNullOrEmpty(); - return (computedAddresses, createWalletTxReceiptContractAddresses, miscContractTxReceiptContractAddresses); + computedAddresses[i] = computedAddress; + createWalletTxReceiptContractAddresses[i] = createWalletTxReceipt?.ContractAddress!; + miscContractTxReceiptContractAddresses[i] = miscContractTxReceipt?.ContractAddress!; } - } - [Test] - public async Task Should_deploy_contracts_successfully() - { - var chain = await CreateChain(); - (Address[] entryPointAddresses, Address?[] walletAddresses, _) = await _contracts.Deploy(chain); + return (computedAddresses, createWalletTxReceiptContractAddresses, miscContractTxReceiptContractAddresses); } + } - [Test] - public void Should_sign_correctly() - { - UserOperation createOp = Build.A.UserOperation - .WithSender(new Address("0x65f1326ef62E7b63B2EdF41840E37eB2a0F97515")) - .WithNonce(7) - .WithCallData(Bytes.FromHexString("0x80c5c7d000000000000000000000000017e4493e5dc3e0bafdb68147cf15f52f669ef91d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004278ddd3c00000000000000000000000000000000000000000000000000000000")) - .WithCallGas(29129) - .WithVerificationGas(100000) - .WithPreVerificationGas(21000) - .WithMaxFeePerGas(1000000007) - .WithMaxPriorityFeePerGas(1000000000) - .SignedAndResolved( - new PrivateKey("0xa31e1f30394cba49bca6783cf25679abae1e5fd7f70a95ef794b73e041a8c864"), - new Address("0x90f3E1105E63C877bF9587DE5388C23Cdb702c6B"), - 5 - ) - .TestObject; - - Address entryPointId = new Address("0x90f3e1105e63c877bf9587de5388c23cdb702c6b"); - ulong chainId = 5; - Hash256 idFromTransaction = - new Hash256("0x87c3605deda77b02b78e62157309985d94531cf7fbb13992c602c8555bece921"); - createOp.CalculateRequestId(entryPointId, chainId); - Assert.That(createOp.RequestId!, Is.EqualTo(idFromTransaction), - "Request IDs do not match."); + [Test] + public async Task Should_deploy_contracts_successfully() + { + var chain = await CreateChain(); + (Address[] entryPointAddresses, Address?[] walletAddresses, _) = await _contracts.Deploy(chain); + } - Assert.That( - createOp.Signature, Is.EqualTo(Bytes.FromHexString("0xe4ef96c1ebffdae061838b79a0ba2b0289083099dc4d576a7ed0c61c80ed893273ba806a581c72be9e550611defe0bf490f198061b8aa63dd6acfc0b620e0c871c")), - "signatures are different" - ); - } + [Test] + public void Should_sign_correctly() + { + UserOperation createOp = Build.A.UserOperation + .WithSender(new Address("0x65f1326ef62E7b63B2EdF41840E37eB2a0F97515")) + .WithNonce(7) + .WithCallData(Bytes.FromHexString("0x80c5c7d000000000000000000000000017e4493e5dc3e0bafdb68147cf15f52f669ef91d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004278ddd3c00000000000000000000000000000000000000000000000000000000")) + .WithCallGas(29129) + .WithVerificationGas(100000) + .WithPreVerificationGas(21000) + .WithMaxFeePerGas(1000000007) + .WithMaxPriorityFeePerGas(1000000000) + .SignedAndResolved( + new PrivateKey("0xa31e1f30394cba49bca6783cf25679abae1e5fd7f70a95ef794b73e041a8c864"), + new Address("0x90f3E1105E63C877bF9587DE5388C23Cdb702c6B"), + 5 + ) + .TestObject; + + Address entryPointId = new Address("0x90f3e1105e63c877bf9587de5388c23cdb702c6b"); + ulong chainId = 5; + Hash256 idFromTransaction = + new Hash256("0x87c3605deda77b02b78e62157309985d94531cf7fbb13992c602c8555bece921"); + createOp.CalculateRequestId(entryPointId, chainId); + Assert.That(createOp.RequestId!, Is.EqualTo(idFromTransaction), + "Request IDs do not match."); + + Assert.That( + createOp.Signature, Is.EqualTo(Bytes.FromHexString("0xe4ef96c1ebffdae061838b79a0ba2b0289083099dc4d576a7ed0c61c80ed893273ba806a581c72be9e550611defe0bf490f198061b8aa63dd6acfc0b620e0c871c")), + "signatures are different" + ); + } - [TestCase(true, false)] - [TestCase(false, true)] - [Retry(3)] - public async Task Should_execute_well_formed_op_successfully_if_codehash_not_changed(bool changeCodeHash, bool success) - { - var chain = await CreateChain(); - (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); + [TestCase(true, false)] + [TestCase(false, true)] + [Retry(3)] + public async Task Should_execute_well_formed_op_successfully_if_codehash_not_changed(bool changeCodeHash, bool success) + { + var chain = await CreateChain(); + (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); - byte[] countCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); - byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, counterAddress[0]!, 0, countCallData); + byte[] countCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); + byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, counterAddress[0]!, 0, countCallData); - UserOperation op = Build.A.UserOperation - .WithSender(walletAddress[0]!) - .WithCallData(execCounterCountFromEntryPoint) - .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) - .TestObject; + UserOperation op = Build.A.UserOperation + .WithSender(walletAddress[0]!) + .WithCallData(execCounterCountFromEntryPoint) + .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) + .TestObject; - UInt256 countBefore = _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!); - countBefore.Should().Be(0); + UInt256 countBefore = _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!); + countBefore.Should().Be(0); - chain.SendUserOperation(entryPointAddress[0], op); - if (changeCodeHash) - { - chain.State.InsertCode(walletAddress[0]!, Bytes.Concat(chain.State.GetCode(walletAddress[0]!), 0x00), chain.SpecProvider.GenesisSpec); - chain.State.Commit(chain.SpecProvider.GenesisSpec); - chain.State.RecalculateStateRoot(); - chain.State.CommitTree(chain.BlockTree.Head!.Number); - await chain.BlockTree.SuggestBlockAsync(new BlockBuilder().WithStateRoot(chain.State.StateRoot).TestObject); - } - await chain.AddBlock(true); - - if (success) - { - Assert.That( - () => _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!), - Is.EqualTo(UInt256.One).After(5000, 50)); - } - else - { - Assert.That( - () => _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!), - Is.EqualTo(UInt256.Zero).After(5000, 50)); - } - } - - [Test] - public async Task Should_display_the_list_of_supported_entry_points() + chain.SendUserOperation(entryPointAddress[0], op); + if (changeCodeHash) { - var chain = await CreateChain(); - (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); - chain.SupportedEntryPoints(); + chain.State.InsertCode(walletAddress[0]!, Bytes.Concat(chain.State.GetCode(walletAddress[0]!), 0x00), chain.SpecProvider.GenesisSpec); + chain.State.Commit(chain.SpecProvider.GenesisSpec); + chain.State.RecalculateStateRoot(); + chain.State.CommitTree(chain.BlockTree.Head!.Number); + await chain.BlockTree.SuggestBlockAsync(new BlockBuilder().WithStateRoot(chain.State.StateRoot).TestObject); } + await chain.AddBlock(true); - [Test] - public async Task Should_execute_well_formed_op_successfully_for_all_entry_points() + if (success) { - var chain = await CreateChain(); - (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); - - for (int i = 0; i < entryPointNum; i++) - { - - byte[] countCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, - _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); - byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, - _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, - counterAddress[i]!, 0, countCallData); - - UserOperation op = Build.A.UserOperation - .WithSender(walletAddress[i]!) - .WithCallData(execCounterCountFromEntryPoint) - .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[i], chain.SpecProvider.ChainId) - .TestObject; - - /* - Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(walletAddress[i]!) - .WithGasLimit(1_000_000) - .WithGasPrice(2) - .WithValue(1.Ether()) - .WithNonce((UInt256)(i)) - .SignedAndResolved(TestItem.PrivateKeyB).TestObject; - await chain.AddBlock(true, fundTransaction); - */ - - UInt256 countBefore = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); - countBefore.Should().Be(0); - - chain.SendUserOperation(entryPointAddress[i], op); - await chain.AddBlock(true); - - UInt256 countAfter = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); - countAfter.Should().Be(1); - - } - + Assert.That( + () => _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!), + Is.EqualTo(UInt256.One).After(5000, 50)); } - - [Test] - public async Task Should_execute_well_formed_op_successfully_for_all_entry_points_at_the_same_time() + else { - var chain = await CreateChain(); - (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); - - for (int i = 0; i < entryPointNum; i++) - { - - byte[] countCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, - _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); - byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, - _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, - counterAddress[i]!, 0, countCallData); - - UserOperation op = Build.A.UserOperation - .WithSender(walletAddress[i]!) - .WithCallData(execCounterCountFromEntryPoint) - .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[i], chain.SpecProvider.ChainId) - .TestObject; - - /* - Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(walletAddress[i]!) - .WithGasLimit(1_000_000) - .WithGasPrice(2) - .WithValue(1.Ether()) - .WithNonce((UInt256)(i)) - .SignedAndResolved(TestItem.PrivateKeyB).TestObject; - await chain.AddBlock(true, fundTransaction); - */ - - UInt256 countBefore = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); - countBefore.Should().Be(0); - - chain.SendUserOperation(entryPointAddress[i], op); - } + Assert.That( + () => _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!), + Is.EqualTo(UInt256.Zero).After(5000, 50)); + } + } + [Test] + public async Task Should_display_the_list_of_supported_entry_points() + { + var chain = await CreateChain(); + (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); + chain.SupportedEntryPoints(); + } - await chain.AddBlock(true); + [Test] + public async Task Should_execute_well_formed_op_successfully_for_all_entry_points() + { + var chain = await CreateChain(); + (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); - for (int i = 0; i < entryPointNum; i++) - { - UInt256 countAfter = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); - countAfter.Should().Be(1); - } + for (int i = 0; i < entryPointNum; i++) + { - Console.WriteLine("2"); - } + byte[] countCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, + _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); + byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, + _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, + counterAddress[i]!, 0, countCallData); - [Test] - public async Task Should_succeed_at_creating_account_after_prefund() - { - var chain = await CreateChain(); - chain.GasLimitCalculator.GasLimit = 20_000_000; - (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain); - - byte[] walletConstructor = _contracts.GetWalletConstructor(entryPointAddress[0]); - Address accountAddress = _contracts.GetAccountAddress(chain, entryPointAddress[0], walletConstructor, 0, 0); - - UserOperation createOp = Build.A.UserOperation - .WithSender(accountAddress!) - .WithInitCode(walletConstructor) - .WithCallGas(10_000_000) - .WithVerificationGas(2_000_000) - .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) + UserOperation op = Build.A.UserOperation + .WithSender(walletAddress[i]!) + .WithCallData(execCounterCountFromEntryPoint) + .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[i], chain.SpecProvider.ChainId) .TestObject; + /* Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(accountAddress!) - .WithGasLimit(100_000) + .WithTo(walletAddress[i]!) + .WithGasLimit(1_000_000) .WithGasPrice(2) .WithValue(1.Ether()) - .WithNonce(0) + .WithNonce((UInt256)(i)) .SignedAndResolved(TestItem.PrivateKeyB).TestObject; await chain.AddBlock(true, fundTransaction); + */ - chain.SendUserOperation(entryPointAddress[0], createOp); + UInt256 countBefore = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); + countBefore.Should().Be(0); + + chain.SendUserOperation(entryPointAddress[i], op); await chain.AddBlock(true); - chain.State.GetCode(accountAddress).Should().BeEquivalentTo(_contracts.SimpleWalletAbi.DeployedBytecode!); + UInt256 countAfter = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); + countAfter.Should().Be(1); + } - [Test] - public async Task Should_batch_multiple_ops() - { - var chain = await CreateChain(); - chain.GasLimitCalculator.GasLimit = 30_000_000; - (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); + } - byte[] countCalldata = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); - byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, counterAddress[0]!, 0, countCalldata); + [Test] + public async Task Should_execute_well_formed_op_successfully_for_all_entry_points_at_the_same_time() + { + var chain = await CreateChain(); + (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); - UserOperation op = Build.A.UserOperation - .WithSender(walletAddress[0]!) - .WithCallData(execCounterCountFromEntryPoint) - .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) - .TestObject; + for (int i = 0; i < entryPointNum; i++) + { - byte[] walletConstructor = _contracts.GetWalletConstructor(entryPointAddress[0]); - Address accountAddress = _contracts.GetAccountAddress(chain, entryPointAddress[0], walletConstructor, 0, 0); + byte[] countCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, + _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); + byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, + _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, + counterAddress[i]!, 0, countCallData); - UserOperation createOp = Build.A.UserOperation - .WithSender(accountAddress!) - .WithInitCode(walletConstructor) + UserOperation op = Build.A.UserOperation + .WithSender(walletAddress[i]!) .WithCallData(execCounterCountFromEntryPoint) - .WithCallGas(10_000_000) - .WithVerificationGas(2_000_000) - .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) + .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[i], chain.SpecProvider.ChainId) .TestObject; + /* Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(accountAddress!) - .WithGasLimit(100_000) + .WithTo(walletAddress[i]!) + .WithGasLimit(1_000_000) .WithGasPrice(2) .WithValue(1.Ether()) - .WithNonce(0) + .WithNonce((UInt256)(i)) .SignedAndResolved(TestItem.PrivateKeyB).TestObject; - Transaction fundTransaction2 = Core.Test.Builders.Build.A.Transaction - .WithTo(walletAddress[0]!) - .WithGasLimit(100_000) - .WithGasPrice(2) - .WithValue(1.Ether()) - .WithNonce(1) - .SignedAndResolved(TestItem.PrivateKeyB).TestObject; - await chain.AddBlock(true, fundTransaction, fundTransaction2); + await chain.AddBlock(true, fundTransaction); + */ - UInt256 countBefore = _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!); - UInt256 countBefore1 = _contracts.GetCount(chain, counterAddress[0]!, accountAddress!); + UInt256 countBefore = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); countBefore.Should().Be(0); - countBefore1.Should().Be(0); - - chain.SendUserOperation(entryPointAddress[0], op); - chain.SendUserOperation(entryPointAddress[0], createOp); - await chain.AddBlock(true); - - chain.State.GetCode(accountAddress).Should().BeEquivalentTo(_contracts.SimpleWalletAbi.DeployedBytecode!); - - UInt256 countAfter = _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!); - UInt256 countAfter1 = _contracts.GetCount(chain, counterAddress[0]!, accountAddress!); - countAfter.Should().Be(1); - countAfter1.Should().Be(1); + chain.SendUserOperation(entryPointAddress[i], op); } - [Test] - public async Task Should_create_account_with_tokens() - { - var chain = await CreateChain(); - chain.GasLimitCalculator.GasLimit = 20_000_000; - // string[] epAddresses = {"0xdb8b5f6080a8e466b64a8d7458326cb650b3353f", "0x90f3e1105e63c877bf9587de5388c23cdb702c6b"}; + await chain.AddBlock(true); - byte[] paymasterBytecode = Bytes.Concat( - _contracts.TokenPaymasterAbi.Bytecode!, - _encoder.Encode( - AbiEncodingStyle.None, - _contracts.TokenPaymasterAbi.Constructors[0].GetCallInfo().Signature, "tst", - new Address("0xb0894727fe4ff102e1f1c8a16f38afc7b859f215"))); + for (int i = 0; i < entryPointNum; i++) + { + UInt256 countAfter = _contracts.GetCount(chain, counterAddress[i]!, walletAddress[i]!); + countAfter.Should().Be(1); + } - (Address[] entryPointAddress, Address?[] walletAddress, Address?[] paymasterAddress) = - await _contracts.Deploy(chain, paymasterBytecode); + Console.WriteLine("2"); + } - byte[] walletConstructor = _contracts.GetWalletConstructor(entryPointAddress[0]); - Address accountAddress = _contracts.GetAccountAddress(chain, entryPointAddress[0], walletConstructor, 0, 0); + [Test] + public async Task Should_succeed_at_creating_account_after_prefund() + { + var chain = await CreateChain(); + chain.GasLimitCalculator.GasLimit = 20_000_000; + (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain); + + byte[] walletConstructor = _contracts.GetWalletConstructor(entryPointAddress[0]); + Address accountAddress = _contracts.GetAccountAddress(chain, entryPointAddress[0], walletConstructor, 0, 0); + + UserOperation createOp = Build.A.UserOperation + .WithSender(accountAddress!) + .WithInitCode(walletConstructor) + .WithCallGas(10_000_000) + .WithVerificationGas(2_000_000) + .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) + .TestObject; + + Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction + .WithTo(accountAddress!) + .WithGasLimit(100_000) + .WithGasPrice(2) + .WithValue(1.Ether()) + .WithNonce(0) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + await chain.AddBlock(true, fundTransaction); + + chain.SendUserOperation(entryPointAddress[0], createOp); + await chain.AddBlock(true); + + chain.State.GetCode(accountAddress).Should().BeEquivalentTo(_contracts.SimpleWalletAbi.DeployedBytecode!); + } - byte[] addStakeCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, - _contracts.TokenPaymasterAbi.Functions["addStake"].GetCallInfo().Signature, 0); - Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(paymasterAddress[0]!) - .WithGasLimit(100_000) - .WithGasPrice(2) - .WithValue(2.Ether()) - .WithData(addStakeCallData) - .WithNonce(chain.State.GetNonce(Contracts.ContractCreatorPrivateKey.Address)) - .SignedAndResolved(Contracts.ContractCreatorPrivateKey).TestObject; - await chain.AddBlock(true, fundTransaction); + [Test] + public async Task Should_batch_multiple_ops() + { + var chain = await CreateChain(); + chain.GasLimitCalculator.GasLimit = 30_000_000; + (Address[] entryPointAddress, Address?[] walletAddress, Address?[] counterAddress) = await _contracts.Deploy(chain, _contracts.TestCounterAbi.Bytecode!); + + byte[] countCalldata = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.TestCounterAbi.Functions["count"].GetCallInfo().Signature); + byte[] execCounterCountFromEntryPoint = _encoder.Encode(AbiEncodingStyle.IncludeSignature, _contracts.SimpleWalletAbi.Functions["execFromEntryPoint"].GetCallInfo().Signature, counterAddress[0]!, 0, countCalldata); + + UserOperation op = Build.A.UserOperation + .WithSender(walletAddress[0]!) + .WithCallData(execCounterCountFromEntryPoint) + .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) + .TestObject; + + byte[] walletConstructor = _contracts.GetWalletConstructor(entryPointAddress[0]); + Address accountAddress = _contracts.GetAccountAddress(chain, entryPointAddress[0], walletConstructor, 0, 0); + + UserOperation createOp = Build.A.UserOperation + .WithSender(accountAddress!) + .WithInitCode(walletConstructor) + .WithCallData(execCounterCountFromEntryPoint) + .WithCallGas(10_000_000) + .WithVerificationGas(2_000_000) + .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) + .TestObject; + + Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction + .WithTo(accountAddress!) + .WithGasLimit(100_000) + .WithGasPrice(2) + .WithValue(1.Ether()) + .WithNonce(0) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + Transaction fundTransaction2 = Core.Test.Builders.Build.A.Transaction + .WithTo(walletAddress[0]!) + .WithGasLimit(100_000) + .WithGasPrice(2) + .WithValue(1.Ether()) + .WithNonce(1) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + await chain.AddBlock(true, fundTransaction, fundTransaction2); + + UInt256 countBefore = _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!); + UInt256 countBefore1 = _contracts.GetCount(chain, counterAddress[0]!, accountAddress!); + countBefore.Should().Be(0); + countBefore1.Should().Be(0); + + chain.SendUserOperation(entryPointAddress[0], op); + chain.SendUserOperation(entryPointAddress[0], createOp); + await chain.AddBlock(true); + + chain.State.GetCode(accountAddress).Should().BeEquivalentTo(_contracts.SimpleWalletAbi.DeployedBytecode!); + + UInt256 countAfter = _contracts.GetCount(chain, counterAddress[0]!, walletAddress[0]!); + UInt256 countAfter1 = _contracts.GetCount(chain, counterAddress[0]!, accountAddress!); + countAfter.Should().Be(1); + countAfter1.Should().Be(1); - byte[] mintTokensCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, - _contracts.TokenPaymasterAbi.Functions["mintTokens"].GetCallInfo().Signature, accountAddress, - 1.Ether()); - Transaction mintTokensTransaction = Core.Test.Builders.Build.A.Transaction - .WithTo(paymasterAddress[0]!) - .WithGasLimit(100_000) - .WithGasPrice(2) - .WithData(mintTokensCallData) - .WithValue(0) - .WithNonce(chain.State.GetNonce(Contracts.ContractCreatorPrivateKey.Address)) - .SignedAndResolved(Contracts.ContractCreatorPrivateKey).TestObject; - await chain.AddBlock(true, mintTokensTransaction); - - UserOperation createOp = Build.A.UserOperation - .WithPaymaster(paymasterAddress[0]!) - .WithSender(accountAddress) - .WithInitCode(walletConstructor) - .WithCallGas(10_000_000) - .WithVerificationGas(2_000_000) - .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) - .TestObject; - chain.SendUserOperation(entryPointAddress[0], createOp); - await chain.AddBlock(true); + } - chain.State.GetCode(accountAddress).Should().BeEquivalentTo(_contracts.SimpleWalletAbi.DeployedBytecode!); - } + [Test] + public async Task Should_create_account_with_tokens() + { + var chain = await CreateChain(); + chain.GasLimitCalculator.GasLimit = 20_000_000; + + // string[] epAddresses = {"0xdb8b5f6080a8e466b64a8d7458326cb650b3353f", "0x90f3e1105e63c877bf9587de5388c23cdb702c6b"}; + + byte[] paymasterBytecode = Bytes.Concat( + _contracts.TokenPaymasterAbi.Bytecode!, + _encoder.Encode( + AbiEncodingStyle.None, + _contracts.TokenPaymasterAbi.Constructors[0].GetCallInfo().Signature, "tst", + new Address("0xb0894727fe4ff102e1f1c8a16f38afc7b859f215"))); + + (Address[] entryPointAddress, Address?[] walletAddress, Address?[] paymasterAddress) = + await _contracts.Deploy(chain, paymasterBytecode); + + byte[] walletConstructor = _contracts.GetWalletConstructor(entryPointAddress[0]); + Address accountAddress = _contracts.GetAccountAddress(chain, entryPointAddress[0], walletConstructor, 0, 0); + + byte[] addStakeCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, + _contracts.TokenPaymasterAbi.Functions["addStake"].GetCallInfo().Signature, 0); + Transaction fundTransaction = Core.Test.Builders.Build.A.Transaction + .WithTo(paymasterAddress[0]!) + .WithGasLimit(100_000) + .WithGasPrice(2) + .WithValue(2.Ether()) + .WithData(addStakeCallData) + .WithNonce(chain.State.GetNonce(Contracts.ContractCreatorPrivateKey.Address)) + .SignedAndResolved(Contracts.ContractCreatorPrivateKey).TestObject; + await chain.AddBlock(true, fundTransaction); + + byte[] mintTokensCallData = _encoder.Encode(AbiEncodingStyle.IncludeSignature, + _contracts.TokenPaymasterAbi.Functions["mintTokens"].GetCallInfo().Signature, accountAddress, + 1.Ether()); + Transaction mintTokensTransaction = Core.Test.Builders.Build.A.Transaction + .WithTo(paymasterAddress[0]!) + .WithGasLimit(100_000) + .WithGasPrice(2) + .WithData(mintTokensCallData) + .WithValue(0) + .WithNonce(chain.State.GetNonce(Contracts.ContractCreatorPrivateKey.Address)) + .SignedAndResolved(Contracts.ContractCreatorPrivateKey).TestObject; + await chain.AddBlock(true, mintTokensTransaction); + + UserOperation createOp = Build.A.UserOperation + .WithPaymaster(paymasterAddress[0]!) + .WithSender(accountAddress) + .WithInitCode(walletConstructor) + .WithCallGas(10_000_000) + .WithVerificationGas(2_000_000) + .SignedAndResolved(TestItem.PrivateKeyA, entryPointAddress[0], chain.SpecProvider.ChainId) + .TestObject; + chain.SendUserOperation(entryPointAddress[0], createOp); + await chain.AddBlock(true); + + chain.State.GetCode(accountAddress).Should().BeEquivalentTo(_contracts.SimpleWalletAbi.DeployedBytecode!); + } - public static void SignUserOperation(UserOperation op, PrivateKey privateKey, Address entryPointAddress, ulong chainId) - { - op.CalculateRequestId(entryPointAddress, chainId); - - Signer signer = new(chainId, privateKey, NullLogManager.Instance); - Hash256 hashedRequestId = Keccak.Compute( - Bytes.Concat( - Encoding.UTF8.GetBytes("\x19"), - Encoding.UTF8.GetBytes("Ethereum Signed Message:\n" + op.RequestId!.Bytes.Length), - op.RequestId!.Bytes) - ); - Signature signature = signer.Sign(hashedRequestId); - - op.Signature = Bytes.FromHexString(signature.ToString()); - } + public static void SignUserOperation(UserOperation op, PrivateKey privateKey, Address entryPointAddress, ulong chainId) + { + op.CalculateRequestId(entryPointAddress, chainId); + + Signer signer = new(chainId, privateKey, NullLogManager.Instance); + Hash256 hashedRequestId = Keccak.Compute( + Bytes.Concat( + Encoding.UTF8.GetBytes("\x19"), + Encoding.UTF8.GetBytes("Ethereum Signed Message:\n" + op.RequestId!.Bytes.Length), + op.RequestId!.Bytes) + ); + Signature signature = signer.Sign(hashedRequestId); + + op.Signature = Bytes.FromHexString(signature.ToString()); } } diff --git a/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs b/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs index 5c72e3724ad..3d7e6ce7bef 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction.Test/UserOperationSubscribeTests.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -28,7 +30,7 @@ using Nethermind.JsonRpc.Modules.Subscribe; using Nethermind.Synchronization.ParallelSync; using Nethermind.TxPool; -using Newtonsoft.Json; + namespace Nethermind.AccountAbstraction.Test { @@ -67,9 +69,6 @@ public void Setup() _jsonRpcDuplexClient = Substitute.For(); _jsonSerializer = new EthereumJsonSerializer(); - JsonSerializer jsonSerializer = new(); - jsonSerializer.Converters.AddRange(EthereumJsonSerializer.CommonConverters); - SubscriptionFactory subscriptionFactory = new( _logManager, _blockTree, @@ -78,7 +77,7 @@ public void Setup() _filterStore, new EthSyncingInfo(_blockTree, _receiptStorage, _syncConfig, new StaticSelector(SyncMode.All), _logManager), _specProvider, - jsonSerializer); + _jsonSerializer); subscriptionFactory.RegisterSubscriptionType( "newPendingUserOperations", diff --git a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs index 6d7f1145b67..8d11df268bb 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/AccountAbstractionPlugin.cs @@ -388,8 +388,9 @@ public Task InitBlockProducer(IConsensusPlugin consensusPlugin) private AbiDefinition LoadEntryPointContract() { + AbiParameterConverter.RegisterFactory(new AbiTypeFactory(new AbiTuple())); + AbiDefinitionParser parser = new(); - parser.RegisterAbiTypeFactory(new AbiTuple()); string json = parser.LoadContract(typeof(EntryPoint)); return parser.Parse(json); } diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/UserOperationSubscriptionParam.cs b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/UserOperationSubscriptionParam.cs index 418d0c80383..c5233ccc89d 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/UserOperationSubscriptionParam.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Subscribe/UserOperationSubscriptionParam.cs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; + using Nethermind.Core; using Nethermind.JsonRpc; -using Newtonsoft.Json; namespace Nethermind.AccountAbstraction.Subscribe; @@ -13,12 +14,11 @@ public class UserOperationSubscriptionParam : IJsonRpcParam public Address[] EntryPoints { get; set; } = Array.Empty
(); public bool IncludeUserOperations { get; set; } - public void ReadJson(JsonSerializer serializer, string jsonValue) + public void ReadJson(JsonElement jsonValue, JsonSerializerOptions options) { - UserOperationSubscriptionParam ep = serializer.Deserialize(jsonValue.ToJsonTextReader()) + UserOperationSubscriptionParam ep = JsonSerializer.Deserialize(jsonValue, options) ?? throw new ArgumentException($"Invalid 'entryPoints' filter: {jsonValue}"); EntryPoints = ep.EntryPoints; IncludeUserOperations = ep.IncludeUserOperations; - } } diff --git a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs index 8a7261f8228..f1b18c2789e 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs @@ -25,722 +25,723 @@ using Nethermind.Logging; using Nethermind.Specs; using Nethermind.State; -using Newtonsoft.Json; + using NSubstitute; using NUnit.Framework; using BlockTree = Nethermind.Blockchain.BlockTree; using Nethermind.Evm; +using Nethermind.Core.Specs; +using System.Text.Json; + +namespace Nethermind.AuRa.Test.Validators; -namespace Nethermind.AuRa.Test.Validators +public class ContractBasedValidatorTests { - public class ContractBasedValidatorTests + private IWorldState _stateProvider; + private IAbiEncoder _abiEncoder; + private ILogManager _logManager; + private AuRaParameters.Validator _validator; + private Block _block; + private BlockHeader _parentHeader; + private IReadOnlyTransactionProcessor _transactionProcessor; + private IAuRaBlockFinalizationManager _blockFinalizationManager; + private static Address _contractAddress = Address.FromNumber(1000); + private (Address Sender, byte[] TransactionData) _getValidatorsData = (Address.Zero, new byte[] { 0, 1, 2 }); + private (Address Sender, byte[] TransactionData) _finalizeChangeData = (Address.SystemUser, new byte[] { 3, 4, 5 }); + private Address[] _initialValidators; + private IBlockTree _blockTree; + private IReceiptStorage _receiptsStorage; + private IValidatorStore _validatorStore; + private IValidSealerStrategy _validSealerStrategy; + private IReadOnlyTxProcessorSource _readOnlyTxProcessorSource; + private ValidatorContract _validatorContract; + + [SetUp] + public void SetUp() { - private IWorldState _stateProvider; - private IAbiEncoder _abiEncoder; - private ILogManager _logManager; - private AuRaParameters.Validator _validator; - private Block _block; - private BlockHeader _parentHeader; - private IReadOnlyTransactionProcessor _transactionProcessor; - private IAuRaBlockFinalizationManager _blockFinalizationManager; - private static Address _contractAddress = Address.FromNumber(1000); - private (Address Sender, byte[] TransactionData) _getValidatorsData = (Address.Zero, new byte[] { 0, 1, 2 }); - private (Address Sender, byte[] TransactionData) _finalizeChangeData = (Address.SystemUser, new byte[] { 3, 4, 5 }); - private Address[] _initialValidators; - private IBlockTree _blockTree; - private IReceiptStorage _receiptsStorage; - private IValidatorStore _validatorStore; - private IValidSealerStrategy _validSealerStrategy; - private IReadOnlyTxProcessorSource _readOnlyTxProcessorSource; - private ValidatorContract _validatorContract; - - [SetUp] - public void SetUp() + _validatorStore = new ValidatorStore(new MemDb()); + _validSealerStrategy = new ValidSealerStrategy(); + _stateProvider = Substitute.For(); + _abiEncoder = Substitute.For(); + _logManager = LimboLogs.Instance; + _blockTree = Substitute.For(); + _blockFinalizationManager = Substitute.For(); + _receiptsStorage = Substitute.For(); + _validator = new AuRaParameters.Validator() { - _validatorStore = new ValidatorStore(new MemDb()); - _validSealerStrategy = new ValidSealerStrategy(); - _stateProvider = Substitute.For(); - _abiEncoder = Substitute.For(); - _logManager = LimboLogs.Instance; - _blockTree = Substitute.For(); - _blockFinalizationManager = Substitute.For(); - _receiptsStorage = Substitute.For(); - _validator = new AuRaParameters.Validator() - { - Addresses = new[] { _contractAddress }, - ValidatorType = AuRaParameters.ValidatorType.Contract - }; - _block = new Block(Build.A.BlockHeader.WithNumber(1).WithAura(1, Array.Empty()).TestObject, new BlockBody()); - - _transactionProcessor = Substitute.For(); - _transactionProcessor.IsContractDeployed(_contractAddress).Returns(true); - _readOnlyTxProcessorSource = Substitute.For(); - _readOnlyTxProcessorSource.Build(Arg.Any()).Returns(_transactionProcessor); - _stateProvider.StateRoot.Returns(TestItem.KeccakA); - _blockTree.Head.Returns(_block); - - _abiEncoder - .Encode(AbiEncodingStyle.IncludeSignature, Arg.Is(s => s.Name == "getValidators"), Arg.Any()) - .Returns(_getValidatorsData.TransactionData); + Addresses = new[] { _contractAddress }, + ValidatorType = AuRaParameters.ValidatorType.Contract + }; + _block = new Block(Build.A.BlockHeader.WithNumber(1).WithAura(1, Array.Empty()).TestObject, new BlockBody()); + + _transactionProcessor = Substitute.For(); + _transactionProcessor.IsContractDeployed(_contractAddress).Returns(true); + _readOnlyTxProcessorSource = Substitute.For(); + _readOnlyTxProcessorSource.Build(Arg.Any()).Returns(_transactionProcessor); + _stateProvider.StateRoot.Returns(TestItem.KeccakA); + _blockTree.Head.Returns(_block); + + _abiEncoder + .Encode(AbiEncodingStyle.IncludeSignature, Arg.Is(s => s.Name == "getValidators"), Arg.Any()) + .Returns(_getValidatorsData.TransactionData); + + _abiEncoder + .Encode(AbiEncodingStyle.IncludeSignature, Arg.Is(s => s.Name == "finalizeChange"), Arg.Any()) + .Returns(_finalizeChangeData.TransactionData); + + _validatorContract = new ValidatorContract(_transactionProcessor, _abiEncoder, _contractAddress, _stateProvider, _readOnlyTxProcessorSource, new Signer(0, TestItem.PrivateKeyD, LimboLogs.Instance)); + } - _abiEncoder - .Encode(AbiEncodingStyle.IncludeSignature, Arg.Is(s => s.Name == "finalizeChange"), Arg.Any()) - .Returns(_finalizeChangeData.TransactionData); + [TearDown] + public void TearDown() + { + _blockFinalizationManager?.Dispose(); + _transactionProcessor?.Dispose(); + } - _validatorContract = new ValidatorContract(_transactionProcessor, _abiEncoder, _contractAddress, _stateProvider, _readOnlyTxProcessorSource, new Signer(0, TestItem.PrivateKeyD, LimboLogs.Instance)); - } + [Test] + public void throws_ArgumentNullException_on_empty_validatorStore() + { + Action act = () => new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, null, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); + act.Should().Throw(); + } - [TearDown] - public void TearDown() - { - _blockFinalizationManager?.Dispose(); - _transactionProcessor?.Dispose(); - } + [Test] + public void throws_ArgumentNullException_on_empty_validSealearStrategy() + { + Action act = () => new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, null, _blockFinalizationManager, default, _logManager, 1); + act.Should().Throw(); + } - [Test] - public void throws_ArgumentNullException_on_empty_validatorStore() - { - Action act = () => new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, null, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); - act.Should().Throw(); - } + [Test] + public void throws_ArgumentNullException_on_empty_blockTree() + { + Action act = () => new ContractBasedValidator(_validatorContract, null, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); + act.Should().Throw(); + } - [Test] - public void throws_ArgumentNullException_on_empty_validSealearStrategy() - { - Action act = () => new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, null, _blockFinalizationManager, default, _logManager, 1); - act.Should().Throw(); - } + [Test] + public void throws_ArgumentNullException_on_empty_logManager() + { + Action act = () => new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, null, 1); + act.Should().Throw(); + } - [Test] - public void throws_ArgumentNullException_on_empty_blockTree() - { - Action act = () => new ContractBasedValidator(_validatorContract, null, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); - act.Should().Throw(); - } + [Test] + public void creates_system_account_on_start_block() + { + Address initialValidator = Address.FromNumber(2000); + SetupInitialValidators(initialValidator); + _block.Header.Beneficiary = initialValidator; + ContractBasedValidator validator = new(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); - [Test] - public void throws_ArgumentNullException_on_empty_logManager() - { - Action act = () => new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, null, 1); - act.Should().Throw(); - } + validator.OnBlockProcessingStart(_block); - [Test] - public void creates_system_account_on_start_block() - { - Address initialValidator = Address.FromNumber(2000); - SetupInitialValidators(initialValidator); - _block.Header.Beneficiary = initialValidator; - ContractBasedValidator validator = new(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); + _stateProvider.Received(1).CreateAccount(Address.SystemUser, UInt256.Zero); + _stateProvider.Received(1).Commit(Homestead.Instance); + } - validator.OnBlockProcessingStart(_block); + [Test] + public void initializes_pendingValidators_from_db() + { + _validatorStore = Substitute.For(); - _stateProvider.Received(1).CreateAccount(Address.SystemUser, UInt256.Zero); - _stateProvider.Received(1).Commit(Homestead.Instance); - } + int blockNumber = 10; + Address[] validators = TestItem.Addresses.Take(10).ToArray(); + Hash256 blockHash = Keccak.Compute("Test"); + PendingValidators pendingValidators = new(blockNumber, blockHash, validators); + _validatorStore.PendingValidators.Returns(pendingValidators); + _blockTree.Head.Returns((Block)null); - [Test] - public void initializes_pendingValidators_from_db() - { - _validatorStore = Substitute.For(); + IAuRaValidator validator = new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); - int blockNumber = 10; - Address[] validators = TestItem.Addresses.Take(10).ToArray(); - Hash256 blockHash = Keccak.Compute("Test"); - PendingValidators pendingValidators = new(blockNumber, blockHash, validators); - _validatorStore.PendingValidators.Returns(pendingValidators); - _blockTree.Head.Returns((Block)null); + _blockFinalizationManager.BlocksFinalized += + Raise.EventWith(new FinalizeEventArgs(_block.Header, + Build.A.BlockHeader.WithNumber(blockNumber).WithHash(blockHash).TestObject)); - IAuRaValidator validator = new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, default, _logManager, 1); + validator.Validators.Should().BeEquivalentTo(validators, o => o.WithStrictOrdering()); + } - _blockFinalizationManager.BlocksFinalized += - Raise.EventWith(new FinalizeEventArgs(_block.Header, - Build.A.BlockHeader.WithNumber(blockNumber).WithHash(blockHash).TestObject)); + [TestCase(1)] + [TestCase(10)] + public void loads_initial_validators_from_contract(long blockNumber) + { + Address initialValidator = TestItem.AddressA; + Block block = Build.A.Block.WithParent(_parentHeader).WithNumber(blockNumber).WithBeneficiary(initialValidator).WithAura(1, Array.Empty()).TestObject; + SetupInitialValidators(block.Header, initialValidator); + int startBlockNumber = 1; + ContractBasedValidator validator = new(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, _parentHeader, _logManager, startBlockNumber); - validator.Validators.Should().BeEquivalentTo(validators, o => o.WithStrictOrdering()); - } + bool finalizeChangeCalled = blockNumber == 1; - [TestCase(1)] - [TestCase(10)] - public void loads_initial_validators_from_contract(long blockNumber) + if (!finalizeChangeCalled) { - Address initialValidator = TestItem.AddressA; - Block block = Build.A.Block.WithParent(_parentHeader).WithNumber(blockNumber).WithBeneficiary(initialValidator).WithAura(1, Array.Empty()).TestObject; - SetupInitialValidators(block.Header, initialValidator); - int startBlockNumber = 1; - ContractBasedValidator validator = new(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, _parentHeader, _logManager, startBlockNumber); - - bool finalizeChangeCalled = blockNumber == 1; - - if (!finalizeChangeCalled) - { - validator.Validators = new[] { TestItem.AddressD }; - } - - validator.OnBlockProcessingStart(block); - - // getValidators should have been called - _transactionProcessor.Received() - .CallAndRestore( - Arg.Is(t => CheckTransaction(t, _getValidatorsData)), - Arg.Is(blkCtx => blkCtx.Header.Equals(_parentHeader)), - Arg.Is(t => t is CallOutputTracer)); - - // finalizeChange should be called - _transactionProcessor.Received(finalizeChangeCalled ? 1 : 0) - .Execute(Arg.Is(t => CheckTransaction(t, _finalizeChangeData)), - Arg.Is(blkCtx => blkCtx.Header.Equals(block.Header)), - Arg.Is(t => t is CallOutputTracer)); - - // initial validator should be true - Address[] expectedValidators = { initialValidator }; - validator.Validators.Should().BeEquivalentTo(expectedValidators, o => o.WithStrictOrdering()); - _validatorStore.GetValidators().Should().BeEquivalentTo(expectedValidators.AsEnumerable()); + validator.Validators = new[] { TestItem.AddressD }; } - public static IEnumerable ConsecutiveInitiateChangeData + validator.OnBlockProcessingStart(block); + + // getValidators should have been called + _transactionProcessor.Received() + .CallAndRestore( + Arg.Is(t => CheckTransaction(t, _getValidatorsData)), + Arg.Is(blkCtx => blkCtx.Header.Equals(_parentHeader)), + Arg.Is(t => t is CallOutputTracer)); + + // finalizeChange should be called + _transactionProcessor.Received(finalizeChangeCalled ? 1 : 0) + .Execute(Arg.Is(t => CheckTransaction(t, _finalizeChangeData)), + Arg.Is(blkCtx => blkCtx.Header.Equals(block.Header)), + Arg.Is(t => t is CallOutputTracer)); + + // initial validator should be true + Address[] expectedValidators = { initialValidator }; + validator.Validators.Should().BeEquivalentTo(expectedValidators, o => o.WithStrictOrdering()); + _validatorStore.GetValidators().Should().BeEquivalentTo(expectedValidators.AsEnumerable()); + } + + public static IEnumerable ConsecutiveInitiateChangeData + { + get { - get + yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters { - yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + StartBlockNumber = 1, + Reorganisations = new Dictionary() { - StartBlockNumber = 1, - Reorganisations = new Dictionary() { + 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() { - 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + BlockNumber = 1, + ExpectedFinalizationCount = 6, + NumberOfSteps = 30, + Validators = new List() { - BlockNumber = 1, - ExpectedFinalizationCount = 6, - NumberOfSteps = 30, - Validators = new List() + new() { - new() - { - Addresses = GenerateValidators(1), - InitializeBlock = 0, - FinalizeBlock = 0 - }, - new() - { - Addresses = GenerateValidators(2), - InitializeBlock = 3, - FinalizeBlock = 3 - }, - new() - { - Addresses = GenerateValidators(3), - InitializeBlock = 6, - FinalizeBlock = 7 - }, - new() - { - Addresses = GenerateValidators(4), - InitializeBlock = 10, - FinalizeBlock = 11 - }, - new() - { - Addresses = GenerateValidators(10), - InitializeBlock = 15, - FinalizeBlock = 17 - }, - new() - { - Addresses = GenerateValidators(5), - InitializeBlock = 20, - FinalizeBlock = 25 - }, - } + Addresses = GenerateValidators(1), + InitializeBlock = 0, + FinalizeBlock = 0 + }, + new() + { + Addresses = GenerateValidators(2), + InitializeBlock = 3, + FinalizeBlock = 3 + }, + new() + { + Addresses = GenerateValidators(3), + InitializeBlock = 6, + FinalizeBlock = 7 + }, + new() + { + Addresses = GenerateValidators(4), + InitializeBlock = 10, + FinalizeBlock = 11 + }, + new() + { + Addresses = GenerateValidators(10), + InitializeBlock = 15, + FinalizeBlock = 17 + }, + new() + { + Addresses = GenerateValidators(5), + InitializeBlock = 20, + FinalizeBlock = 25 + }, } } - }, - TestName = "consecutive_initiate_change_gets_finalized_and_switch_validators" - }) - { - TestName = "consecutive_initiate_change_gets_finalized_and_switch_validators" - }; + } + }, + TestName = "consecutive_initiate_change_gets_finalized_and_switch_validators" + }) + { + TestName = "consecutive_initiate_change_gets_finalized_and_switch_validators" + }; - yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + { + StartBlockNumber = 1, + Reorganisations = new Dictionary() { - StartBlockNumber = 1, - Reorganisations = new Dictionary() { + 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() { - 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + BlockNumber = 1, + ExpectedFinalizationCount = 4, + NumberOfSteps = 11, + Validators = new List() { - BlockNumber = 1, - ExpectedFinalizationCount = 4, - NumberOfSteps = 11, - Validators = new List() + new() { - new() - { - Addresses = GenerateValidators(1), - InitializeBlock = 0, - FinalizeBlock = 0 - }, - new() - { - Addresses = GenerateValidators(5), - InitializeBlock = 3, - FinalizeBlock = 3 - }, - new() - { - Addresses = GenerateValidators(2), - InitializeBlock = 5, - FinalizeBlock = 7 - }, - new() - { - Addresses = GenerateValidators(20), - InitializeBlock = 7, - FinalizeBlock = Int32.MaxValue // IgnoredInitializeChange - }, - new() - { - Addresses = GenerateValidators(3), - InitializeBlock = 9, - FinalizeBlock = 10 - }, - } + Addresses = GenerateValidators(1), + InitializeBlock = 0, + FinalizeBlock = 0 + }, + new() + { + Addresses = GenerateValidators(5), + InitializeBlock = 3, + FinalizeBlock = 3 + }, + new() + { + Addresses = GenerateValidators(2), + InitializeBlock = 5, + FinalizeBlock = 7 + }, + new() + { + Addresses = GenerateValidators(20), + InitializeBlock = 7, + FinalizeBlock = Int32.MaxValue // IgnoredInitializeChange + }, + new() + { + Addresses = GenerateValidators(3), + InitializeBlock = 9, + FinalizeBlock = 10 + }, } } - }, - TestName = "consecutive_initiate_change_gets_finalized_ignoring_duplicate_initiate_change" - }) - { - TestName = "consecutive_initiate_change_gets_finalized_ignoring_duplicate_initiate_change" - }; + } + }, + TestName = "consecutive_initiate_change_gets_finalized_ignoring_duplicate_initiate_change" + }) + { + TestName = "consecutive_initiate_change_gets_finalized_ignoring_duplicate_initiate_change" + }; - yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + { + StartBlockNumber = 1, + Reorganisations = new Dictionary() { - StartBlockNumber = 1, - Reorganisations = new Dictionary() { + 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() { - 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + BlockNumber = 1, + ExpectedFinalizationCount = 2, + NumberOfSteps = 11, + Validators = new List() { - BlockNumber = 1, - ExpectedFinalizationCount = 2, - NumberOfSteps = 11, - Validators = new List() + new() { - new() - { - Addresses = GenerateValidators(1), - InitializeBlock = 0, - FinalizeBlock = 0 - }, - new() - { - Addresses = GenerateValidators(5), - InitializeBlock = 3, - FinalizeBlock = 3 - }, - new() - // this will not get finalized because of reorganisation - { - Addresses = GenerateValidators(2), - InitializeBlock = 5, - FinalizeBlock = 7 - }, - } - } - }, - { - 7, new ConsecutiveInitiateChangeTestParameters.ChainInfo() - { - BlockNumber = 5, //reorganisation to block 5 in order to invalidate last initiate change - ExpectedFinalizationCount = 0, - NumberOfSteps = 10, + Addresses = GenerateValidators(1), + InitializeBlock = 0, + FinalizeBlock = 0 + }, + new() + { + Addresses = GenerateValidators(5), + InitializeBlock = 3, + FinalizeBlock = 3 + }, + new() + // this will not get finalized because of reorganisation + { + Addresses = GenerateValidators(2), + InitializeBlock = 5, + FinalizeBlock = 7 + }, } } }, - TestName = "consecutive_initiate_change_reorganisation_ignores_reorganised_initiate_change" - }) - { - TestName = "consecutive_initiate_change_reorganisation_ignores_reorganised_initiate_change" - }; + { + 7, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + { + BlockNumber = 5, //reorganisation to block 5 in order to invalidate last initiate change + ExpectedFinalizationCount = 0, + NumberOfSteps = 10, + } + } + }, + TestName = "consecutive_initiate_change_reorganisation_ignores_reorganised_initiate_change" + }) + { + TestName = "consecutive_initiate_change_reorganisation_ignores_reorganised_initiate_change" + }; - yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + { + StartBlockNumber = 1, + Reorganisations = new Dictionary() { - StartBlockNumber = 1, - Reorganisations = new Dictionary() { + 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() { - 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + BlockNumber = 1, + ExpectedFinalizationCount = 2, + NumberOfSteps = 11, + Validators = new List() { - BlockNumber = 1, - ExpectedFinalizationCount = 2, - NumberOfSteps = 11, - Validators = new List() + new() { - new() - { - Addresses = GenerateValidators(1), - InitializeBlock = 0, - FinalizeBlock = 0 - }, - new() - { - Addresses = GenerateValidators(5), - InitializeBlock = 3, - FinalizeBlock = 3 - }, - } - } - }, - { - 7, new ConsecutiveInitiateChangeTestParameters.ChainInfo() - { - BlockNumber = 6, - ExpectedFinalizationCount = 1, - NumberOfSteps = 10, - Validators = new List() + Addresses = GenerateValidators(1), + InitializeBlock = 0, + FinalizeBlock = 0 + }, + new() { - new() - { - Addresses = GenerateValidators(7), - InitializeBlock = 8, - FinalizeBlock = 10 - } + Addresses = GenerateValidators(5), + InitializeBlock = 3, + FinalizeBlock = 3 }, } } }, - TestName = "consecutive_initiate_change_reorganisation_finalizes_after_reorganisation" - }) - { - TestName = "consecutive_initiate_change_reorganisation_finalizes_after_reorganisation" - }; - - yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters - { - StartBlockNumber = 1, - Reorganisations = new Dictionary() { + 7, new ConsecutiveInitiateChangeTestParameters.ChainInfo() { - 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + BlockNumber = 6, + ExpectedFinalizationCount = 1, + NumberOfSteps = 10, + Validators = new List() { - BlockNumber = 1, - ExpectedFinalizationCount = 2, - NumberOfSteps = 11, - Validators = new List() + new() { - new() - { - Addresses = GenerateValidators(1), - InitializeBlock = 0, - FinalizeBlock = 0 - }, - new() - { - Addresses = GenerateValidators(5), - InitializeBlock = 3, - FinalizeBlock = 3 - }, - new() - { - Addresses = GenerateValidators(2), - InitializeBlock = 5, - FinalizeBlock = 7 - }, + Addresses = GenerateValidators(7), + InitializeBlock = 8, + FinalizeBlock = 10 } - } - }, + }, + } + } + }, + TestName = "consecutive_initiate_change_reorganisation_finalizes_after_reorganisation" + }) + { + TestName = "consecutive_initiate_change_reorganisation_finalizes_after_reorganisation" + }; + + yield return new TestCaseData(new ConsecutiveInitiateChangeTestParameters + { + StartBlockNumber = 1, + Reorganisations = new Dictionary() + { + { + 1, new ConsecutiveInitiateChangeTestParameters.ChainInfo() { - 7, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + BlockNumber = 1, + ExpectedFinalizationCount = 2, + NumberOfSteps = 11, + Validators = new List() { - BlockNumber = 6, //reorganisation to block 6 in order to keep last initiate change - ExpectedFinalizationCount = 2, - NumberOfSteps = 10, - Validators = new List() + new() + { + Addresses = GenerateValidators(1), + InitializeBlock = 0, + FinalizeBlock = 0 + }, + new() + { + Addresses = GenerateValidators(5), + InitializeBlock = 3, + FinalizeBlock = 3 + }, + new() { - new() - { - Addresses = GenerateValidators(7), - InitializeBlock = 10, - FinalizeBlock = 11 - } + Addresses = GenerateValidators(2), + InitializeBlock = 5, + FinalizeBlock = 7 }, } } }, - TestName = "consecutive_initiate_change_reorganisation_finalizes_not_reorganised_initiate_change", - }) - { - TestName = "consecutive_initiate_change_reorganisation_finalizes_not_reorganised_initiate_change", - }; - } + { + 7, new ConsecutiveInitiateChangeTestParameters.ChainInfo() + { + BlockNumber = 6, //reorganisation to block 6 in order to keep last initiate change + ExpectedFinalizationCount = 2, + NumberOfSteps = 10, + Validators = new List() + { + new() + { + Addresses = GenerateValidators(7), + InitializeBlock = 10, + FinalizeBlock = 11 + } + }, + } + } + }, + TestName = "consecutive_initiate_change_reorganisation_finalizes_not_reorganised_initiate_change", + }) + { + TestName = "consecutive_initiate_change_reorganisation_finalizes_not_reorganised_initiate_change", + }; } + } - [TestCaseSource(nameof(ConsecutiveInitiateChangeData))] - public void consecutive_initiate_change_gets_finalized_and_switch_validators(ConsecutiveInitiateChangeTestParameters test) - { - Dictionary hashSeeds = new(); + [TestCaseSource(nameof(ConsecutiveInitiateChangeData))] + public void consecutive_initiate_change_gets_finalized_and_switch_validators(ConsecutiveInitiateChangeTestParameters test) + { + Dictionary hashSeeds = new(); - Address[] currentValidators = GenerateValidators(1); - SetupInitialValidators(currentValidators); + Address[] currentValidators = GenerateValidators(1); + SetupInitialValidators(currentValidators); - IAuRaValidator validator = new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, _blockTree.Head.Header, _logManager, test.StartBlockNumber); + IAuRaValidator validator = new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, _blockTree.Head.Header, _logManager, test.StartBlockNumber); - test.TryDoReorganisations(test.StartBlockNumber, out _); - for (int i = 0; i < test.Current.NumberOfSteps; i++) - { - int blockNumber = test.Current.BlockNumber + i; + test.TryDoReorganisations(test.StartBlockNumber, out _); + for (int i = 0; i < test.Current.NumberOfSteps; i++) + { + int blockNumber = test.Current.BlockNumber + i; - if (test.TryDoReorganisations(blockNumber, out ConsecutiveInitiateChangeTestParameters.ChainInfo lastChain)) - { - ValidateFinalizationForChain(lastChain); - i = 0; - blockNumber = test.Current.BlockNumber + i; - } - - if (hashSeeds.TryGetValue(blockNumber, out int value)) - value++; - else - hashSeeds[blockNumber] = 0; - - _block.Header.Number = blockNumber; - _block.Header.Beneficiary = currentValidators[blockNumber % currentValidators.Length]; - _block.Header.AuRaStep = blockNumber; - _block.Header.Hash = Keccak.Compute((blockNumber + hashSeeds[blockNumber]).ToString()); - _block.Header.ParentHash = blockNumber == test.StartBlockNumber ? Keccak.Zero : Keccak.Compute((blockNumber - 1 + hashSeeds[blockNumber - 1]).ToString()); - - TxReceipt[] txReceipts = test.GetReceipts(_validatorContract, _block, _contractAddress, _abiEncoder, SetupAbiAddresses); - - Hash256? blockHashForClosure = _block.Hash; - _receiptsStorage.Get(Arg.Is(b => b.Hash == blockHashForClosure)).Returns(txReceipts); - - _block.Header.Bloom = new Bloom(txReceipts.SelectMany(r => r.Logs).ToArray()); - - _blockTree.FindBlock(_block.Header.Hash, Arg.Any()).Returns(new Block(_block.Header.Clone())); - - Action preProcess = () => validator.OnBlockProcessingStart(_block); - preProcess.Should().NotThrow(test.TestName); - validator.OnBlockProcessingEnd(_block, txReceipts); - int finalizedNumber = blockNumber - validator.Validators.MinSealersForFinalization() + 1; - _blockFinalizationManager.GetLastLevelFinalizedBy(_block.Header.Hash).Returns(finalizedNumber); - _blockFinalizationManager.BlocksFinalized += Raise.EventWith( - new FinalizeEventArgs(_block.Header, Build.A.BlockHeader.WithNumber(finalizedNumber) - .WithHash(Keccak.Compute((finalizedNumber + hashSeeds[finalizedNumber]).ToString())).TestObject)); - - currentValidators = test.GetCurrentValidators(blockNumber); - validator.Validators.Should().BeEquivalentTo(currentValidators, o => o.WithStrictOrdering(), $"Validator address should be recognized in block {blockNumber}"); + if (test.TryDoReorganisations(blockNumber, out ConsecutiveInitiateChangeTestParameters.ChainInfo lastChain)) + { + ValidateFinalizationForChain(lastChain); + i = 0; + blockNumber = test.Current.BlockNumber + i; } - ValidateFinalizationForChain(test.Current); + if (hashSeeds.TryGetValue(blockNumber, out int value)) + value++; + else + hashSeeds[blockNumber] = 0; + + _block.Header.Number = blockNumber; + _block.Header.Beneficiary = currentValidators[blockNumber % currentValidators.Length]; + _block.Header.AuRaStep = blockNumber; + _block.Header.Hash = Keccak.Compute((blockNumber + hashSeeds[blockNumber]).ToString()); + _block.Header.ParentHash = blockNumber == test.StartBlockNumber ? Keccak.Zero : Keccak.Compute((blockNumber - 1 + hashSeeds[blockNumber - 1]).ToString()); + + TxReceipt[] txReceipts = test.GetReceipts(_validatorContract, _block, _contractAddress, _abiEncoder, SetupAbiAddresses); + + Hash256? blockHashForClosure = _block.Hash; + _receiptsStorage.Get(Arg.Is(b => b.Hash == blockHashForClosure)).Returns(txReceipts); + + _block.Header.Bloom = new Bloom(txReceipts.SelectMany(r => r.Logs).ToArray()); + + _blockTree.FindBlock(_block.Header.Hash, Arg.Any()).Returns(new Block(_block.Header.Clone())); + + Action preProcess = () => validator.OnBlockProcessingStart(_block); + preProcess.Should().NotThrow(test.TestName); + validator.OnBlockProcessingEnd(_block, txReceipts); + int finalizedNumber = blockNumber - validator.Validators.MinSealersForFinalization() + 1; + _blockFinalizationManager.GetLastLevelFinalizedBy(_block.Header.Hash).Returns(finalizedNumber); + _blockFinalizationManager.BlocksFinalized += Raise.EventWith( + new FinalizeEventArgs(_block.Header, Build.A.BlockHeader.WithNumber(finalizedNumber) + .WithHash(Keccak.Compute((finalizedNumber + hashSeeds[finalizedNumber]).ToString())).TestObject)); + + currentValidators = test.GetCurrentValidators(blockNumber); + validator.Validators.Should().BeEquivalentTo(currentValidators, o => o.WithStrictOrdering(), $"Validator address should be recognized in block {blockNumber}"); } - [TestCase(8, 5, null)] - [TestCase(7, 5, 7)] - [TestCase(6, 5, 7)] - [TestCase(5, 5, 7)] - [TestCase(4, 4, 7)] - [TestCase(2, 2, 4)] - [TestCase(1, 1, 4)] - [TestCase(1, 7, null)] - public void nonconsecutive_non_producing_preProcess_loads_pending_validators_from_receipts(int lastLevelFinalized, int initialValidatorsIndex, int? expectedBlockValidators) + ValidateFinalizationForChain(test.Current); + } + + [TestCase(8, 5, null)] + [TestCase(7, 5, 7)] + [TestCase(6, 5, 7)] + [TestCase(5, 5, 7)] + [TestCase(4, 4, 7)] + [TestCase(2, 2, 4)] + [TestCase(1, 1, 4)] + [TestCase(1, 7, null)] + public void nonconsecutive_non_producing_preProcess_loads_pending_validators_from_receipts(int lastLevelFinalized, int initialValidatorsIndex, int? expectedBlockValidators) + { + IEnumerable GetAllBlocks(BlockTree bt) { - IEnumerable GetAllBlocks(BlockTree bt) - { - Block? block = bt.FindBlock(bt.Head.Hash, BlockTreeLookupOptions.None); - while (block is not null) - { - yield return block; - block = bt.FindBlock(block.ParentHash, BlockTreeLookupOptions.None); - } + Block? block = bt.FindBlock(bt.Head.Hash, BlockTreeLookupOptions.None); + while (block is not null) + { + yield return block; + block = bt.FindBlock(block.ParentHash, BlockTreeLookupOptions.None); } + } - Address validators = TestItem.Addresses[initialValidatorsIndex * 10]; - InMemoryReceiptStorage inMemoryReceiptStorage = new(); - BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(MainnetSpecProvider.Instance) - .WithTransactions(inMemoryReceiptStorage, delegate (Block block, Transaction transaction) + Address validators = TestItem.Addresses[initialValidatorsIndex * 10]; + InMemoryReceiptStorage inMemoryReceiptStorage = new(); + BlockTreeBuilder blockTreeBuilder = Build.A.BlockTree(MainnetSpecProvider.Instance) + .WithTransactions(inMemoryReceiptStorage, delegate (Block block, Transaction transaction) + { + byte i = 0; + return new[] { - byte i = 0; - return new[] - { Build.A.LogEntry.WithAddress(_contractAddress) .WithData(new[] {(byte) (block.Number * 10 + i++)}) .WithTopics(_validatorContract.AbiDefinition.Events[ValidatorContract.InitiateChange].GetHash(), block.ParentHash) .TestObject - }; - }) - .OfChainLength(9, 0, 0, false, validators); - - BlockTree blockTree = blockTreeBuilder.TestObject; - SetupInitialValidators(blockTree.Head?.Header, blockTree.FindHeader(blockTree.Head?.ParentHash, BlockTreeLookupOptions.None), validators); - IAuRaValidator validator = new ContractBasedValidator(_validatorContract, blockTree, inMemoryReceiptStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, _parentHeader, _logManager, 1); + }; + }) + .OfChainLength(9, 0, 0, false, validators); - _abiEncoder.Decode(_validatorContract.AbiDefinition.Functions[ValidatorContract.GetValidatorsFunction].GetReturnInfo(), Arg.Any()) - .Returns(c => - { - byte addressIndex = c.Arg()[0]; - return new object[] { new Address[] { TestItem.Addresses[addressIndex] } }; - }); + BlockTree blockTree = blockTreeBuilder.TestObject; + SetupInitialValidators(blockTree.Head?.Header, blockTree.FindHeader(blockTree.Head?.ParentHash, BlockTreeLookupOptions.None), validators); + IAuRaValidator validator = new ContractBasedValidator(_validatorContract, blockTree, inMemoryReceiptStorage, _validatorStore, _validSealerStrategy, _blockFinalizationManager, _parentHeader, _logManager, 1); - _blockFinalizationManager.GetLastLevelFinalizedBy(blockTree.Head.ParentHash).Returns(lastLevelFinalized); + _abiEncoder.Decode(_validatorContract.AbiDefinition.Functions[ValidatorContract.GetValidatorsFunction].GetReturnInfo(), Arg.Any()) + .Returns(c => + { + byte addressIndex = c.Arg()[0]; + return new object[] { new Address[] { TestItem.Addresses[addressIndex] } }; + }); - validator.OnBlockProcessingStart(blockTree.FindBlock(blockTree.Head.Hash, BlockTreeLookupOptions.None)); + _blockFinalizationManager.GetLastLevelFinalizedBy(blockTree.Head.ParentHash).Returns(lastLevelFinalized); - PendingValidators pendingValidators = null; - if (expectedBlockValidators.HasValue) - { - Block block = GetAllBlocks(blockTree).First(b => b.Number == expectedBlockValidators.Value); - pendingValidators = new PendingValidators(block.Number, block.Hash, new[] { TestItem.Addresses[block.Number * 10] }); - } + validator.OnBlockProcessingStart(blockTree.FindBlock(blockTree.Head.Hash, BlockTreeLookupOptions.None)); - _validatorStore.PendingValidators.Should().BeEquivalentTo(pendingValidators); + PendingValidators pendingValidators = null; + if (expectedBlockValidators.HasValue) + { + Block block = GetAllBlocks(blockTree).First(b => b.Number == expectedBlockValidators.Value); + pendingValidators = new PendingValidators(block.Number, block.Hash, new[] { TestItem.Addresses[block.Number * 10] }); } + _validatorStore.PendingValidators.Should().BeEquivalentTo(pendingValidators); + } - private void ValidateFinalizationForChain(ConsecutiveInitiateChangeTestParameters.ChainInfo chain) - { - // finalizeChange should be called or not based on test spec - _transactionProcessor.Received(chain.ExpectedFinalizationCount) - .Execute(Arg.Is(t => CheckTransaction(t, _finalizeChangeData)), - Arg.Is(blkCtx => blkCtx.Header.Equals(_block.Header)), - Arg.Is(t => t is CallOutputTracer)); - _transactionProcessor.ClearReceivedCalls(); - } + private void ValidateFinalizationForChain(ConsecutiveInitiateChangeTestParameters.ChainInfo chain) + { + // finalizeChange should be called or not based on test spec + _transactionProcessor.Received(chain.ExpectedFinalizationCount) + .Execute(Arg.Is(t => CheckTransaction(t, _finalizeChangeData)), + Arg.Is(blkCtx => blkCtx.Header.Equals(_block.Header)), + Arg.Is(t => t is CallOutputTracer)); - private static Address[] GenerateValidators(int number) => - Enumerable.Range(1, number).Select(i => Address.FromNumber((UInt256)i)).ToArray(); + _transactionProcessor.ClearReceivedCalls(); + } - private void SetupInitialValidators(params Address[] initialValidators) - { - SetupInitialValidators(_block.Header, initialValidators); - } + private static Address[] GenerateValidators(int number) => + Enumerable.Range(1, number).Select(i => Address.FromNumber((UInt256)i)).ToArray(); - private void SetupInitialValidators(BlockHeader header, params Address[] initialValidators) - { - SetupInitialValidators(header, null, initialValidators); - } + private void SetupInitialValidators(params Address[] initialValidators) + { + SetupInitialValidators(_block.Header, initialValidators); + } - private void SetupInitialValidators(BlockHeader header, BlockHeader parentHeader, params Address[] initialValidators) - { - _initialValidators = initialValidators; + private void SetupInitialValidators(BlockHeader header, params Address[] initialValidators) + { + SetupInitialValidators(header, null, initialValidators); + } - if (parentHeader is null) - { - parentHeader = _parentHeader = Build.A.BlockHeader.WithNumber(header.Number - 1).TestObject; - _blockTree.FindHeader(header.ParentHash, BlockTreeLookupOptions.None).Returns(_parentHeader); - } + private void SetupInitialValidators(BlockHeader header, BlockHeader parentHeader, params Address[] initialValidators) + { + _initialValidators = initialValidators; - _transactionProcessor.When(x => x.CallAndRestore( - Arg.Is(t => CheckTransaction(t, _getValidatorsData)), - Arg.Any(), - Arg.Is(t => t is CallOutputTracer))) - .Do(args => - args.Arg().MarkAsSuccess( - args.Arg().To, - 0, - SetupAbiAddresses(_initialValidators), - Array.Empty())); + if (parentHeader is null) + { + parentHeader = _parentHeader = Build.A.BlockHeader.WithNumber(header.Number - 1).TestObject; + _blockTree.FindHeader(header.ParentHash, BlockTreeLookupOptions.None).Returns(_parentHeader); } - private byte[] SetupAbiAddresses(Address[] addresses) - { - byte[] data = addresses.SelectMany(a => a.Bytes).ToArray(); + _transactionProcessor.When(x => x.CallAndRestore( + Arg.Is(t => CheckTransaction(t, _getValidatorsData)), + Arg.Any(), + Arg.Is(t => t is CallOutputTracer))) + .Do(args => + args.Arg().MarkAsSuccess( + args.Arg().To, + 0, + SetupAbiAddresses(_initialValidators), + Array.Empty())); + } - _abiEncoder.Decode( - AbiEncodingStyle.None, - Arg.Is(s => s.Types.Length == 1 && s.Types[0].CSharpType == typeof(Address[])), - data).Returns(new object[] { addresses }); + private byte[] SetupAbiAddresses(Address[] addresses) + { + byte[] data = addresses.SelectMany(a => a.Bytes).ToArray(); - return data; - } + _abiEncoder.Decode( + AbiEncodingStyle.None, + Arg.Is(s => s.Types.Length == 1 && s.Types[0].CSharpType == typeof(Address[])), + data).Returns(new object[] { addresses }); - private bool CheckTransaction(Transaction t, (Address Sender, byte[] TransactionData) transactionInfo) - { - return t.SenderAddress == transactionInfo.Sender && t.To == _contractAddress && t.Data.AsArray() == transactionInfo.TransactionData; - } + return data; + } - public class ConsecutiveInitiateChangeTestParameters - { - private ChainInfo _last; + private bool CheckTransaction(Transaction t, (Address Sender, byte[] TransactionData) transactionInfo) + { + return t.SenderAddress == transactionInfo.Sender && t.To == _contractAddress && t.Data.AsArray() == transactionInfo.TransactionData; + } - public int StartBlockNumber { get; set; } + public class ConsecutiveInitiateChangeTestParameters + { + private ChainInfo _last; + + public int StartBlockNumber { get; set; } - public ChainInfo Current { get; set; } + public ChainInfo Current { get; set; } - public IDictionary Reorganisations { get; set; } + public IDictionary Reorganisations { get; set; } - public string TestName { get; set; } + public string TestName { get; set; } - public override string ToString() => JsonConvert.SerializeObject(this); + public override string ToString() => JsonSerializer.Serialize(this); - public bool TryDoReorganisations(int blockNumber, out ChainInfo last) + public bool TryDoReorganisations(int blockNumber, out ChainInfo last) + { + if (Reorganisations.TryGetValue(blockNumber, out ChainInfo chainInfo)) { - if (Reorganisations.TryGetValue(blockNumber, out ChainInfo chainInfo)) - { - _last = last = Current; - Current = chainInfo; - Reorganisations.Remove(blockNumber); - return true; - } - - last = null; - return false; + _last = last = Current; + Current = chainInfo; + Reorganisations.Remove(blockNumber); + return true; } - public TxReceipt[] GetReceipts(ValidatorContract validatorContract, Block block, Address contractAddress, IAbiEncoder encoder, Func dataFunc) + last = null; + return false; + } + + public TxReceipt[] GetReceipts(ValidatorContract validatorContract, Block block, Address contractAddress, IAbiEncoder encoder, Func dataFunc) + { + Address[] validators = Current.Validators?.FirstOrDefault(v => v.InitializeBlock == block.Number)?.Addresses; + if (validators is null) { - Address[] validators = Current.Validators?.FirstOrDefault(v => v.InitializeBlock == block.Number)?.Addresses; - if (validators is null) - { - return Array.Empty(); - } - else + return Array.Empty(); + } + else + { + LogEntry[] logs = new[] { - LogEntry[] logs = new[] - { - new LogEntry(contractAddress, - dataFunc(validators), - new[] {validatorContract.AbiDefinition.Events[ValidatorContract.InitiateChange].GetHash(), block.ParentHash}) - }; + new LogEntry(contractAddress, + dataFunc(validators), + new[] {validatorContract.AbiDefinition.Events[ValidatorContract.InitiateChange].GetHash(), block.ParentHash}) + }; - return new TxReceipt[] + return new TxReceipt[] + { + new() { - new() - { - Logs = logs, - Bloom = new Bloom(logs) - } - }; - } + Logs = logs, + Bloom = new Bloom(logs) + } + }; } + } - public Address[] GetCurrentValidators(int blockNumber) - { - ValidatorsInfo LastFinalizedInitChange(ChainInfo chainInfo, int maxInitializeBlockNumber = int.MaxValue) => chainInfo?.Validators?.LastOrDefault(v => v.FinalizeBlock <= blockNumber && v.InitializeBlock < maxInitializeBlockNumber); + public Address[] GetCurrentValidators(int blockNumber) + { + ValidatorsInfo LastFinalizedInitChange(ChainInfo chainInfo, int maxInitializeBlockNumber = int.MaxValue) => chainInfo?.Validators?.LastOrDefault(v => v.FinalizeBlock <= blockNumber && v.InitializeBlock < maxInitializeBlockNumber); - ValidatorsInfo finalizedInitChange = LastFinalizedInitChange(Current); - ValidatorsInfo previousReorgFinalizedInitChange = LastFinalizedInitChange(_last, Current.BlockNumber); - ValidatorsInfo lastFinalizedInitChange = finalizedInitChange ?? previousReorgFinalizedInitChange; - return lastFinalizedInitChange?.Addresses; - } + ValidatorsInfo finalizedInitChange = LastFinalizedInitChange(Current); + ValidatorsInfo previousReorgFinalizedInitChange = LastFinalizedInitChange(_last, Current.BlockNumber); + ValidatorsInfo lastFinalizedInitChange = finalizedInitChange ?? previousReorgFinalizedInitChange; + return lastFinalizedInitChange?.Addresses; + } - public class ValidatorsInfo - { - public Address[] Addresses { get; set; } + public class ValidatorsInfo + { + public Address[] Addresses { get; set; } - public int InitializeBlock { get; set; } + public int InitializeBlock { get; set; } - public int FinalizeBlock { get; set; } - } + public int FinalizeBlock { get; set; } + } - public class ChainInfo - { - public int BlockNumber { get; set; } - public int NumberOfSteps { get; set; } - public int ExpectedFinalizationCount { get; set; } - public IList Validators { get; set; } - } + public class ChainInfo + { + public int BlockNumber { get; set; } + public int NumberOfSteps { get; set; } + public int ExpectedFinalizationCount { get; set; } + public IList Validators { get; set; } } } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Specs/empty_accounts_and_storages.json b/src/Nethermind/Nethermind.Blockchain.Test/Specs/empty_accounts_and_storages.json index c8e896c5bf5..1a519793393 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Specs/empty_accounts_and_storages.json +++ b/src/Nethermind/Nethermind.Blockchain.Test/Specs/empty_accounts_and_storages.json @@ -181,4 +181,3 @@ } } } -} diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionConverter.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionConverter.cs deleted file mode 100644 index fa96bbb92f5..00000000000 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionConverter.cs +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Linq; -using FastEnumUtility; -using Nethermind.Abi; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Nethermind.Blockchain.Contracts.Json -{ - public class AbiDefinitionConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, AbiDefinition value, JsonSerializer serializer) - { - writer.WriteStartArray(); - - foreach (AbiBaseDescription item in value.Items) - { - serializer.Serialize(writer, item); - } - - writer.WriteEndArray(); - } - - private readonly string _nameTokenName = nameof(AbiBaseDescription.Name).ToLowerInvariant(); - private readonly string _typeTokenName = nameof(AbiBaseDescription.Type).ToLowerInvariant(); - - public override AbiDefinition ReadJson( - JsonReader reader, - Type objectType, - AbiDefinition existingValue, - bool hasExistingValue, - JsonSerializer serializer) - { - JToken topLevelToken = JToken.Load(reader); - existingValue ??= new AbiDefinition(); - - JToken abiToken; - if (topLevelToken.Type == JTokenType.Object) - { - abiToken = topLevelToken["abi"]; - byte[] bytecode = Bytes.FromHexString(topLevelToken["bytecode"]?.Value() ?? string.Empty); - byte[] deployedBytecode = Bytes.FromHexString(topLevelToken["deployedBytecode"]?.Value() ?? string.Empty); - existingValue.SetBytecode(bytecode); - existingValue.SetDeployedBytecode(deployedBytecode); - } - else - { - abiToken = topLevelToken; - } - - foreach (var definitionToken in abiToken?.Children() ?? Enumerable.Empty()) - { - string name = definitionToken[_nameTokenName]?.Value(); - JToken typeToken = definitionToken[_typeTokenName]; - if (typeToken is null) - { - continue; - } - - AbiDescriptionType type = FastEnum.Parse(typeToken.Value(), true); - - if (type == AbiDescriptionType.Event) - { - AbiEventDescription abiEvent = new(); - serializer.Populate(definitionToken.CreateReader(), abiEvent); - existingValue.Add(abiEvent); - } - else if (type == AbiDescriptionType.Error) - { - AbiErrorDescription abiError = new(); - serializer.Populate(definitionToken.CreateReader(), abiError); - existingValue.Add(abiError); - } - else - { - AbiFunctionDescription abiFunction = new(); - serializer.Populate(definitionToken.CreateReader(), abiFunction); - existingValue.Add(abiFunction); - } - } - - return existingValue; - } - } -} diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParser.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParser.cs index c5b1026ae98..50b1b83f623 100644 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParser.cs +++ b/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParser.cs @@ -4,35 +4,34 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Abi; -using Nethermind.Serialization.Json; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; namespace Nethermind.Blockchain.Contracts.Json { - public class AbiDefinitionParser : IAbiDefinitionParser + public partial class AbiDefinitionParser : IAbiDefinitionParser { - private readonly JsonSerializer _serializer; private readonly IList _abiTypeFactories = new List(); public AbiDefinitionParser() { - _serializer = JsonSerializer.CreateDefault(GetJsonSerializerSettings()); } public AbiDefinition Parse(string json, string name = null) { - using var reader = new StringReader(json); - return Parse(reader, name); + AbiDefinition definition = JsonSerializer.Deserialize(json, SourceGenerationContext.Default.AbiDefinition); + definition.Name = name; + return definition; } public AbiDefinition Parse(Type type) { using var reader = LoadResource(type); - return Parse(reader, type.Name); + AbiDefinition definition = JsonSerializer.Deserialize(reader, SourceGenerationContext.Default.AbiDefinition); + definition.Name = type.Name; + return definition; } public void RegisterAbiTypeFactory(IAbiTypeFactory abiTypeFactory) @@ -42,47 +41,36 @@ public void RegisterAbiTypeFactory(IAbiTypeFactory abiTypeFactory) public string LoadContract(Type type) { - using var reader = LoadResource(type); + using var reader = new StreamReader(LoadResource(type)); return reader.ReadToEnd(); } public string Serialize(AbiDefinition contract) { - var builder = new StringBuilder(); - using var writer = new StringWriter(builder); - _serializer.Serialize(writer, contract); - return builder.ToString(); - } - - private AbiDefinition Parse(TextReader textReader, string name) - { - using var reader = new JsonTextReader(textReader); - var definition = _serializer.Deserialize(reader); - definition.Name = name; - return definition; + return JsonSerializer.Serialize(contract, SourceGenerationContext.Default.AbiDefinition); } - private static StreamReader LoadResource(Type type) + private static Stream LoadResource(Type type) { var jsonResource = type.FullName.Replace("+", ".") + ".json"; #if DEBUG var names = type.Assembly.GetManifestResourceNames(); #endif var stream = type.Assembly.GetManifestResourceStream(jsonResource) ?? throw new ArgumentException($"Resource for {jsonResource} not found."); - return new StreamReader(stream); + return stream; } - private JsonSerializerSettings GetJsonSerializerSettings() + [JsonSourceGenerationOptions(WriteIndented = true, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] + [JsonSerializable(typeof(AbiDefinition))] + [JsonSerializable(typeof(AbiFunctionDescription))] + [JsonSerializable(typeof(AbiParameter))] + [JsonSerializable(typeof(AbiEventParameter))] + [JsonSerializable(typeof(AbiBaseDescription))] + [JsonSerializable(typeof(AbiEventDescription))] + [JsonSerializable(typeof(AbiFunctionDescription))] + [JsonSerializable(typeof(AbiErrorDescription))] + internal partial class SourceGenerationContext : JsonSerializerContext { - var jsonSerializerSettings = new JsonSerializerSettings(); - jsonSerializerSettings.Converters.Add(new AbiDefinitionConverter()); - jsonSerializerSettings.Converters.Add(new AbiEventParameterConverter(_abiTypeFactories)); - jsonSerializerSettings.Converters.Add(new AbiParameterConverter(_abiTypeFactories)); - jsonSerializerSettings.Converters.Add(new StringEnumConverter() { NamingStrategy = new LowerCaseNamingStrategy() }); - jsonSerializerSettings.Converters.Add(new AbiTypeConverter()); - jsonSerializerSettings.Formatting = Formatting.Indented; - jsonSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - return jsonSerializerSettings; } } } diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParserExtensions.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParserExtensions.cs deleted file mode 100644 index 8b25ed1edba..00000000000 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiDefinitionParserExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Abi; - -namespace Nethermind.Blockchain.Contracts.Json -{ - public static class AbiDefinitionParserExtensions - { - public static void RegisterAbiTypeFactory(this IAbiDefinitionParser parser, AbiType abiType) => - parser.RegisterAbiTypeFactory(new AbiTypeFactory(abiType)); - } -} diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiParameterConverter.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiParameterConverter.cs deleted file mode 100644 index 563d489fb76..00000000000 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiParameterConverter.cs +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Nethermind.Abi; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Nethermind.Blockchain.Contracts.Json -{ - internal static partial class AbiParameterConverterStatics - { - internal const string TypeGroup = "T"; - internal const string TypeLengthGroup = "M"; - internal const string PrecisionGroup = "N"; - internal const string ArrayGroup = "A"; - internal const string LengthGroup = "L"; - - /// - /// Groups: - /// T - type or base type if array - /// M - length of type https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html#types - /// N - precision of type https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html#types - /// A - if matched type is array - /// L - if matched, denotes length of fixed length array - /// - internal static readonly Regex TypeExpression = TypeExpressionRegex(); - - - internal static readonly IDictionary> SimpleTypeFactories = new Dictionary>(StringComparer.InvariantCultureIgnoreCase) - { - {"int", (m, n) => new AbiInt(m ?? 256)}, - {"uint", (m, n) => new AbiUInt(m ?? 256)}, - {"address", (m, n) => AbiType.Address}, - {"bool", (m, n) => AbiType.Bool}, - {"fixed", (m, n) => new AbiFixed(m ?? 128, n ?? 18)}, - {"ufixed", (m, n) => new AbiUFixed(m ?? 128, n ?? 18)}, - {"bytes", (m, n) => m.HasValue ? new AbiBytes(m.Value) : AbiType.DynamicBytes}, - {"function", (m, n) => AbiType.Function}, - {"string", (m, n) => AbiType.String} - }; - - [GeneratedRegex("^(?u?int(?\\d{1,3})?|address|bool|u?fixed((?\\d{1,3})x(?\\d{1,2}))?|bytes(?\\d{1,3})?|function|string|tuple)(?\\[(?\\d+)?\\])?$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] - private static partial Regex TypeExpressionRegex(); - } - - public abstract class AbiParameterConverterBase : JsonConverter where T : AbiParameter, new() - { - private readonly IList _abiTypeFactories; - - protected AbiParameterConverterBase(IList abiTypeFactories) - { - _abiTypeFactories = abiTypeFactories; - } - - public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - - public override bool CanWrite { get; } = false; - - public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var token = JToken.Load(reader); - existingValue ??= new T(); - Populate(existingValue, token); - return existingValue; - } - - protected virtual void Populate(T item, JToken token) - { - item.Name = GetName(token); - item.Type = GetAbiType(token); - } - - private AbiType GetAbiType(JToken token) => - GetParameterType(token[TypePropertyName]!.Value(), token["components"]); - - private static string TypePropertyName => nameof(AbiParameter.Type).ToLowerInvariant(); - - private static string GetName(JToken token) => - token[NamePropertyName]!.Value(); - - private static string NamePropertyName => nameof(AbiParameter.Name).ToLowerInvariant(); - - private AbiType GetParameterType(string type, JToken? components) - { - var match = AbiParameterConverterStatics.TypeExpression.Match(type); - if (match.Success) - { - var baseType = new string(match.Groups[AbiParameterConverterStatics.TypeGroup].Value.TakeWhile(char.IsLetter).ToArray()); - var baseAbiType = GetBaseType(baseType, match, components); - return match.Groups[AbiParameterConverterStatics.ArrayGroup].Success - ? match.Groups[AbiParameterConverterStatics.LengthGroup].Success - ? (AbiType)new AbiFixedLengthArray(baseAbiType, int.Parse(match.Groups[AbiParameterConverterStatics.LengthGroup].Value)) - : new AbiArray(baseAbiType) - : baseAbiType; - } - else - { - throw new ArgumentException($"Invalid contract ABI json. Unknown type {type}."); - } - } - - private AbiType GetBaseType(string baseType, Match match, JToken? components) - { - string GetAbiTypeName() - { - string name = baseType; - if (components is not null) - { - IEnumerable innerTypes = components.SelectTokens($"$..{TypePropertyName}").Select(t => t.Value()); - name = $"({string.Join(",", innerTypes)})"; - } - - return name; - } - - string abiTypeName = GetAbiTypeName(); - - foreach (IAbiTypeFactory factory in _abiTypeFactories) - { - AbiType? abiType = factory.Create(abiTypeName); - if (abiType is not null) - { - return abiType; - } - } - - if (AbiParameterConverterStatics.SimpleTypeFactories.TryGetValue(baseType, out var simpleTypeFactory)) - { - int? m = match.Groups[AbiParameterConverterStatics.TypeLengthGroup].Success ? int.Parse(match.Groups[AbiParameterConverterStatics.TypeLengthGroup].Value) : (int?)null; - int? n = match.Groups[AbiParameterConverterStatics.PrecisionGroup].Success ? int.Parse(match.Groups[AbiParameterConverterStatics.PrecisionGroup].Value) : (int?)null; - return simpleTypeFactory(m, n); - } - else if (baseType == "tuple") - { - JEnumerable children = components!.Children(); - return new AbiTuple(children.Select(GetAbiType).ToArray(), children.Select(GetName).ToArray()); - } - else - { - throw new NotSupportedException($"Abi doesn't support type '{baseType}'"); - } - } - } - - public class AbiParameterConverter : AbiParameterConverterBase - { - public AbiParameterConverter(IList abiTypeFactories) : base(abiTypeFactories) - { - } - } - - public class AbiEventParameterConverter : AbiParameterConverterBase - { - public AbiEventParameterConverter(IList abiTypeFactories) : base(abiTypeFactories) - { - } - - protected override void Populate(AbiEventParameter item, JToken token) - { - base.Populate(item, token); - item.Indexed = token[nameof(AbiEventParameter.Indexed).ToLowerInvariant()]?.Value() ?? false; - } - } -} diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiTypeConverter.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiTypeConverter.cs deleted file mode 100644 index 66975f018c7..00000000000 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/AbiTypeConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Abi; -using Newtonsoft.Json; - -namespace Nethermind.Blockchain.Contracts.Json -{ - public class AbiTypeConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, AbiType value, JsonSerializer serializer) - { - writer.WriteValue(value.Name); - } - - public override AbiType ReadJson(JsonReader reader, Type objectType, AbiType existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - - public override bool CanRead { get; } = false; - } -} diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/IAbiTypeFactory.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/Json/IAbiTypeFactory.cs deleted file mode 100644 index 8ffcd68366a..00000000000 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/Json/IAbiTypeFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Abi; - -namespace Nethermind.Blockchain.Contracts.Json -{ - public interface IAbiTypeFactory - { - AbiType? Create(string abiTypeSignature); - } -} diff --git a/src/Nethermind/Nethermind.Blockchain/Data/FileLocalDataSource.cs b/src/Nethermind/Nethermind.Blockchain/Data/FileLocalDataSource.cs index 9802e85452d..5fe43c6624d 100644 --- a/src/Nethermind/Nethermind.Blockchain/Data/FileLocalDataSource.cs +++ b/src/Nethermind/Nethermind.Blockchain/Data/FileLocalDataSource.cs @@ -8,7 +8,8 @@ using System.Timers; using Nethermind.Logging; using Nethermind.Serialization.Json; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using Polly; namespace Nethermind.Blockchain.Data @@ -76,7 +77,7 @@ private async Task LoadFileAsync() { try { - await Policy.Handle() + await Policy.Handle() .Or() .WaitAndRetryAsync(3, CalcRetryIntervals, (exception, i) => ReportRetry(exception)) .ExecuteAsync(() => @@ -85,7 +86,7 @@ await Policy.Handle() return Task.CompletedTask; }); } - catch (JsonSerializationException e) + catch (JsonException e) { ReportJsonError(e); } @@ -99,12 +100,12 @@ private void LoadFile() { try { - Policy.Handle() + Policy.Handle() .Or() .WaitAndRetry(2, CalcRetryIntervals, (exception, i) => ReportRetry(exception)) .Execute(() => LoadFileCore()); } - catch (JsonSerializationException e) + catch (JsonException e) { ReportJsonError(e); } @@ -145,7 +146,7 @@ private void LoadFileCore() } } - private void ReportJsonError(JsonSerializationException e) + private void ReportJsonError(JsonException e) { if (_logger.IsError) _logger.Error($"Couldn't deserialize {typeof(T)} from {FilePath}. Will not retry any more.", e); } diff --git a/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs b/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs index a6c942f7585..a7c17ba14e2 100644 --- a/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs +++ b/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs @@ -2,10 +2,21 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers.Binary; +using System.Buffers.Text; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Json.Serialization; + +using System.Text.Json; using Nethermind.Core.Crypto; +using Nethermind.Serialization.Json; namespace Nethermind.Blockchain.Find { + using Nethermind.JsonRpc.Data; + + [JsonConverter(typeof(BlockParameterConverter))] public class BlockParameter : IEquatable { public static BlockParameter Earliest = new(BlockParameterType.Earliest); @@ -67,3 +78,234 @@ public override bool Equals(object? obj) public static bool operator !=(BlockParameter? left, BlockParameter? right) => !Equals(left, right); } } + +namespace Nethermind.JsonRpc.Data +{ + using Nethermind.Blockchain.Find; + using Nethermind.Core.Extensions; + + public class BlockParameterConverter : JsonConverter + { + public override bool HandleNull => true; + + public override void Write(Utf8JsonWriter writer, BlockParameter value, JsonSerializerOptions options) + { + if (value.Type == BlockParameterType.BlockNumber) + { + JsonSerializer.Serialize(writer, value.BlockNumber, options); + return; + } + + if (value.Type == BlockParameterType.BlockHash) + { + if (value.RequireCanonical) + { + writer.WriteStartObject(); + writer.WriteBoolean("requireCanonical"u8, true); + writer.WritePropertyName("blockHash"u8); + JsonSerializer.Serialize(writer, value.BlockHash, options); + writer.WriteEndObject(); + } + else + { + JsonSerializer.Serialize(writer, value.BlockHash, options); + } + + return; + } + + switch (value.Type) + { + case BlockParameterType.Earliest: + writer.WriteStringValue("earliest"u8); + break; + case BlockParameterType.Latest: + writer.WriteStringValue("latest"u8); + break; + case BlockParameterType.Pending: + writer.WriteStringValue("pending"u8); + break; + case BlockParameterType.Finalized: + writer.WriteStringValue("finalized"u8); + break; + case BlockParameterType.Safe: + writer.WriteStringValue("safe"u8); + break; + case BlockParameterType.BlockNumber: + throw new InvalidOperationException("block number should be handled separately"); + case BlockParameterType.BlockHash: + throw new InvalidOperationException("block hash should be handled separately"); + default: + throw new InvalidOperationException("unknown block parameter type"); + } + } + + [SkipLocalsInit] + public override BlockParameter? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + JsonTokenType tokenType = reader.TokenType; + if (tokenType == JsonTokenType.String && (reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length) > 66) + { + return JsonSerializer.Deserialize(reader.GetString(), options); + } + if (tokenType == JsonTokenType.StartObject) + { + bool requireCanonical = false; + Hash256 blockHash = null; + for (int i = 0; i < 2; i++) + { + reader.Read(); + if (reader.ValueTextEquals("requireCanonical"u8)) + { + reader.Read(); + requireCanonical = reader.GetBoolean(); + } + else if (reader.ValueTextEquals("blockHash"u8)) + { + blockHash = JsonSerializer.Deserialize(ref reader, options); + } + } + + BlockParameter parameter = new(blockHash, requireCanonical); + + if (!reader.Read() || reader.TokenType != JsonTokenType.EndObject) + { + ThrowInvalidFormatting(); + } + + return parameter; + } + + if (tokenType == JsonTokenType.Null) + { + return BlockParameter.Latest; + } + if (tokenType == JsonTokenType.Number) + { + return new BlockParameter(reader.GetInt64()); + } + + if (tokenType != JsonTokenType.String) + { + ThrowInvalidFormatting(); + } + + if (reader.ValueTextEquals(ReadOnlySpan.Empty) || reader.ValueTextEquals("latest"u8)) + { + return BlockParameter.Latest; + } + else if (reader.ValueTextEquals("earliest"u8)) + { + return BlockParameter.Earliest; + } + else if (reader.ValueTextEquals("pending"u8)) + { + return BlockParameter.Pending; + } + else if (reader.ValueTextEquals("finalized"u8)) + { + return BlockParameter.Finalized; + } + else if (reader.ValueTextEquals("safe"u8)) + { + return BlockParameter.Safe; + } + + Span span = stackalloc byte[66]; + int hexLength = reader.CopyString(span); + span = span[..hexLength]; + + long value = 0; + if (span.Length >= 2 && span.StartsWith("0x"u8)) + { + span = span[2..]; + if (span.Length == 64) + { + byte[] bytes = Bytes.FromUtf8HexString(span); + return new BlockParameter(new Hash256(bytes)); + } + + int oddMod = span.Length % 2; + int length = (span.Length >> 1) + oddMod; + + Span output = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1)); + + Bytes.FromUtf8HexString(span, output[(sizeof(long) - length)..]); + + if (BitConverter.IsLittleEndian) + { + value = BinaryPrimitives.ReverseEndianness(value); + } + + return new BlockParameter(value); + } + + if (Utf8Parser.TryParse(span, out value, out _)) + { + return new BlockParameter(value); + } + + // Lower case the span string + for (int i = 0; i < span.Length; i++) + { + int ch = span[i]; + if (ch >= 'A' && ch <= 'Z') + { + span[i] = (byte)(ch + 'a' - 'A'); + } + } + + if (span.SequenceEqual("latest"u8)) + { + return BlockParameter.Latest; + } + else if (span.SequenceEqual("earliest"u8)) + { + return BlockParameter.Earliest; + } + else if (span.SequenceEqual("pending"u8)) + { + return BlockParameter.Pending; + } + else if (span.SequenceEqual("finalized"u8)) + { + return BlockParameter.Finalized; + } + else if (span.SequenceEqual("safe"u8)) + { + return BlockParameter.Safe; + } + + ThrowInvalidFormatting(); + return null; + } + + private static void ThrowInvalidFormatting() + { + throw new InvalidOperationException("unknown block parameter type"); + } + + public static BlockParameter GetBlockParameter(string? value) + { + switch (value) + { + case null: + case { } empty when string.IsNullOrWhiteSpace(empty): + case { } latest when latest.Equals("latest", StringComparison.InvariantCultureIgnoreCase): + return BlockParameter.Latest; + case { } earliest when earliest.Equals("earliest", StringComparison.InvariantCultureIgnoreCase): + return BlockParameter.Earliest; + case { } pending when pending.Equals("pending", StringComparison.InvariantCultureIgnoreCase): + return BlockParameter.Pending; + case { } finalized when finalized.Equals("finalized", StringComparison.InvariantCultureIgnoreCase): + return BlockParameter.Finalized; + case { } safe when safe.Equals("safe", StringComparison.InvariantCultureIgnoreCase): + return BlockParameter.Safe; + case { Length: 66 } hash when hash.StartsWith("0x"): + return new BlockParameter(new Hash256(hash)); + default: + return new BlockParameter(LongConverter.FromString(value)); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Blockchain/FullPruning/PruningStatus.cs b/src/Nethermind/Nethermind.Blockchain/FullPruning/PruningStatus.cs index 2685df1a4ee..999cc34815a 100644 --- a/src/Nethermind/Nethermind.Blockchain/FullPruning/PruningStatus.cs +++ b/src/Nethermind/Nethermind.Blockchain/FullPruning/PruningStatus.cs @@ -1,15 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Serialization.Json; namespace Nethermind.Blockchain.FullPruning; /// /// Status of Full Pruning /// -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(LowerCaseJsonStringEnumConverter))] public enum PruningStatus { /// diff --git a/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs b/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs index 0e962cdc762..6ccaf5af62b 100644 --- a/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs +++ b/src/Nethermind/Nethermind.Blockchain/LogTraceDumper.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; + using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -13,14 +14,11 @@ using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; -using Newtonsoft.Json; namespace Nethermind.Blockchain; public static class BlockTraceDumper { - public static List Converters { get; } = new(); - public static void LogDiagnosticRlp( Block block, ILogger logger, @@ -56,15 +54,12 @@ public static void LogDiagnosticTrace( try { - IJsonSerializer serializer = new EthereumJsonSerializer(); - serializer.RegisterConverters(Converters); - if (blockTracer is BlockReceiptsTracer receiptsTracer) { fileName = $"receipts_{blockHash}.txt"; using FileStream diagnosticFile = GetFileStream(fileName); IReadOnlyList receipts = receiptsTracer.TxReceipts; - serializer.Serialize(diagnosticFile, receipts, true); + EthereumJsonSerializer.SerializeToStream(diagnosticFile, receipts, true); if (logger.IsInfo) logger.Info($"Created a Receipts trace of invalid block {blockHash} in file {diagnosticFile.Name}"); } @@ -74,7 +69,7 @@ public static void LogDiagnosticTrace( fileName = $"gethStyle_{blockHash}.txt"; using FileStream diagnosticFile = GetFileStream(fileName); IReadOnlyCollection trace = gethTracer.BuildResult(); - serializer.Serialize(diagnosticFile, trace, true); + EthereumJsonSerializer.SerializeToStream(diagnosticFile, trace, true); if (logger.IsInfo) logger.Info($"Created a Geth-style trace of invalid block {blockHash} in file {diagnosticFile.Name}"); } @@ -84,7 +79,7 @@ public static void LogDiagnosticTrace( fileName = $"parityStyle_{blockHash}.txt"; using FileStream diagnosticFile = GetFileStream(fileName); IReadOnlyCollection trace = parityTracer.BuildResult(); - serializer.Serialize(diagnosticFile, trace, true); + EthereumJsonSerializer.SerializeToStream(diagnosticFile, trace, true); if (logger.IsInfo) logger.Info($"Created a Parity-style trace of invalid block {blockHash} in file {diagnosticFile.Name}"); } diff --git a/src/Nethermind/Nethermind.Cli/Program.cs b/src/Nethermind/Nethermind.Cli/Program.cs index 30dcd0476cd..c5e0a2ec1c5 100644 --- a/src/Nethermind/Nethermind.Cli/Program.cs +++ b/src/Nethermind/Nethermind.Cli/Program.cs @@ -46,7 +46,6 @@ public static void Main(string[] args) INodeManager nodeManager = new NodeManager(engine, Serializer, cliConsole, logManager); var moduleLoader = new CliModuleLoader(engine, nodeManager, cliConsole); - RegisterConverters(); engine.JintEngine.SetValue("serialize", new Action(v => { string text = Serializer.Serialize(v.ToObject(), true); @@ -170,17 +169,6 @@ private static void WriteResult(ICliConsole cliConsole, JsValue result) } } - private static void RegisterConverters() - { - Serializer.RegisterConverter(new ParityTxTraceFromReplayConverter()); - Serializer.RegisterConverter(new ParityAccountStateChangeConverter()); - Serializer.RegisterConverter(new ParityTraceActionConverter()); - Serializer.RegisterConverter(new ParityTraceResultConverter()); - Serializer.RegisterConverter(new ParityVmOperationTraceConverter()); - Serializer.RegisterConverter(new ParityVmTraceConverter()); - Serializer.RegisterConverter(new TransactionForRpcWithTraceTypesConverter()); - } - private static ColorScheme? MapColorScheme(string colorSchemeOption) { return _availableColorSchemes.TryGetValue(colorSchemeOption.ToLower(), out var scheme) ? scheme : null; diff --git a/src/Nethermind/Nethermind.Config.Test/Nethermind.Config.Test.csproj b/src/Nethermind/Nethermind.Config.Test/Nethermind.Config.Test.csproj index 622f293849a..cc10ee7e40d 100644 --- a/src/Nethermind/Nethermind.Config.Test/Nethermind.Config.Test.csproj +++ b/src/Nethermind/Nethermind.Config.Test/Nethermind.Config.Test.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs b/src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs index 39c91339c65..91ebde3f3a5 100644 --- a/src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs +++ b/src/Nethermind/Nethermind.Config/ConfigSourceHelper.cs @@ -6,7 +6,8 @@ using System.Collections.Generic; using System.Linq; using Nethermind.Int256; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Config { @@ -25,7 +26,7 @@ public static object ParseValue(Type valueType, string valueString, string categ //In case of collection of objects (more complex config models) we parse entire collection if (itemType.IsClass && typeof(IConfigModel).IsAssignableFrom(itemType)) { - var objCollection = JsonConvert.DeserializeObject(valueString, valueType); + var objCollection = JsonSerializer.Deserialize(valueString, valueType); value = objCollection; } else @@ -68,7 +69,7 @@ public static object ParseValue(Type valueType, string valueString, string categ } catch (InvalidCastException) { - value = JsonConvert.DeserializeObject(valueString, valueType); + value = JsonSerializer.Deserialize(valueString, valueType); } } diff --git a/src/Nethermind/Nethermind.Config/JsonConfigSource.cs b/src/Nethermind/Nethermind.Config/JsonConfigSource.cs index 4c2f2be8177..9e9f49a18b3 100644 --- a/src/Nethermind/Nethermind.Config/JsonConfigSource.cs +++ b/src/Nethermind/Nethermind.Config/JsonConfigSource.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Text; -using Newtonsoft.Json.Linq; +using System.Text.Json; using System.Linq; namespace Nethermind.Config @@ -21,13 +21,13 @@ private void ApplyJsonConfig(string jsonContent) { try { - var json = (JObject)JToken.Parse(jsonContent); - foreach (var moduleEntry in json) + using var json = JsonDocument.Parse(jsonContent); + foreach (var moduleEntry in json.RootElement.EnumerateObject()) { - LoadModule(moduleEntry.Key, (JObject)moduleEntry.Value); + LoadModule(moduleEntry.Name, moduleEntry.Value); } } - catch (Newtonsoft.Json.JsonReaderException e) + catch (JsonException e) { throw new System.Configuration.ConfigurationErrorsException($"Config is not correctly formed JSON. See inner exception for details.", e); } @@ -68,20 +68,36 @@ private void LoadJsonConfig(string configFilePath) ApplyJsonConfig(File.ReadAllText(configFilePath)); } - private void LoadModule(string moduleName, JObject value) + private void LoadModule(string moduleName, JsonElement configItems) { - var configItems = value; var itemsDict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - foreach (var configItem in configItems) + foreach (var configItem in configItems.EnumerateObject()) { - if (!itemsDict.ContainsKey(configItem.Key)) + var key = configItem.Name; + if (!itemsDict.ContainsKey(key)) { - itemsDict[configItem.Key] = configItem.Value.ToString(); + var value = configItem.Value; + if (value.ValueKind == JsonValueKind.Number) + { + itemsDict[key] = value.GetInt64().ToString(); + } + else if (value.ValueKind == JsonValueKind.True) + { + itemsDict[key] = "true"; + } + else if (value.ValueKind == JsonValueKind.False) + { + itemsDict[key] = "false"; + } + else + { + itemsDict[key] = configItem.Value.ToString(); + } } else { - throw new Exception($"Duplicated config value: {configItem.Key}, module: {moduleName}"); + throw new System.Configuration.ConfigurationErrorsException($"Duplicated config value: {key}, module: {moduleName}"); } } diff --git a/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj b/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj index 3964657e20d..bb0b711c09b 100644 --- a/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj +++ b/src/Nethermind/Nethermind.Config/Nethermind.Config.csproj @@ -1,7 +1,6 @@ - diff --git a/src/Nethermind/Nethermind.Consensus.Test/Nethermind.Consensus.Test.csproj b/src/Nethermind/Nethermind.Consensus.Test/Nethermind.Consensus.Test.csproj index bd001e0899a..6a42f854186 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/Nethermind.Consensus.Test.csproj +++ b/src/Nethermind/Nethermind.Consensus.Test/Nethermind.Consensus.Test.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs b/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs index 5f236cde3a3..b2c59314889 100644 --- a/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; + using FluentAssertions; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -11,7 +13,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Int256; -using Newtonsoft.Json; + using NSubstitute; using NUnit.Framework; @@ -152,7 +154,7 @@ public void Eip_1559_CalculateBaseFee_shared_test_cases((BaseFeeTestCases Info, private static IEnumerable<(BaseFeeTestCases, string)> Eip1559BaseFeeTestSource() { string testCases = File.ReadAllText("TestFiles/BaseFeeTestCases.json"); - BaseFeeTestCases[] deserializedTestCases = JsonConvert.DeserializeObject(testCases) ?? Array.Empty(); + BaseFeeTestCases[] deserializedTestCases = JsonSerializer.Deserialize(testCases) ?? Array.Empty(); for (int i = 0; i < deserializedTestCases.Length; ++i) { diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs index 35a99a996ae..a653619f397 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs @@ -110,13 +110,8 @@ public static Hash256 KeccakFromNumber(int i) public static T CloneObject(T value) { - using var stream = new MemoryStream(); - - JsonSerializer.Serialize(stream, value); - - stream.Position = 0; - - return JsonSerializer.Deserialize(stream)!; + string data = Newtonsoft.Json.JsonConvert.SerializeObject(value); + return Newtonsoft.Json.JsonConvert.DeserializeObject(data)!; } public static Address GetRandomAddress(Random? random = null) diff --git a/src/Nethermind/Nethermind.Core.Test/Json/BigIntegerConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/BigIntegerConverterTests.cs index 481593294f9..02f86072dd6 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/BigIntegerConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/BigIntegerConverterTests.cs @@ -4,8 +4,9 @@ using System; using System.IO; using System.Numerics; +using System.Text.Json; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -13,64 +14,27 @@ namespace Nethermind.Core.Test.Json [TestFixture] public class BigIntegerConverterTests : ConverterTestBase { - [TestCase(NumberConversion.Hex)] - [TestCase(NumberConversion.Raw)] - [TestCase(NumberConversion.Decimal)] - public void Test_roundtrip(NumberConversion numberConversion) + static BigIntegerConverter converter = new BigIntegerConverter(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + + public void Test_roundtrip() { - BigIntegerConverter converter = new(numberConversion); TestConverter(int.MaxValue, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(BigInteger.One, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(BigInteger.Zero, (integer, bigInteger) => integer.Equals(bigInteger), converter); } - [Test] - public void Regression_0xa00000() - { - BigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0xa00000")); - reader.ReadAsString(); - BigInteger result = converter.ReadJson(reader, typeof(BigInteger), BigInteger.Zero, false, JsonSerializer.CreateDefault()); - Assert.That(result, Is.EqualTo(BigInteger.Parse("10485760"))); - } - - [TestCase((NumberConversion)99)] - public void Unknown_not_supported(NumberConversion notSupportedConversion) - { - BigIntegerConverter converter = new(notSupportedConversion); - Assert.Throws( - () => TestConverter(int.MaxValue, (a, b) => a.Equals(b), converter)); - Assert.Throws( - () => TestConverter(1L, (a, b) => a.Equals(b), converter)); - } - - [Test] - public void Can_read_0x0() - { - BigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0")); - reader.ReadAsString(); - BigInteger result = converter.ReadJson(reader, typeof(BigInteger), BigInteger.Zero, false, JsonSerializer.CreateDefault()); - Assert.That(result, Is.EqualTo(BigInteger.Parse("0"))); - } - [Test] public void Can_read_0() { - BigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0")); - reader.ReadAsString(); - BigInteger result = converter.ReadJson(reader, typeof(BigInteger), BigInteger.Zero, false, JsonSerializer.CreateDefault()); + BigInteger result = JsonSerializer.Deserialize("0", options); Assert.That(result, Is.EqualTo(BigInteger.Parse("0"))); } [Test] public void Can_read_1() { - BigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("1")); - reader.ReadAsString(); - BigInteger result = converter.ReadJson(reader, typeof(BigInteger), BigInteger.Zero, false, JsonSerializer.CreateDefault()); + BigInteger result = JsonSerializer.Deserialize("1", options); Assert.That(result, Is.EqualTo(BigInteger.Parse("1"))); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs index d58f653f763..d3d334aae16 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Nethermind.Core.Extensions; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -25,13 +25,9 @@ public void Test_roundtrip(byte[]? bytes) [Test] public void Direct_null() { - ByteArrayConverter converter = new(); - StringBuilder sb = new(); - JsonSerializer serializer = new(); - serializer.Converters.Add(converter); - converter.WriteJson( - new JsonTextWriter(new StringWriter(sb)), null, serializer); - sb.ToString().Should().Be("null"); + IJsonSerializer serializer = new EthereumJsonSerializer(); + var result = serializer.Serialize(null); + result.Should().Be("null"); } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs b/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs index f60dbff40e1..03329a6e88e 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/ConverterTestBase.cs @@ -4,7 +4,8 @@ using System; using System.IO; using System.Text; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -13,14 +14,17 @@ public class ConverterTestBase { protected void TestConverter(T? item, Func equalityComparer, JsonConverter converter) { - JsonSerializer serializer = new(); - serializer.Converters.Add(converter); - StringBuilder builder = new(); - StringWriter writer = new(builder); - serializer.Serialize(writer, item); - string result = builder.ToString(); - JsonReader reader = new JsonTextReader(new StringReader(result)); - T? deserialized = serializer.Deserialize(reader); + var options = new JsonSerializerOptions + { + Converters = + { + converter + } + }; + + string result = JsonSerializer.Serialize(item, options); + + T? deserialized = JsonSerializer.Deserialize(result, options); #pragma warning disable CS8604 Assert.True(equalityComparer(item, deserialized)); diff --git a/src/Nethermind/Nethermind.Core.Test/Json/KeccakConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs similarity index 53% rename from src/Nethermind/Nethermind.Core.Test/Json/KeccakConverterTests.cs rename to src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs index 304f7d960dc..336070222c6 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/KeccakConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs @@ -2,23 +2,25 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.IO; +using System.Text.Json; + using Nethermind.Core.Crypto; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Core.Test.Json { [TestFixture] - public class KeccakConverterTests + public class Hash256ConverterTests { + static Hash256Converter converter = new(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + [Test] public void Can_read_null() { - KeccakConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader(string.Empty)); - reader.ReadAsString(); - Hash256 result = converter.ReadJson(reader, typeof(Hash256), null, false, JsonSerializer.CreateDefault()); + Hash256? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs index 72570401b6e..b7ecf8cc1b7 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs @@ -3,8 +3,10 @@ using System; using System.IO; +using System.Text.Json; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -12,84 +14,56 @@ namespace Nethermind.Core.Test.Json [TestFixture] public class LongConverterTests : ConverterTestBase { - [TestCase(NumberConversion.Hex)] - [TestCase(NumberConversion.Decimal)] - public void Test_roundtrip(NumberConversion numberConversion) + static LongConverter converter = new(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + + public void Test_roundtrip() { - LongConverter converter = new(numberConversion); TestConverter(int.MaxValue, (a, b) => a.Equals(b), converter); TestConverter(1L, (a, b) => a.Equals(b), converter); TestConverter(0L, (a, b) => a.Equals(b), converter); } - [TestCase((NumberConversion)99)] - public void Unknown_not_supported(NumberConversion notSupportedConversion) - { - LongConverter converter = new(notSupportedConversion); - Assert.Throws( - () => TestConverter(int.MaxValue, (a, b) => a.Equals(b), converter)); - Assert.Throws( - () => TestConverter(1L, (a, b) => a.Equals(b), converter)); - } - [Test] public void Regression_0xa00000() { - LongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0xa00000")); - reader.ReadAsString(); - long result = converter.ReadJson(reader, typeof(long), 0, false, JsonSerializer.CreateDefault()); + long result = JsonSerializer.Deserialize("\"0xa00000\"", options); Assert.That(result, Is.EqualTo(10485760)); } [Test] public void Can_read_0x0() { - LongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0")); - reader.ReadAsString(); - long result = converter.ReadJson(reader, typeof(long), 0L, false, JsonSerializer.CreateDefault()); + long result = JsonSerializer.Deserialize("\"0x0\"", options); Assert.That(result, Is.EqualTo(long.Parse("0"))); } [Test] public void Can_read_0x000() { - LongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0000")); - reader.ReadAsString(); - long result = converter.ReadJson(reader, typeof(long), 0L, false, JsonSerializer.CreateDefault()); + long result = JsonSerializer.Deserialize("\"0x0000\"", options); Assert.That(result, Is.EqualTo(long.Parse("0"))); } [Test] public void Can_read_0() { - LongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0")); - reader.ReadAsString(); - long result = converter.ReadJson(reader, typeof(long), 0L, false, JsonSerializer.CreateDefault()); + long result = JsonSerializer.Deserialize("0", options); Assert.That(result, Is.EqualTo(long.Parse("0"))); } [Test] public void Can_read_1() { - LongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("1")); - reader.ReadAsString(); - long result = converter.ReadJson(reader, typeof(long), 0L, false, JsonSerializer.CreateDefault()); + long result = JsonSerializer.Deserialize("1", options); Assert.That(result, Is.EqualTo(long.Parse("1"))); } [Test] public void Throws_on_null() { - LongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("null")); - reader.ReadAsString(); Assert.Throws( - () => converter.ReadJson(reader, typeof(long), 0L, false, JsonSerializer.CreateDefault())); + () => JsonSerializer.Deserialize("null", options)); } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableBigIntegerConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableBigIntegerConverterTests.cs index 424c47fc234..dd29a2f7c38 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableBigIntegerConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableBigIntegerConverterTests.cs @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.IO; using System.Numerics; +using System.Text.Json; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -12,65 +14,34 @@ namespace Nethermind.Core.Test.Json [TestFixture] public class NullableBigIntegerConverterTests : ConverterTestBase { - [TestCase(NumberConversion.Hex)] - [TestCase(NumberConversion.Raw)] - [TestCase(NumberConversion.Decimal)] - public void Test_roundtrip(NumberConversion numberConversion) + static NullableBigIntegerConverter converter = new NullableBigIntegerConverter(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + public void Test_roundtrip() { - NullableBigIntegerConverter converter = new(numberConversion); TestConverter(null, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(int.MaxValue, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(BigInteger.One, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(BigInteger.Zero, (integer, bigInteger) => integer.Equals(bigInteger), converter); } - [Test] - public void Regression_0xa00000() - { - BigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0xa00000")); - reader.ReadAsString(); - BigInteger result = converter.ReadJson(reader, typeof(BigInteger), BigInteger.Zero, false, JsonSerializer.CreateDefault()); - Assert.That(result, Is.EqualTo(BigInteger.Parse("10485760"))); - } - - [Test] - public void Can_read_0x0() - { - NullableBigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0")); - reader.ReadAsString(); - BigInteger? result = converter.ReadJson(reader, typeof(BigInteger?), BigInteger.Zero, false, JsonSerializer.CreateDefault()); - Assert.That(result, Is.EqualTo(BigInteger.Parse("0"))); - } - [Test] public void Can_read_0() { - NullableBigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0")); - reader.ReadAsString(); - BigInteger? result = converter.ReadJson(reader, typeof(BigInteger?), BigInteger.Zero, false, JsonSerializer.CreateDefault()); + BigInteger? result = JsonSerializer.Deserialize("0", options); Assert.That(result, Is.EqualTo(BigInteger.Parse("0"))); } [Test] public void Can_read_1() { - NullableBigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("1")); - reader.ReadAsString(); - BigInteger? result = converter.ReadJson(reader, typeof(BigInteger?), BigInteger.Zero, false, JsonSerializer.CreateDefault()); + BigInteger? result = JsonSerializer.Deserialize("1", options); Assert.That(result, Is.EqualTo(BigInteger.Parse("1"))); } [Test] public void Can_read_null() { - NullableBigIntegerConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("null")); - reader.ReadAsString(); - BigInteger? result = converter.ReadJson(reader, typeof(BigInteger?), BigInteger.Zero, false, JsonSerializer.CreateDefault()); + BigInteger? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableLongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableLongConverterTests.cs index 93a275ed57e..70917d3f953 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableLongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableLongConverterTests.cs @@ -3,8 +3,10 @@ using System; using System.IO; +using System.Text.Json; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -12,93 +14,62 @@ namespace Nethermind.Core.Test.Json [TestFixture] public class NullableLongConverterTests : ConverterTestBase { - [TestCase(NumberConversion.Hex)] - [TestCase(NumberConversion.Decimal)] - public void Test_roundtrip(NumberConversion numberConversion) + static NullableLongConverter converter = new(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + + public void Test_roundtrip() { - NullableLongConverter converter = new(numberConversion); TestConverter(int.MaxValue, (a, b) => a.Equals(b), converter); TestConverter(1L, (a, b) => a.Equals(b), converter); TestConverter(0L, (a, b) => a.Equals(b), converter); } - [TestCase((NumberConversion)99)] - public void Unknown_not_supported(NumberConversion notSupportedConversion) - { - NullableLongConverter converter = new(notSupportedConversion); - Assert.Throws( - () => TestConverter(int.MaxValue, (a, b) => a.Equals(b), converter)); - Assert.Throws( - () => TestConverter(1L, (a, b) => a.Equals(b), converter)); - } - [Test] public void Regression_0xa00000() { - NullableLongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0xa00000")); - reader.ReadAsString(); - long? result = converter.ReadJson(reader, typeof(long?), 0, false, JsonSerializer.CreateDefault()); + long? result = JsonSerializer.Deserialize("\"0xa00000\"", options); Assert.That(result, Is.EqualTo(10485760)); } [Test] public void Can_read_0x0() { - NullableLongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0")); - reader.ReadAsString(); - long? result = converter.ReadJson(reader, typeof(long?), 0L, false, JsonSerializer.CreateDefault()); + long? result = JsonSerializer.Deserialize("\"0x0\"", options); Assert.That(result, Is.EqualTo(long.Parse("0"))); } [Test] public void Can_read_0x000() { - NullableLongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0000")); - reader.ReadAsString(); - long? result = converter.ReadJson(reader, typeof(long?), 0L, false, JsonSerializer.CreateDefault()); + long? result = JsonSerializer.Deserialize("\"0x0000\"", options); Assert.That(result, Is.EqualTo(long.Parse("0"))); } [Test] public void Can_read_0() { - NullableLongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0")); - reader.ReadAsString(); - long? result = converter.ReadJson(reader, typeof(long?), 0L, false, JsonSerializer.CreateDefault()); + long? result = JsonSerializer.Deserialize("0", options); Assert.That(result, Is.EqualTo(long.Parse("0"))); } [Test] public void Can_read_1() { - NullableLongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("1")); - reader.ReadAsString(); - long? result = converter.ReadJson(reader, typeof(long?), 0L, false, JsonSerializer.CreateDefault()); + long? result = JsonSerializer.Deserialize("1", options); Assert.That(result, Is.EqualTo(long.Parse("1"))); } [Test] public void Can_read_null() { - NullableLongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("null")); - reader.ReadAsString(); - long? result = converter.ReadJson(reader, typeof(long?), 0L, false, JsonSerializer.CreateDefault()); + long? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } [Test] public void Can_read_negative_numbers() { - NullableLongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("-1")); - reader.ReadAsString(); - long? result = converter.ReadJson(reader, typeof(long?), 0L, false, JsonSerializer.CreateDefault()); + long? result = JsonSerializer.Deserialize("-1", options); Assert.That(result, Is.EqualTo(long.Parse("-1"))); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableUInt256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableUInt256ConverterTests.cs index a1ec5edcdf3..b15ef87ae15 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableUInt256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableUInt256ConverterTests.cs @@ -1,10 +1,11 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.IO; +using System.Text.Json; + using Nethermind.Int256; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -12,11 +13,13 @@ namespace Nethermind.Core.Test.Json [TestFixture] public class NullableUInt256ConverterTests : ConverterTestBase { + static NullableUInt256Converter converter = new(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + [TestCase(NumberConversion.Hex)] [TestCase(NumberConversion.Decimal)] public void Test_roundtrip(NumberConversion numberConversion) { - NullableUInt256Converter converter = new(numberConversion); TestConverter(null, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(int.MaxValue, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(UInt256.One, (integer, bigInteger) => integer.Equals(bigInteger), converter); @@ -26,40 +29,28 @@ public void Test_roundtrip(NumberConversion numberConversion) [Test] public void Regression_0xa00000() { - NullableUInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0xa00000")); - reader.ReadAsString(); - UInt256? result = converter.ReadJson(reader, typeof(UInt256?), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256? result = JsonSerializer.Deserialize("\"0xa00000\"", options); Assert.That(result, Is.EqualTo(UInt256.Parse("10485760"))); } [Test] public void Can_read_0x0() { - NullableUInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0")); - reader.ReadAsString(); - UInt256? result = converter.ReadJson(reader, typeof(UInt256?), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256? result = JsonSerializer.Deserialize("\"0x0\"", options); Assert.That(result, Is.EqualTo(UInt256.Parse("0"))); } [Test] public void Can_read_0() { - NullableUInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0")); - reader.ReadAsString(); - UInt256? result = converter.ReadJson(reader, typeof(UInt256?), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256? result = JsonSerializer.Deserialize("0", options); Assert.That(result, Is.EqualTo(UInt256.Parse("0"))); } [Test] public void Can_read_1() { - NullableUInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("1")); - reader.ReadAsString(); - UInt256? result = converter.ReadJson(reader, typeof(UInt256?), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256? result = JsonSerializer.Deserialize("1", options); Assert.That(result, Is.EqualTo(UInt256.Parse("1"))); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs index 70c3c49dd96..373cd9ef080 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.IO; +using System.Text.Json; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -12,94 +13,65 @@ namespace Nethermind.Core.Test.Json [TestFixture] public class NullableULongConverterTests : ConverterTestBase { + static NullableULongConverter converter = new(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + [TestCase(NumberConversion.Hex)] [TestCase(NumberConversion.Decimal)] public void Test_roundtrip(NumberConversion numberConversion) { - NullableULongConverter converter = new(numberConversion); TestConverter(int.MaxValue, (a, b) => a.Equals(b), converter); TestConverter(1L, (a, b) => a.Equals(b), converter); TestConverter(0L, (a, b) => a.Equals(b), converter); } - [TestCase((NumberConversion)99)] - public void Unknown_not_supported(NumberConversion notSupportedConversion) - { - NullableULongConverter converter = new(notSupportedConversion); - Assert.Throws( - () => TestConverter(int.MaxValue, (a, b) => a.Equals(b), converter)); - Assert.Throws( - () => TestConverter(1L, (a, b) => a.Equals(b), converter)); - } - [Test] public void Regression_0xa00000() { - NullableULongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0xa00000")); - reader.ReadAsString(); - ulong? result = converter.ReadJson(reader, typeof(ulong?), 0, false, JsonSerializer.CreateDefault()); + ulong? result = JsonSerializer.Deserialize("\"0xa00000\"", options); Assert.That(result, Is.EqualTo(10485760)); } [Test] public void Can_read_0x0() { - NullableULongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0")); - reader.ReadAsString(); - ulong? result = converter.ReadJson(reader, typeof(ulong?), 0L, false, JsonSerializer.CreateDefault()); + ulong? result = JsonSerializer.Deserialize("\"0x0\"", options); Assert.That(result, Is.EqualTo(ulong.Parse("0"))); } [Test] public void Can_read_0x000() { - NullableULongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0000")); - reader.ReadAsString(); - ulong? result = converter.ReadJson(reader, typeof(ulong?), 0L, false, JsonSerializer.CreateDefault()); + ulong? result = JsonSerializer.Deserialize("\"0x000\"", options); Assert.That(result, Is.EqualTo(ulong.Parse("0"))); } [Test] public void Can_read_0() { - NullableULongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0")); - reader.ReadAsString(); - ulong? result = converter.ReadJson(reader, typeof(ulong?), 0L, false, JsonSerializer.CreateDefault()); + ulong? result = JsonSerializer.Deserialize("0", options); Assert.That(result, Is.EqualTo(ulong.Parse("0"))); } [Test] public void Can_read_1() { - NullableULongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("1")); - reader.ReadAsString(); - ulong? result = converter.ReadJson(reader, typeof(ulong?), 0L, false, JsonSerializer.CreateDefault()); + ulong? result = JsonSerializer.Deserialize("1", options); Assert.That(result, Is.EqualTo(ulong.Parse("1"))); } [Test] public void Can_read_null() { - NullableULongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("null")); - reader.ReadAsString(); - ulong? result = converter.ReadJson(reader, typeof(ulong?), 0L, false, JsonSerializer.CreateDefault()); + ulong? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } [Test] public void Throws_on_negative_numbers() { - NullableULongConverter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("-1")); - reader.ReadAsString(); - Assert.Throws( - () => converter.ReadJson(reader, typeof(ulong?), 0L, false, JsonSerializer.CreateDefault())); + Assert.Throws( + () => JsonSerializer.Deserialize("-1", options)); } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs index 40e1582dfde..c5686aeec28 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs @@ -6,7 +6,8 @@ using System.IO; using Nethermind.Int256; using Nethermind.Serialization.Json; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using NUnit.Framework; namespace Nethermind.Core.Test.Json @@ -14,12 +15,14 @@ namespace Nethermind.Core.Test.Json [TestFixture] public class UInt256ConverterTests : ConverterTestBase { + static UInt256Converter converter = new(); + static JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + [TestCase(NumberConversion.Hex)] [TestCase(NumberConversion.Decimal)] [TestCase(NumberConversion.Raw)] public void Test_roundtrip(NumberConversion numberConversion) { - UInt256Converter converter = new(numberConversion); TestConverter(int.MaxValue, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(UInt256.One, (integer, bigInteger) => integer.Equals(bigInteger), converter); TestConverter(UInt256.Zero, (integer, bigInteger) => integer.Equals(bigInteger), converter); @@ -28,91 +31,74 @@ public void Test_roundtrip(NumberConversion numberConversion) [TestCase((NumberConversion)99)] public void Undefined_not_supported(NumberConversion notSupportedConversion) { - UInt256Converter converter = new(notSupportedConversion); + ForcedNumberConversion.ForcedConversion.Value = notSupportedConversion; + + UInt256Converter converter = new(); Assert.Throws( () => TestConverter(int.MaxValue, (integer, bigInteger) => integer.Equals(bigInteger), converter)); Assert.Throws( () => TestConverter(UInt256.One, (integer, bigInteger) => integer.Equals(bigInteger), converter)); + + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; } [Test] public void Raw_works_with_zero_and_this_is_ok() { - UInt256Converter converter = new(NumberConversion.Raw); + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Raw; + UInt256Converter converter = new(); TestConverter(0, (integer, bigInteger) => integer.Equals(bigInteger), converter); - converter = new UInt256Converter((NumberConversion)99); - TestConverter(0, (integer, bigInteger) => integer.Equals(bigInteger), converter); + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; } [Test] public void Regression_0xa00000() { - UInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0xa00000")); - reader.ReadAsString(); - UInt256 result = converter.ReadJson(reader, typeof(UInt256), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256 result = JsonSerializer.Deserialize("\"0xa00000\"", options); Assert.That(result, Is.EqualTo(UInt256.Parse("10485760"))); } [Test] public void Can_read_0x0() { - UInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0")); - reader.ReadAsString(); - UInt256 result = converter.ReadJson(reader, typeof(UInt256), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256 result = JsonSerializer.Deserialize("\"0x0\"", options); Assert.That(result, Is.EqualTo(UInt256.Parse("0"))); } [Test] public void Can_read_0x000() { - UInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0x0000")); - reader.ReadAsString(); - UInt256 result = converter.ReadJson(reader, typeof(UInt256), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256 result = JsonSerializer.Deserialize("\"0x0000\"", options); Assert.That(result, Is.EqualTo(UInt256.Parse("0"))); } [Test] public void Can_read_0() { - UInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("0")); - reader.ReadAsString(); - UInt256 result = converter.ReadJson(reader, typeof(UInt256), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256 result = JsonSerializer.Deserialize("0", options); Assert.That(result, Is.EqualTo(UInt256.Parse("0"))); } [Test] public void Can_read_1() { - UInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("1")); - reader.ReadAsString(); - UInt256 result = converter.ReadJson(reader, typeof(UInt256), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256 result = JsonSerializer.Deserialize("1", options); Assert.That(result, Is.EqualTo(UInt256.Parse("1"))); } [Test] public void Can_read_unmarked_hex() { - UInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("\"de\"")); - reader.ReadAsString(); - UInt256 result = converter.ReadJson(reader, typeof(UInt256), UInt256.Zero, false, JsonSerializer.CreateDefault()); + UInt256 result = JsonSerializer.Deserialize("\"de\"", options); Assert.That(result, Is.EqualTo(UInt256.Parse("de", NumberStyles.HexNumber))); } [Test] public void Throws_on_null() { - UInt256Converter converter = new(); - JsonReader reader = new JsonTextReader(new StringReader("null")); - reader.ReadAsString(); Assert.Throws( - () => converter.ReadJson(reader, typeof(UInt256), UInt256.Zero, false, JsonSerializer.CreateDefault())); + () => JsonSerializer.Deserialize("null", options)); } } } diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index a9886537862..d92d67e1193 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -6,12 +6,16 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text.Json.Serialization; + using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; +using Nethermind.Serialization.Json; namespace Nethermind.Core { + [JsonConverter(typeof(AddressConverter))] [TypeConverter(typeof(AddressTypeConverter))] public class Address : IEquatable
, IComparable
{ diff --git a/src/Nethermind/Nethermind.Core/AddressConverter.cs b/src/Nethermind/Nethermind.Core/AddressConverter.cs new file mode 100644 index 00000000000..808e383218f --- /dev/null +++ b/src/Nethermind/Nethermind.Core/AddressConverter.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Nethermind.Core; + +namespace Nethermind.Serialization.Json; + +public class AddressConverter : JsonConverter
+{ + public override Address? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + byte[]? bytes = ByteArrayConverter.Convert(ref reader); + return bytes is null ? null : new Address(bytes); + } + + public override void Write( + Utf8JsonWriter writer, + Address address, + JsonSerializerOptions options) + { + ByteArrayConverter.Convert(writer, address.Bytes, skipLeadingZeros: false); + } +} diff --git a/src/Nethermind/Nethermind.Core/Bloom.cs b/src/Nethermind/Nethermind.Core/Bloom.cs index ef661fae4f6..ca7fa2c10ea 100644 --- a/src/Nethermind/Nethermind.Core/Bloom.cs +++ b/src/Nethermind/Nethermind.Core/Bloom.cs @@ -2,11 +2,15 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json.Serialization; + using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Serialization.Json; namespace Nethermind.Core { + [JsonConverter(typeof(BloomConverter))] public class Bloom : IEquatable { public static readonly Bloom Empty = new(); diff --git a/src/Nethermind/Nethermind.Core/BloomConverter.cs b/src/Nethermind/Nethermind.Core/BloomConverter.cs new file mode 100644 index 00000000000..ee8a6158442 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/BloomConverter.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using System.Text.Json.Serialization; +using System.Text.Json; + +namespace Nethermind.Serialization.Json; + +public class BloomConverter : JsonConverter +{ + public override Bloom? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + byte[]? bytes = ByteArrayConverter.Convert(ref reader); + return bytes is null ? null : new Bloom(bytes); + } + + public override void Write( + Utf8JsonWriter writer, + Bloom bloom, + JsonSerializerOptions options) + { + ByteArrayConverter.Convert(writer, bloom.Bytes, skipLeadingZeros: false); + } +} diff --git a/src/Nethermind/Nethermind.Core/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Core/ByteArrayConverter.cs new file mode 100644 index 00000000000..d9c148e7422 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/ByteArrayConverter.cs @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Core.Extensions; + +namespace Nethermind.Serialization.Json; + +public class ByteArrayConverter : JsonConverter +{ + private readonly static ushort _hexPrefix = MemoryMarshal.Cast("0x"u8)[0]; + + public override byte[]? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return Convert(ref reader); + } + + public static byte[]? Convert(ref Utf8JsonReader reader) + { + JsonTokenType tokenType = reader.TokenType; + if (tokenType == JsonTokenType.None || tokenType == JsonTokenType.Null) + { + return null; + } + else if (tokenType != JsonTokenType.String) + { + ThrowInvalidOperationException(); + } + + int length = reader.ValueSpan.Length; + byte[]? bytes = null; + if (length == 0) + { + length = checked((int)reader.ValueSequence.Length); + if (length == 0) + { + return null; + } + + bytes = ArrayPool.Shared.Rent(length); + reader.ValueSequence.CopyTo(bytes); + } + + ReadOnlySpan hex = bytes is null ? reader.ValueSpan : bytes.AsSpan(0, length); + if (length >= 2 && Unsafe.As(ref MemoryMarshal.GetReference(hex)) == _hexPrefix) + { + hex = hex[2..]; + } + + byte[] returnVal = Bytes.FromUtf8HexString(hex); + if (bytes is not null) + { + ArrayPool.Shared.Return(bytes); + } + + return returnVal; + } + + [DoesNotReturn] + [StackTraceHidden] + internal static void ThrowInvalidOperationException() + { + throw new InvalidOperationException(); + } + + public override void Write( + Utf8JsonWriter writer, + byte[] bytes, + JsonSerializerOptions options) + { + Convert(writer, bytes, skipLeadingZeros: false); + } + + [SkipLocalsInit] + public static void Convert(Utf8JsonWriter writer, ReadOnlySpan bytes, bool skipLeadingZeros = true) + { + const int maxStackLength = 128; + const int stackLength = 256; + + int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingZeros() : 0; + int length = bytes.Length * 2 - leadingNibbleZeros + 4; + + byte[]? array = null; + if (length > maxStackLength) + { + array = ArrayPool.Shared.Rent(length); + } + + Span hex = (array is null ? stackalloc byte[stackLength] : array)[..length]; + hex[^1] = (byte)'"'; + hex[0] = (byte)'"'; + hex[1] = (byte)'0'; + hex[2] = (byte)'x'; + + Span output = hex[3..^1]; + + bool extraNibble = (leadingNibbleZeros & 1) != 0; + ReadOnlySpan input = bytes.Slice(leadingNibbleZeros / 2); + input.OutputBytesToByteHex(output, extraNibble: (leadingNibbleZeros & 1) != 0); + writer.WriteRawValue(hex, skipInputValidation: true); + + if (array is not null) + { + ArrayPool.Shared.Return(array); + } + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/ThrowHelper.cs b/src/Nethermind/Nethermind.Core/Collections/ThrowHelper.cs index ff15d962124..81a93cd1935 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ThrowHelper.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ThrowHelper.cs @@ -34,6 +34,41 @@ private static void ThrowArgumentNullException(string argName) [StackTraceHidden] internal static void ThrowNotSupportedException() { - throw new NotImplementedException(); + throw new NotSupportedException(); + } + + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowInvalidOperationException_NoWritingAllowed() + { + throw new InvalidOperationException("No Writing Allowed"); + } + + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowArgumentOutOfRangeException_SizeHint() + { + throw new ArgumentOutOfRangeException("sizeHint"); + } + + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowArgumentNullException_Options() + { + throw new ArgumentNullException("options"); + } + + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowArgumentNullException_WritingStream() + { + throw new ArgumentNullException("writingStream"); + } + + [DoesNotReturn] + [StackTraceHidden] + public static void ThrowArgumentOutOfRangeException_Bytes() + { + throw new ArgumentOutOfRangeException("bytes"); } } diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs index 658fe8c7dce..3eb5d3191d5 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs @@ -6,8 +6,10 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Text.Json.Serialization; using Nethermind.Core.Extensions; +using Nethermind.Serialization.Json; namespace Nethermind.Core.Crypto { @@ -115,6 +117,7 @@ public Hash256 ToCommitment() public static bool operator <=(in ValueHash256 left, in ValueHash256 right) => left.CompareTo(in right) <= 0; } + [JsonConverter(typeof(Hash256Converter))] [DebuggerStepThrough] public class Hash256 : IEquatable, IComparable { diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs new file mode 100644 index 00000000000..1ec5de16b07 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Crypto/Hash256Converter.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json.Serialization; +using System.Text.Json; +using Nethermind.Core.Crypto; + +namespace Nethermind.Serialization.Json; + +public class Hash256Converter : JsonConverter +{ + public override Hash256? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + byte[]? bytes = ByteArrayConverter.Convert(ref reader); + return bytes is null ? null : new Hash256(bytes); + } + + public override void Write( + Utf8JsonWriter writer, + Hash256 keccak, + JsonSerializerOptions options) + { + ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); + } +} diff --git a/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs b/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs index 20b01a3b4a0..ba7f359825f 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs @@ -4,11 +4,15 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text.Json.Serialization; using System.Threading; + using Nethermind.Core.Extensions; +using Nethermind.Serialization.Json; namespace Nethermind.Core.Crypto { + [JsonConverter(typeof(PublicKeyConverter))] public class PublicKey : IEquatable { public const int PrefixedLengthInBytes = 65; diff --git a/src/Nethermind/Nethermind.Core/Crypto/PublicKeyConverter.cs b/src/Nethermind/Nethermind.Core/Crypto/PublicKeyConverter.cs new file mode 100644 index 00000000000..37809dbcdb6 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Crypto/PublicKeyConverter.cs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json.Serialization; +using System.Text.Json; + +using Nethermind.Core.Crypto; + +namespace Nethermind.Serialization.Json; + +public class PublicKeyConverter : JsonConverter +{ + public override PublicKey? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + byte[]? bytes = ByteArrayConverter.Convert(ref reader); + if (bytes is null) + { + return null; + } + if (bytes.Length < 64) + { + var newArray = new byte[64]; + bytes.AsSpan().CopyTo(newArray.AsSpan(64 - bytes.Length)); + bytes = newArray; + } + + return new PublicKey(bytes); + } + + public override void Write( + Utf8JsonWriter writer, + PublicKey publicKey, + JsonSerializerOptions options) + { + ByteArrayConverter.Convert(writer, publicKey.Bytes); + } +} diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 0a1dea60776..bb514e056e0 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -638,37 +638,6 @@ public static string ByteArrayToHexViaLookup32Safe(byte[] bytes, bool withZeroX) }); } - [DebuggerStepThrough] - public static string ByteArrayToHexViaLookup32Safe(Memory bytes, bool withZeroX) - { - if (bytes.Length == 0) - { - return withZeroX ? "0x" : string.Empty; - } - - int length = bytes.Length * 2 + (withZeroX ? 2 : 0); - StateSmallMemory stateToPass = new(bytes, withZeroX); - - return string.Create(length, stateToPass, static (chars, state) => - { - ref char charsRef = ref MemoryMarshal.GetReference(chars); - - Memory bytes = state.Bytes; - if (bytes.Length == 0) - { - if (state.WithZeroX) - { - chars[1] = 'x'; - chars[0] = '0'; - } - - return; - } - - OutputBytesToCharHex(ref bytes.Span[0], state.Bytes.Length, ref charsRef, state.WithZeroX, leadingZeros: 0); - }); - } - [DebuggerStepThrough] private static string ByteArrayToHexViaLookup32(byte[] bytes, bool withZeroX, bool skipLeadingZeros, bool withEip55Checksum) @@ -705,6 +674,63 @@ private static string ByteArrayToHexViaLookup32(byte[] bytes, bool withZeroX, bo }); } + public static void OutputBytesToByteHex(this ReadOnlySpan bytes, Span hex, bool extraNibble) + { + int toProcess = bytes.Length; + if (hex.Length != (toProcess * 2) - (extraNibble ? 1 : 0)) + { + ThrowArgumentOutOfRangeException(); + } + + ref byte input = ref MemoryMarshal.GetReference(bytes); + ref ushort lookup32 = ref Lookup16[0]; + ref ushort output = ref Unsafe.As(ref MemoryMarshal.GetReference(hex)); + if (extraNibble) + { + // Odd number of hex bytes, handle the first + // seperately so loop can work in pairs + ushort val = Unsafe.Add(ref lookup32, input); + Unsafe.As(ref output) = (byte)(val >> 8); + + output = ref Unsafe.AddByteOffset(ref output, 1); + input = ref Unsafe.Add(ref input, 1); + toProcess--; + } + + while (toProcess >= 8) + { + output = Unsafe.Add(ref lookup32, input); + Unsafe.Add(ref output, 1) = Unsafe.Add(ref lookup32, Unsafe.Add(ref input, 1)); + Unsafe.Add(ref output, 2) = Unsafe.Add(ref lookup32, Unsafe.Add(ref input, 2)); + Unsafe.Add(ref output, 3) = Unsafe.Add(ref lookup32, Unsafe.Add(ref input, 3)); + Unsafe.Add(ref output, 4) = Unsafe.Add(ref lookup32, Unsafe.Add(ref input, 4)); + Unsafe.Add(ref output, 5) = Unsafe.Add(ref lookup32, Unsafe.Add(ref input, 5)); + Unsafe.Add(ref output, 6) = Unsafe.Add(ref lookup32, Unsafe.Add(ref input, 6)); + Unsafe.Add(ref output, 7) = Unsafe.Add(ref lookup32, Unsafe.Add(ref input, 7)); + + output = ref Unsafe.Add(ref output, 8); + input = ref Unsafe.Add(ref input, 8); + + toProcess -= 8; + } + + while (toProcess > 0) + { + output = Unsafe.Add(ref lookup32, input); + + output = ref Unsafe.Add(ref output, 1); + input = ref Unsafe.Add(ref input, 1); + + toProcess -= 1; + } + + [DoesNotReturn] + static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException(); + } + } + internal static void OutputBytesToCharHex(ref byte input, int length, ref char charsRef, bool withZeroX, int leadingZeros) { if (withZeroX) @@ -879,6 +905,19 @@ private static string ByteArrayToHexViaLookup32Checksum(int length, State stateT } internal static uint[] Lookup32 = CreateLookup32("x2"); + internal static ushort[] Lookup16 = CreateLookup16("x2"); + + private static ushort[] CreateLookup16(string format) + { + ushort[] result = new ushort[256]; + for (int i = 0; i < 256; i++) + { + string s = i.ToString(format); + result[i] = (ushort)(s[0] + (s[1] << 8)); + } + + return result; + } private static uint[] CreateLookup32(string format) { @@ -892,7 +931,7 @@ private static uint[] CreateLookup32(string format) return result; } - internal static int CountLeadingZeros(ReadOnlySpan bytes) + public static int CountLeadingZeros(this ReadOnlySpan bytes) { int leadingZeros = 0; for (int i = 0; i < bytes.Length; i++) @@ -919,7 +958,7 @@ internal static int CountLeadingZeros(ReadOnlySpan bytes) } [DebuggerStepThrough] - public static byte[] FromUtf8HexString(ReadOnlySpan hexString) + public static byte[] FromUtf8HexString(scoped ReadOnlySpan hexString) { if (hexString.Length == 0) { @@ -928,7 +967,50 @@ public static byte[] FromUtf8HexString(ReadOnlySpan hexString) int oddMod = hexString.Length % 2; byte[] result = GC.AllocateUninitializedArray((hexString.Length >> 1) + oddMod); - return HexConverter.TryDecodeFromUtf8(hexString, result, oddMod == 1) ? result : throw new FormatException("Incorrect hex string"); + FromUtf8HexString(hexString, result); + return result; + } + + [DebuggerStepThrough] + public static void FromUtf8HexString(ReadOnlySpan hexString, Span result) + { + int oddMod = hexString.Length % 2; + int length = (hexString.Length >> 1) + oddMod; + if (length != result.Length) + { + ThrowInvalidOperationException(); + } + + bool isSuccess; + if (oddMod == 0 && + BitConverter.IsLittleEndian && (Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && + hexString.Length >= Vector128.Count) + { + isSuccess = HexConverter.TryDecodeFromUtf8_Vector128(hexString, result); + } + else + { + isSuccess = HexConverter.TryDecodeFromUtf8(hexString, result, oddMod == 1); + } + + if (!isSuccess) + { + ThrowFormatException_IncorrectHexString(); + } + } + + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowInvalidOperationException() + { + throw new InvalidOperationException(); + } + + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowFormatException_IncorrectHexString() + { + throw new FormatException("Incorrect hex string"); } [DebuggerStepThrough] diff --git a/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs b/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs index fd072cdf98d..bc00cab479f 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/HexConverter.cs @@ -261,6 +261,82 @@ public static bool TryDecodeFromUtf8(ReadOnlySpan hex, Span bytes, b return (byteLo | byteHi) != 0xFF; } + public static bool TryDecodeFromUtf8_Vector128(ReadOnlySpan hex, Span bytes) + { + Debug.Assert(Ssse3.IsSupported || AdvSimd.Arm64.IsSupported); + Debug.Assert((hex.Length / 2) + (hex.Length % 2) == bytes.Length); + Debug.Assert(hex.Length >= Vector128.Count); + + nuint offset = 0; + nuint lengthSubTwoVector128 = (nuint)hex.Length - ((nuint)Vector128.Count); + + ref byte srcRef = ref MemoryMarshal.GetReference(hex); + ref byte destRef = ref MemoryMarshal.GetReference(bytes); + + do + { + Vector128 vec = Vector128.LoadUnsafe(ref srcRef, offset); + + // Based on "Algorithm #3" https://github.com/WojciechMula/toys/blob/master/simd-parse-hex/geoff_algorithm.cpp + // by Geoff Langdale and Wojciech Mula + // Move digits '0'..'9' into range 0xf6..0xff. + Vector128 t1 = vec + Vector128.Create((byte)(0xFF - '9')); + // And then correct the range to 0xf0..0xf9. + // All other bytes become less than 0xf0. + Vector128 t2 = SubtractSaturate(t1, Vector128.Create((byte)6)); + // Convert into uppercase 'a'..'f' => 'A'..'F' and + // move hex letter 'A'..'F' into range 0..5. + Vector128 t3 = (vec & Vector128.Create((byte)0xDF)) - Vector128.Create((byte)'A'); + // And correct the range into 10..15. + // The non-hex letters bytes become greater than 0x0f. + Vector128 t4 = AddSaturate(t3, Vector128.Create((byte)10)); + // Convert '0'..'9' into nibbles 0..9. Non-digit bytes become + // greater than 0x0f. Finally choose the result: either valid nibble (0..9/10..15) + // or some byte greater than 0x0f. + Vector128 nibbles = Vector128.Min(t2 - Vector128.Create((byte)0xF0), t4); + // Any high bit is a sign that input is not a valid hex data + if (!AllCharsInVector128AreAscii(vec) || + AddSaturate(nibbles, Vector128.Create((byte)(127 - 15))).ExtractMostSignificantBits() != 0) + { + // Input is either non-ASCII or invalid hex data + break; + } + Vector128 output; + if (Ssse3.IsSupported) + { + output = Ssse3.MultiplyAddAdjacent(nibbles, + Vector128.Create((short)0x0110).AsSByte()).AsByte(); + } + else + { + // Workaround for missing MultiplyAddAdjacent on ARM + Vector128 even = AdvSimd.Arm64.TransposeEven(nibbles, Vector128.Zero).AsInt16(); + Vector128 odd = AdvSimd.Arm64.TransposeOdd(nibbles, Vector128.Zero).AsInt16(); + even = AdvSimd.ShiftLeftLogical(even, 4).AsInt16(); + output = AdvSimd.AddSaturate(even, odd).AsByte(); + } + // Accumulate output in lower INT64 half and take care about endianness + output = Vector128.Shuffle(output, Vector128.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 0, 0, 0, 0, 0, 0, 0, 0)); + // Store 8 bytes in dest by given offset + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destRef, offset / 2), output.AsUInt64().ToScalar()); + + offset += (nuint)Vector128.Count * 2; + if (offset == (nuint)hex.Length) + { + return true; + } + // Overlap with the current chunk for trailing elements + if (offset > lengthSubTwoVector128) + { + offset = lengthSubTwoVector128; + } + } + while (true); + + // Fall back to the scalar routine in case of invalid input. + return TryDecodeFromUtf8(hex[(int)offset..], bytes[(int)offset..], isOdd: false); + } + public static bool TryDecodeFromUtf16_Vector128(ReadOnlySpan chars, Span bytes) { Debug.Assert(Ssse3.IsSupported || AdvSimd.Arm64.IsSupported); @@ -351,6 +427,15 @@ private static bool AllCharsInVector128AreAscii(Vector128 vec) return (vec & Vector128.Create(unchecked((ushort)~0x007F))) == Vector128.Zero; } + /// + /// Returns true iff the Vector128 represents 8 ASCII UTF-16 characters in machine endianness. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool AllCharsInVector128AreAscii(Vector128 vec) + { + return (vec & Vector128.Create(unchecked((byte)~0x7F))) == Vector128.Zero; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector128 AddSaturate(Vector128 left, Vector128 right) { diff --git a/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs b/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs index 0017eec04d8..1306742283c 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs @@ -3,6 +3,8 @@ using System; using System.Buffers.Binary; +using System.Runtime.CompilerServices; + using Nethermind.Int256; namespace Nethermind.Core.Extensions; @@ -125,16 +127,12 @@ public static byte[] ToBigEndianByteArray(this long value) return bytes; } - public static void WriteBigEndian(this long value, Span output) { BinaryPrimitives.WriteInt64BigEndian(output, value); } - [ThreadStatic] - private static byte[]? t_byteBuffer64; - private static byte[] GetByteBuffer64() => t_byteBuffer64 ??= new byte[8]; - + [SkipLocalsInit] public static string ToHexString(this long value, bool skipLeadingZeros) { if (value == 0L) @@ -142,11 +140,12 @@ public static string ToHexString(this long value, bool skipLeadingZeros) return "0x0"; } - byte[] bytes = GetByteBuffer64(); + Span bytes = stackalloc byte[8]; BinaryPrimitives.WriteInt64BigEndian(bytes, value); return bytes.ToHexString(true, skipLeadingZeros, false); } + [SkipLocalsInit] public static string ToHexString(this ulong value, bool skipLeadingZeros) { if (value == 0UL) @@ -154,20 +153,17 @@ public static string ToHexString(this ulong value, bool skipLeadingZeros) return "0x0"; } - byte[] bytes = GetByteBuffer64(); + Span bytes = stackalloc byte[8]; BinaryPrimitives.WriteUInt64BigEndian(bytes, value); return bytes.ToHexString(true, skipLeadingZeros, false); } - [ThreadStatic] - private static byte[]? t_byteBuffer256; - private static byte[] GetByteBuffer256() => t_byteBuffer256 ??= new byte[32]; - + [SkipLocalsInit] public static string ToHexString(this in UInt256 value, bool skipLeadingZeros) { if (skipLeadingZeros) { - if (value == UInt256.Zero) + if (value == default) { return "0x0"; } @@ -178,7 +174,7 @@ public static string ToHexString(this in UInt256 value, bool skipLeadingZeros) } } - byte[] bytes = GetByteBuffer256(); + Span bytes = stackalloc byte[32]; value.ToBigEndian(bytes); return bytes.ToHexString(true, skipLeadingZeros, false); } diff --git a/src/Nethermind/Nethermind.Core/LowerCaseJsonStringEnumConverter.cs b/src/Nethermind/Nethermind.Core/LowerCaseJsonStringEnumConverter.cs new file mode 100644 index 00000000000..88a5e804c9f --- /dev/null +++ b/src/Nethermind/Nethermind.Core/LowerCaseJsonStringEnumConverter.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Nethermind.Serialization.Json; + +public class LowerCaseJsonStringEnumConverter : JsonStringEnumConverter where TEnum : struct, System.Enum +{ + public LowerCaseJsonStringEnumConverter() : base(namingPolicy: LowerCaseJsonNamingPolicy.Default) + { + } +} + +public class LowerCaseJsonNamingPolicy : JsonNamingPolicy +{ + public static LowerCaseJsonNamingPolicy Default { get; } = new(); + + public override string ConvertName(string name) + { + return name.ToLowerInvariant(); + } +} diff --git a/src/Nethermind/Nethermind.Core/Nethermind.Core.csproj b/src/Nethermind/Nethermind.Core/Nethermind.Core.csproj index ac9107fdf7a..9c993255683 100644 --- a/src/Nethermind/Nethermind.Core/Nethermind.Core.csproj +++ b/src/Nethermind/Nethermind.Core/Nethermind.Core.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Nethermind/Nethermind.Core/TypeDiscovery.cs b/src/Nethermind/Nethermind.Core/TypeDiscovery.cs index a3731a2be2d..1da004c3237 100644 --- a/src/Nethermind/Nethermind.Core/TypeDiscovery.cs +++ b/src/Nethermind/Nethermind.Core/TypeDiscovery.cs @@ -51,7 +51,6 @@ private static void LoadAllImpl() || assembly.FullName.StartsWith("NLog") || assembly.FullName.StartsWith("netstandard") || assembly.FullName.StartsWith("TestableIO") - || assembly.FullName.StartsWith("Newtonsoft") || assembly.FullName.StartsWith("DotNetty")) { continue; diff --git a/src/Nethermind/Nethermind.Core/Withdrawal.cs b/src/Nethermind/Nethermind.Core/Withdrawal.cs index 3d0e9da19b2..13741586406 100644 --- a/src/Nethermind/Nethermind.Core/Withdrawal.cs +++ b/src/Nethermind/Nethermind.Core/Withdrawal.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Text; +using System.Text.Json.Serialization; using Nethermind.Core.Extensions; using Nethermind.Int256; -using Newtonsoft.Json; namespace Nethermind.Core; @@ -30,7 +30,7 @@ public class Withdrawal /// /// Gets or sets the withdrawal amount in GWei. /// - [JsonProperty(PropertyName = "amount")] + [JsonPropertyName("amount")] public ulong AmountInGwei { get; set; } [JsonIgnore] diff --git a/src/Nethermind/Nethermind.EthStats/Nethermind.EthStats.csproj b/src/Nethermind/Nethermind.EthStats/Nethermind.EthStats.csproj index dafcfb6c121..c2411ed8992 100644 --- a/src/Nethermind/Nethermind.EthStats/Nethermind.EthStats.csproj +++ b/src/Nethermind/Nethermind.EthStats/Nethermind.EthStats.csproj @@ -5,7 +5,6 @@ - diff --git a/src/Nethermind/Nethermind.EthStats/Senders/MessageSender.cs b/src/Nethermind/Nethermind.EthStats/Senders/MessageSender.cs index e004bca363e..4c05e29b513 100644 --- a/src/Nethermind/Nethermind.EthStats/Senders/MessageSender.cs +++ b/src/Nethermind/Nethermind.EthStats/Senders/MessageSender.cs @@ -2,21 +2,16 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; using Nethermind.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; +using Nethermind.Serialization.Json; using Websocket.Client; namespace Nethermind.EthStats.Senders { public class MessageSender : IMessageSender { - private static readonly JsonSerializerSettings SerializerSettings = new() - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - private readonly string _instanceId; private readonly ILogger _logger; @@ -34,7 +29,7 @@ public Task SendAsync(IWebsocketClient? client, T message, string? type = nul } (EmitMessage? emitMessage, string? messageType) = CreateMessage(message, type); - string payload = JsonConvert.SerializeObject(emitMessage, SerializerSettings); + string payload = JsonSerializer.Serialize(emitMessage, EthereumJsonSerializer.JsonOptions); if (_logger.IsTrace) _logger.Trace($"Sending ETH stats message '{messageType}': {payload}"); client.Send(payload); diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeJavaScriptTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeJavaScriptTracerTests.cs index 72fa010945a..45009fe185e 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeJavaScriptTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeJavaScriptTracerTests.cs @@ -446,8 +446,7 @@ public void complex_tracer_nested_call() } - private static EthereumJsonSerializer GetEthereumJsonSerializer() => - new(null, new JavaScriptObjectConverter()); + private static EthereumJsonSerializer GetEthereumJsonSerializer() => new(); private const string ComplexTracer = """ { diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxFileTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxFileTracerTests.cs index ad6a509a33e..9e89f054e8e 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxFileTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxFileTracerTests.cs @@ -54,13 +54,13 @@ public void Should_return_memory_when_enabled() var entries = new List(); var trace = ExecuteAndTraceToFile(e => entries.Add(CloneTraceEntry(e)), GetBytecode(), GethTraceOptions.Default with { EnableMemory = true }); - entries[0].Memory.Count.Should().Be(0); - entries[1].Memory.Count.Should().Be(0); - entries[2].Memory.Count.Should().Be(0); - entries[3].Memory.Count.Should().Be(1); - entries[4].Memory.Count.Should().Be(1); - entries[5].Memory.Count.Should().Be(1); - entries[6].Memory.Count.Should().Be(2); + entries[0].Memory.Length.Should().Be(0); + entries[1].Memory.Length.Should().Be(0); + entries[2].Memory.Length.Should().Be(0); + entries[3].Memory.Length.Should().Be(1); + entries[4].Memory.Length.Should().Be(1); + entries[5].Memory.Length.Should().Be(1); + entries[6].Memory.Length.Should().Be(2); } [Test] @@ -69,13 +69,13 @@ public void Should_return_stack_when_enabled() var entries = new List(); var trace = ExecuteAndTraceToFile(e => entries.Add(CloneTraceEntry(e)), GetBytecode(), GethTraceOptions.Default); - entries[0].Stack.Count.Should().Be(0); - entries[1].Stack.Count.Should().Be(1); - entries[2].Stack.Count.Should().Be(2); - entries[3].Stack.Count.Should().Be(0); - entries[4].Stack.Count.Should().Be(1); - entries[5].Stack.Count.Should().Be(2); - entries[6].Stack.Count.Should().Be(0); + entries[0].Stack.Length.Should().Be(0); + entries[1].Stack.Length.Should().Be(1); + entries[2].Stack.Length.Should().Be(2); + entries[3].Stack.Length.Should().Be(0); + entries[4].Stack.Length.Should().Be(1); + entries[5].Stack.Length.Should().Be(2); + entries[6].Stack.Length.Should().Be(0); } [Test] diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTrace.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTrace.cs index 49c8808ba68..8db5fabea8e 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTrace.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTrace.cs @@ -3,10 +3,12 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Evm.Tracing.GethStyle; +[JsonConverter(typeof(GethLikeTxTraceConverter))] public class GethLikeTxTrace : IDisposable { private readonly IDisposable? _disposable; @@ -26,10 +28,10 @@ public GethLikeTxTrace() { } public byte[] ReturnValue { get; set; } = Array.Empty(); - [JsonProperty(PropertyName = "structLogs")] public List Entries { get; set; } = new(); public GethLikeJavaScriptTrace? CustomTracerResult { get; set; } + public void Dispose() { _disposable?.Dispose(); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/GethLikeTxTraceConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTraceConverter.cs similarity index 67% rename from src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/GethLikeTxTraceConverter.cs rename to src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTraceConverter.cs index 75cec943f1f..b278deb2afd 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/GethLikeTxTraceConverter.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTraceConverter.cs @@ -2,14 +2,52 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Linq; -using Nethermind.Evm.Tracing.GethStyle; -using Nethermind.JsonRpc.Modules.Trace; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.JsonRpc.Modules.DebugModule; +namespace Nethermind.Evm.Tracing.GethStyle; +public class GethLikeTxTraceConverter : JsonConverter +{ + public override GethLikeTxTrace Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) => throw new NotSupportedException(); + + public override void Write( + Utf8JsonWriter writer, + GethLikeTxTrace value, + JsonSerializerOptions options) + { + if (value is null) + { + writer.WriteNullValue(); + } + else if (value.CustomTracerResult is not null) + { + JsonSerializer.Serialize(writer, value.CustomTracerResult, options); + } + else + { + writer.WriteStartObject(); + + writer.WritePropertyName("gas"u8); + JsonSerializer.Serialize(writer, value.Gas, options); + + writer.WritePropertyName("failed"u8); + JsonSerializer.Serialize(writer, value.Failed, options); + + writer.WritePropertyName("returnValue"u8); + JsonSerializer.Serialize(writer, value.ReturnValue, options); + + writer.WritePropertyName("structLogs"u8); + JsonSerializer.Serialize(writer, value.Entries, options); + + writer.WriteEndObject(); + } + } +} +/* public class GethLikeTxTraceConverter : JsonConverter { public override GethLikeTxTrace ReadJson( @@ -95,3 +133,4 @@ private static void WriteEntries(JsonWriter writer, List entri writer.WriteEndArray(); } } +*/ diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTraceJsonLinesConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTraceJsonLinesConverter.cs index 266079badef..853f639dedf 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTraceJsonLinesConverter.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethLikeTxTraceJsonLinesConverter.cs @@ -53,7 +53,7 @@ public override void Write(Utf8JsonWriter writer, GethTxFileTraceEntry value, Js writer.WritePropertyName("memSize"); writer.WriteNumberValue(value.MemorySize ?? 0UL); - if ((value.Memory?.Count ?? 0) != 0) + if ((value.Memory?.Length ?? 0) != 0) { var memory = string.Concat(value.Memory); diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs index f3c9d9f6cc9..3a9504af097 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTraceOptions.cs @@ -2,38 +2,39 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Core.Crypto; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Nethermind.Evm.Tracing.GethStyle; public record GethTraceOptions { - [JsonProperty("disableMemory")] + [JsonPropertyName("disableMemory")] [Obsolete("Use EnableMemory instead.")] public bool DisableMemory { get => !EnableMemory; init => EnableMemory = !value; } - [JsonProperty("disableStack")] - public bool DisableStack { get; init; } - - [JsonProperty("disableStorage")] + [JsonPropertyName("disableStorage")] public bool DisableStorage { get; init; } - [JsonProperty("enableMemory")] + [JsonPropertyName("enableMemory")] public bool EnableMemory { get; init; } - [JsonProperty("timeout")] + [JsonPropertyName("disableStack")] + public bool DisableStack { get; init; } + + [JsonPropertyName("timeout")] public string Timeout { get; init; } - [JsonProperty("tracer")] + [JsonPropertyName("tracer")] public string Tracer { get; init; } - [JsonProperty("txHash")] + [JsonPropertyName("txHash")] public Hash256? TxHash { get; init; } - [JsonProperty("tracerConfig")] - public JRaw? TracerConfig { get; init; } + [JsonPropertyName("tracerConfig")] + public JsonElement? TracerConfig { get; init; } public static GethTraceOptions Default { get; } = new(); } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxMemoryTraceEntry.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxMemoryTraceEntry.cs index 8407b387968..e4c8583a5e6 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxMemoryTraceEntry.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxMemoryTraceEntry.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; namespace Nethermind.Evm.Tracing.GethStyle; @@ -12,13 +13,20 @@ internal override void UpdateMemorySize(ulong size) base.UpdateMemorySize(size); // Geth's approach to memory trace is to show empty memory spaces on entry for the values that are being set by the operation - Memory ??= new List(); + Memory ??= Array.Empty(); - int missingChunks = (int)((size - (ulong)Memory.Count * EvmPooledMemory.WordSize) / EvmPooledMemory.WordSize); + int missingChunks = (int)((size - (ulong)Memory.Length * EvmPooledMemory.WordSize) / EvmPooledMemory.WordSize); - for (int i = 0; i < missingChunks; i++) + if (missingChunks > 0) { - Memory.Add("0000000000000000000000000000000000000000000000000000000000000000"); + var memory = Memory; + Array.Resize(ref memory, memory.Length + missingChunks); + for (int i = Memory.Length; i < memory.Length; i++) + { + memory[i] = "0000000000000000000000000000000000000000000000000000000000000000"; + } + + Memory = memory; } } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxTraceEntry.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxTraceEntry.cs index 54165d66f60..16e9565a56c 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxTraceEntry.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/GethTxTraceEntry.cs @@ -2,26 +2,38 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json.Serialization; +using Nethermind.Serialization.Json; namespace Nethermind.Evm.Tracing.GethStyle; public class GethTxTraceEntry { - public int Depth { get; set; } + public GethTxTraceEntry() + { + } - public string? Error { get; set; } + [JsonPropertyName("pc")] + [JsonConverter(typeof(LongRawJsonConverter))] + public long ProgramCounter { get; set; } + + [JsonPropertyName("op")] + public string? Opcode { get; set; } + [JsonConverter(typeof(LongRawJsonConverter))] public long Gas { get; set; } + [JsonConverter(typeof(LongRawJsonConverter))] public long GasCost { get; set; } - public IList? Memory { get; set; } + public int Depth { get; set; } - public string? Opcode { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string? Error { get; set; } - public long ProgramCounter { get; set; } + public string[]? Stack { get; set; } - public IList? Stack { get; set; } + public string[]? Memory { get; set; } public Dictionary? Storage { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/Engine.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/Engine.cs index 300da421ef0..69268330ce1 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/Engine.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/Engine.cs @@ -182,11 +182,11 @@ public dynamic CreateTracer(string tracer) static V8Script LoadJavaScriptCode(string tracer) { tracer = tracer.Trim(); - if (tracer.StartsWith("_")) + if (tracer.StartsWith('_')) { throw new ArgumentException($"Cannot access internal tracer '{tracer}'"); } - else if (tracer.StartsWith("{") && tracer.EndsWith("}")) + else if (tracer.StartsWith('{') && tracer.EndsWith('}')) { int hashCode = tracer.GetHashCode(); if (_runtimeScripts.TryGet(hashCode, out V8Script script)) @@ -220,7 +220,7 @@ static V8Script LoadJavaScriptCode(string tracer) static string LoadJavaScriptDebugCode(string tracer) { tracer = tracer.Trim(); - if (tracer.StartsWith("{") && tracer.EndsWith("}")) + if (tracer.StartsWith('{') && tracer.EndsWith('}')) { return PackTracerCode(tracer); } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTrace.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTrace.cs index 1fb35eeb60c..4ec00f8f0e1 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTrace.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTrace.cs @@ -1,8 +1,13 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + +using Nethermind.Evm.Tracing.GethStyle.JavaScript; + namespace Nethermind.Evm.Tracing.GethStyle; +[JsonConverter(typeof(GethLikeJavaScriptTraceConverter))] public class GethLikeJavaScriptTrace { private static readonly object _empty = new { }; diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTraceConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTraceConverter.cs index bc18773d8f4..63e850243e2 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTraceConverter.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/JavaScript/GethLikeJavaScriptTraceConverter.cs @@ -2,20 +2,20 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; namespace Nethermind.Evm.Tracing.GethStyle.JavaScript; public class GethLikeJavaScriptTraceConverter : JsonConverter { - public override bool CanRead => false; - - public override void WriteJson(JsonWriter writer, GethLikeJavaScriptTrace? value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, GethLikeJavaScriptTrace value, JsonSerializerOptions options) { if (value is null) { - writer.WriteNull(); + writer.WriteNullValue(); return; } @@ -23,7 +23,7 @@ public override void WriteJson(JsonWriter writer, GethLikeJavaScriptTrace? value ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Raw; try { - serializer.Serialize(writer, value.Value); + JsonSerializer.Serialize(writer, value.Value, options); } finally { @@ -31,6 +31,8 @@ public override void WriteJson(JsonWriter writer, GethLikeJavaScriptTrace? value } } - public override GethLikeJavaScriptTrace? ReadJson(JsonReader reader, Type objectType, GethLikeJavaScriptTrace? existingValue, bool hasExistingValue, JsonSerializer serializer) => + public override GethLikeJavaScriptTrace? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { throw new NotSupportedException(); + } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChange.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChange.cs index f02d54a9fd1..e6dff46cc2e 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChange.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChange.cs @@ -2,10 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; + using Nethermind.Int256; +using System.Text.Json.Serialization; namespace Nethermind.Evm.Tracing.ParityStyle { + [JsonConverter(typeof(ParityAccountStateChangeJsonConverter))] public class ParityAccountStateChange { public ParityStateChange? Code { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChangeJsonConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChangeJsonConverter.cs new file mode 100644 index 00000000000..d82c1540a79 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAccountStateChangeJsonConverter.cs @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Nethermind.Int256; +using Nethermind.Serialization.Json; + +namespace Nethermind.Evm.Tracing.ParityStyle; + +public class ParityAccountStateChangeJsonConverter : JsonConverter +{ + private Bytes32Converter _32BytesConverter = new(); + + public override ParityAccountStateChange Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) => throw new NotImplementedException(); + + private void WriteChange(Utf8JsonWriter writer, ParityStateChange change, JsonSerializerOptions options) + { + if (change is null) + { + writer.WriteStringValue("="u8); + } + else + { + if (change.Before is null) + { + writer.WriteStartObject(); + writer.WritePropertyName("+"u8); + JsonSerializer.Serialize(writer, change.After, options); + writer.WriteEndObject(); + } + else + { + writer.WriteStartObject(); + writer.WritePropertyName("*"u8); + writer.WriteStartObject(); + writer.WritePropertyName("from"u8); + JsonSerializer.Serialize(writer, change.Before, options); + writer.WritePropertyName("to"u8); + JsonSerializer.Serialize(writer, change.After, options); + writer.WriteEndObject(); + writer.WriteEndObject(); + } + } + } + + private void WriteChange(Utf8JsonWriter writer, ParityStateChange change, JsonSerializerOptions options) + { + if (change is null) + { + writer.WriteStringValue("="u8); + } + else + { + if (change.Before is null) + { + writer.WriteStartObject(); + writer.WritePropertyName("+"u8); + JsonSerializer.Serialize(writer, change.After, options); + writer.WriteEndObject(); + } + else + { + writer.WriteStartObject(); + writer.WritePropertyName("*"u8); + writer.WriteStartObject(); + writer.WritePropertyName("from"u8); + JsonSerializer.Serialize(writer, change.Before, options); + writer.WritePropertyName("to"u8); + JsonSerializer.Serialize(writer, change.After, options); + writer.WriteEndObject(); + writer.WriteEndObject(); + } + } + } + + private void WriteStorageChange(Utf8JsonWriter writer, ParityStateChange change, bool isNew, JsonSerializerOptions options) + { + if (change is null) + { + writer.WriteStringValue("="u8); + } + else + { + if (isNew) + { + writer.WriteStartObject(); + writer.WritePropertyName("+"u8); + _32BytesConverter.Write(writer, change.After, options); + writer.WriteEndObject(); + } + else + { + writer.WriteStartObject(); + writer.WritePropertyName("*"u8); + writer.WriteStartObject(); + writer.WritePropertyName("from"u8); + _32BytesConverter.Write(writer, change.Before, options); + writer.WritePropertyName("to"u8); + _32BytesConverter.Write(writer, change.After, options); + writer.WriteEndObject(); + writer.WriteEndObject(); + } + } + } + + public override void Write( + Utf8JsonWriter writer, + ParityAccountStateChange value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WritePropertyName("balance"u8); + if (value.Balance is null) + { + writer.WriteStringValue("="u8); + } + else + { + WriteChange(writer, value.Balance, options); + } + + writer.WritePropertyName("code"u8); + if (value.Code is null) + { + writer.WriteStringValue("="u8); + } + else + { + WriteChange(writer, value.Code, options); + } + + writer.WritePropertyName("nonce"u8); + if (value.Nonce is null) + { + writer.WriteStringValue("="u8); + } + else + { + WriteChange(writer, value.Nonce, options); + } + + writer.WritePropertyName("storage"u8); + + writer.WriteStartObject(); + if (value.Storage is not null) + { + foreach (KeyValuePair> pair in value.Storage.OrderBy(s => s.Key)) + { + string trimmedKey = pair.Key.ToString("x64"); + trimmedKey = trimmedKey.Substring(trimmedKey.Length - 64, 64); + + writer.WritePropertyName(string.Concat("0x", trimmedKey)); + WriteStorageChange(writer, pair.Value, value.Balance?.Before is null && value.Balance?.After is not null, options); + } + } + + writer.WriteEndObject(); + + writer.WriteEndObject(); + } +} diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAction.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAction.cs index 15db698edc6..d268fa394c7 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAction.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityAction.cs @@ -2,11 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json.Serialization; + using Nethermind.Core; using Nethermind.Int256; namespace Nethermind.Evm.Tracing.ParityStyle { + [JsonConverter(typeof(ParityTraceActionConverter))] public class ParityTraceAction { public int[]? TraceAddress { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTrace.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTrace.cs index f8af9bcafc8..c17f99201d6 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTrace.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTrace.cs @@ -2,8 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json.Serialization; + using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Serialization.Json; namespace Nethermind.Evm.Tracing.ParityStyle { @@ -13,6 +16,7 @@ public class ParityLikeTxTrace public Hash256? BlockHash { get; set; } + [JsonConverter(typeof(LongRawJsonConverter))] public long BlockNumber { get; set; } public int? TransactionPosition { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceActionConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceActionConverter.cs new file mode 100644 index 00000000000..3feb7a025bf --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceActionConverter.cs @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Nethermind.Core; +using Nethermind.Int256; + +namespace Nethermind.Evm.Tracing.ParityStyle +{ + /* + * { + * "callType": "call", + * "from": "0x430adc807210dab17ce7538aecd4040979a45137", + * "gas": "0x1a1f8", + * "input": "0x", + * "to": "0x9bcb0733c56b1d8f0c7c4310949e00485cae4e9d", + * "value": "0x2707377c7552d8000" + * }, + */ + public class ParityTraceActionConverter : JsonConverter + { + public override ParityTraceAction Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new ArgumentException($"Cannot deserialize {nameof(ParityTraceActionConverter)}."); + } + + var value = new ParityTraceAction(); + + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new ArgumentException($"Cannot deserialize {nameof(ParityTraceActionConverter)}."); + } + + if (reader.ValueTextEquals("callType"u8)) + { + reader.Read(); + value.CallType = reader.GetString(); + } + else if (reader.ValueTextEquals("type"u8)) + { + reader.Read(); + value.Type = reader.GetString(); + } + else if (reader.ValueTextEquals("creationMethod"u8)) + { + reader.Read(); + value.CreationMethod = reader.GetString(); + } + else if (reader.ValueTextEquals("from"u8)) + { + reader.Read(); + value.From = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("to"u8)) + { + reader.Read(); + value.To = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("gas"u8)) + { + reader.Read(); + value.Gas = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("value"u8)) + { + reader.Read(); + value.Value = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("input"u8)) + { + reader.Read(); + value.Input = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("result"u8)) + { + reader.Read(); + value.Result = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("subtraces"u8)) + { + reader.Read(); + value.Subtraces = JsonSerializer.Deserialize>(ref reader, options); + } + else if (reader.ValueTextEquals("author"u8)) + { + reader.Read(); + value.Author = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("rewardType"u8)) + { + reader.Read(); + value.RewardType = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("error"u8)) + { + reader.Read(); + value.Error = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("traceAddress"u8)) + { + reader.Read(); + value.TraceAddress = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("includeInTrace"u8)) + { + reader.Read(); + value.IncludeInTrace = reader.GetBoolean(); + } + else if (reader.ValueTextEquals("isPrecompiled"u8)) + { + reader.Read(); + value.IsPrecompiled = reader.GetBoolean(); + } + + reader.Read(); + } + + return value; + } + + public override void Write( + Utf8JsonWriter writer, + ParityTraceAction value, + JsonSerializerOptions options) + { + if (value.Type == "reward") + { + WriteRewardJson(writer, value, options); + return; + } + + if (value.Type == "suicide") + { + WriteSelfDestructJson(writer, value, options); + return; + } + writer.WriteStartObject(); + + if (value.CallType != "create") + { + writer.WriteString("callType"u8, value.CallType); + } + else + { + writer.WriteString("creationMethod"u8, value.CreationMethod); + } + + writer.WritePropertyName("from"u8); + JsonSerializer.Serialize(writer, value.From, options); + writer.WritePropertyName("gas"u8); + JsonSerializer.Serialize(writer, value.Gas, options); + + writer.WritePropertyName("input"u8); + JsonSerializer.Serialize(writer, value.Input, options); + writer.WritePropertyName("to"u8); + JsonSerializer.Serialize(writer, value.To, options); + + writer.WritePropertyName("value"u8); + JsonSerializer.Serialize(writer, value.Value, options); + + writer.WriteEndObject(); + } + + private void WriteSelfDestructJson(Utf8JsonWriter writer, ParityTraceAction value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WritePropertyName("address"u8); + JsonSerializer.Serialize(writer, value.From, options); + + writer.WritePropertyName("balance"u8); + JsonSerializer.Serialize(writer, value.Value, options); + + writer.WritePropertyName("refundAddress"u8); + JsonSerializer.Serialize(writer, value.To, options); + + writer.WriteEndObject(); + } + + private void WriteRewardJson(Utf8JsonWriter writer, ParityTraceAction value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WritePropertyName("author"u8); + JsonSerializer.Serialize(writer, value.Author, options); + + writer.WritePropertyName("rewardType"u8); + JsonSerializer.Serialize(writer, value.RewardType, options); + + writer.WritePropertyName("value"u8); + JsonSerializer.Serialize(writer, value.Value, options); + writer.WriteEndObject(); + } + } +} diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityResult.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceResult.cs similarity index 80% rename from src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityResult.cs rename to src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceResult.cs index a84a116c226..cbec45e04c2 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityResult.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceResult.cs @@ -1,10 +1,13 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + using Nethermind.Core; namespace Nethermind.Evm.Tracing.ParityStyle { + [JsonConverter(typeof(ParityTraceResultConverter))] public class ParityTraceResult { public long GasUsed { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceResultConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceResultConverter.cs new file mode 100644 index 00000000000..5c38c2ca3dd --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityTraceResultConverter.cs @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Nethermind.Core; + +namespace Nethermind.Evm.Tracing.ParityStyle; + +public class ParityTraceResultConverter : JsonConverter +{ + public override ParityTraceResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new ArgumentException($"Cannot deserialize {nameof(ParityTraceActionConverter)}."); + } + + var value = new ParityTraceResult(); + + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new ArgumentException($"Cannot deserialize {nameof(ParityTraceActionConverter)}."); + } + + if (reader.ValueTextEquals("gasUsed"u8)) + { + reader.Read(); + value.GasUsed = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("output"u8)) + { + reader.Read(); + value.Output = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("address"u8)) + { + reader.Read(); + value.Address = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("code"u8)) + { + reader.Read(); + value.Code = JsonSerializer.Deserialize(ref reader, options); + } + + reader.Read(); + } + + return value; + } + + public override void Write( + Utf8JsonWriter writer, + ParityTraceResult value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + if (value.Address is not null) + { + writer.WritePropertyName("address"u8); + JsonSerializer.Serialize(writer, value.Address, options); + writer.WritePropertyName("code"u8); + JsonSerializer.Serialize(writer, value.Code, options); + } + + writer.WritePropertyName("gasUsed"u8); + JsonSerializer.Serialize(writer, value.GasUsed, options); + + if (value.Address is null) + { + writer.WritePropertyName("output"u8); + JsonSerializer.Serialize(writer, value.Output, options); + } + + writer.WriteEndObject(); + } +} diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTrace.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTrace.cs index e452830d0f6..ba44a80c022 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTrace.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTrace.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + namespace Nethermind.Evm.Tracing.ParityStyle { // { @@ -14,6 +16,7 @@ namespace Nethermind.Evm.Tracing.ParityStyle // "pc": 526.0, // "sub": null // } + [JsonConverter(typeof(ParityVmOperationTraceConverter))] public class ParityVmOperationTrace { public long Cost { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTraceConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTraceConverter.cs new file mode 100644 index 00000000000..6d25e255741 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmOperationTraceConverter.cs @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Nethermind.Evm.Tracing.ParityStyle +{ + public class ParityVmOperationTraceConverter : JsonConverter + { + public override ParityVmOperationTrace Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) => throw new NotImplementedException(); + + public override void Write( + Utf8JsonWriter writer, + ParityVmOperationTrace value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WriteNumber("cost"u8, value.Cost); + writer.WritePropertyName("ex"u8); + writer.WriteStartObject(); + writer.WritePropertyName("mem"u8); + if (value.Memory is not null) + { + writer.WriteStartObject(); + writer.WritePropertyName("data"u8); + JsonSerializer.Serialize(writer, value.Memory.Data, options); + writer.WritePropertyName("off"u8); + JsonSerializer.Serialize(writer, value.Memory.Offset, options); + writer.WriteEndObject(); + } + else + { + writer.WriteNullValue(); + } + + writer.WritePropertyName("push"u8); + if (value.Push is not null) + { + writer.WriteStartArray(); + for (int i = 0; i < value.Push.Length; i++) + { + JsonSerializer.Serialize(writer, value.Push[i], options); + } + + writer.WriteEndArray(); + } + else + { + writer.WriteNullValue(); + } + + writer.WritePropertyName("store"u8); + if (value.Store is not null) + { + writer.WriteStartObject(); + writer.WritePropertyName("key"u8); + JsonSerializer.Serialize(writer, value.Store.Key, options); + writer.WritePropertyName("val"u8); + JsonSerializer.Serialize(writer, value.Store.Value, options); + writer.WriteEndObject(); + } + else + { + writer.WriteNullValue(); + } + + writer.WriteNumber("used"u8, value.Used); + writer.WriteEndObject(); + + writer.WriteNumber("pc"u8, value.Pc); + writer.WritePropertyName("sub"u8); + JsonSerializer.Serialize(writer, value.Sub, options); + + writer.WriteEndObject(); + } + } +} diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTrace.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTrace.cs index 6bb748812c2..0e4a7815139 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTrace.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTrace.cs @@ -1,11 +1,14 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -namespace Nethermind.Evm.Tracing.ParityStyle +using System.Text.Json.Serialization; +using Nethermind.JsonRpc.Modules.Trace; + +namespace Nethermind.Evm.Tracing.ParityStyle; + +[JsonConverter(typeof(ParityVmTraceConverter))] +public class ParityVmTrace { - public class ParityVmTrace - { - public byte[] Code { get; set; } - public ParityVmOperationTrace[] Operations { get; set; } - } + public byte[] Code { get; set; } + public ParityVmOperationTrace[] Operations { get; set; } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTraceConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTraceConverter.cs new file mode 100644 index 00000000000..4e62a254357 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityVmTraceConverter.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Evm.Tracing.ParityStyle; + +namespace Nethermind.JsonRpc.Modules.Trace; + +public class ParityVmTraceConverter : JsonConverter +{ + public override void Write(Utf8JsonWriter writer, ParityVmTrace value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WritePropertyName("code"u8); + JsonSerializer.Serialize(writer, value.Code ?? Array.Empty(), options); + writer.WritePropertyName("ops"u8); + JsonSerializer.Serialize(writer, value.Operations, options); + writer.WriteEndObject(); + } + + public override ParityVmTrace? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } +} diff --git a/src/Nethermind/Nethermind.Evm/Tracing/TraceMemory.cs b/src/Nethermind/Nethermind.Evm/Tracing/TraceMemory.cs index ee1fb5ec8ef..cec5cf8726e 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/TraceMemory.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/TraceMemory.cs @@ -19,25 +19,27 @@ public TraceMemory(ulong size, ReadOnlyMemory memory) _memory = memory; } - public List ToHexWordList() + public string[] ToHexWordList() { - List memory = new(((int)Size / EvmPooledMemory.WordSize) + ((Size % EvmPooledMemory.WordSize == 0) ? 0 : 1)); + string[] memory = new string[((int)Size / EvmPooledMemory.WordSize) + ((Size % EvmPooledMemory.WordSize == 0) ? 0 : 1)]; int traceLocation = 0; + int i = 0; while ((ulong)traceLocation < Size) { int sizeAvailable = Math.Min(EvmPooledMemory.WordSize, _memory.Length - traceLocation); if (sizeAvailable > 0) { ReadOnlySpan bytes = _memory.Slice(traceLocation, sizeAvailable).Span; - memory.Add(bytes.ToHexString()); + memory[i] = bytes.ToHexString(); } else // Memory might not be initialized { - memory.Add(Bytes.Zero32.ToHexString()); + memory[i] = Bytes.Zero32.ToHexString(); } traceLocation += EvmPooledMemory.WordSize; + i++; } return memory; diff --git a/src/Nethermind/Nethermind.Evm/Tracing/TraceStack.cs b/src/Nethermind/Nethermind.Evm/Tracing/TraceStack.cs index 3759e3d5182..c9d84054066 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/TraceStack.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/TraceStack.cs @@ -23,12 +23,12 @@ public ReadOnlyMemory this[int index] public int Count => _stack.Length / EvmStack.WordSize; - public List ToHexWordList() + public string[] ToHexWordList() { - List hexWordList = new(Count); - for (int i = 0; i < Count; i += 1) + string[] hexWordList = new string[Count]; + for (int i = 0; i < hexWordList.Length; i += 1) { - hexWordList.Add(this[i].Span.ToHexString(true, true)); + hexWordList[i] = this[i].Span.ToHexString(true, true); } return hexWordList; diff --git a/src/Nethermind/Nethermind.Facade/Eth/SyncingResult.cs b/src/Nethermind/Nethermind.Facade/Eth/SyncingResult.cs index 0dde2929dc3..518eb44678a 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/SyncingResult.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/SyncingResult.cs @@ -1,14 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + using Nethermind.Synchronization.ParallelSync; namespace Nethermind.Facade.Eth { + [JsonConverter(typeof(SyncingResultJsonConverter))] public struct SyncingResult { public static SyncingResult NotSyncing = new(); - public bool IsSyncing { get; set; } public long StartingBlock { get; set; } public long CurrentBlock { get; set; } diff --git a/src/Nethermind/Nethermind.Facade/Eth/SyncingResultJsonConverter.cs b/src/Nethermind/Nethermind.Facade/Eth/SyncingResultJsonConverter.cs new file mode 100644 index 00000000000..b65b7e9fd81 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/SyncingResultJsonConverter.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Nethermind.Facade.Eth; + +public class SyncingResultJsonConverter : JsonConverter +{ + public override SyncingResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) => throw new NotSupportedException(); + + public override void Write( + Utf8JsonWriter writer, + SyncingResult value, + JsonSerializerOptions options) + { + if (!value.IsSyncing) + { + writer.WriteBooleanValue(false); + return; + } + + JsonSerializer.Serialize(writer, new Result + { + StartingBlock = value.StartingBlock, + CurrentBlock = value.CurrentBlock, + HighestBlock = value.HighestBlock + }, options); + } + + private struct Result + { + public long StartingBlock { get; set; } + public long CurrentBlock { get; set; } + public long HighestBlock { get; set; } + } +} diff --git a/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs b/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs index d79908c3634..fb7c0567339 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs @@ -78,7 +78,7 @@ private void OnNewPendingTransaction(object sender, TxPool.TxEventArgs e) int filterId = filter.Id; List transactions = _pendingTransactions.GetOrAdd(filterId, _ => new List()); transactions.Add(e.Transaction.Hash); - if (_logger.IsDebug) _logger.Debug($"Filter with id: '{filterId}' contains {transactions.Count} transactions."); + if (_logger.IsDebug) _logger.Debug($"Filter with id: {filterId} contains {transactions.Count} transactions."); } } @@ -92,7 +92,7 @@ private void OnRemovedPendingTransaction(object sender, TxPool.TxEventArgs e) int filterId = filter.Id; List transactions = _pendingTransactions.GetOrAdd(filterId, _ => new List()); transactions.Remove(e.Transaction.Hash); - if (_logger.IsDebug) _logger.Debug($"Filter with id: '{filterId}' contains {transactions.Count} transactions."); + if (_logger.IsDebug) _logger.Debug($"Filter with id: {filterId} contains {transactions.Count} transactions."); } } @@ -194,7 +194,7 @@ private void StoreBlock(BlockFilter filter, Block block) List blocks = _blockHashes.GetOrAdd(filter.Id, i => new List()); blocks.Add(block.Hash); - if (_logger.IsDebug) _logger.Debug($"Filter with id: '{filter.Id}' contains {blocks.Count} blocks."); + if (_logger.IsDebug) _logger.Debug($"Filter with id: {filter.Id} contains {blocks.Count} blocks."); } private void StoreLogs(LogFilter filter, TxReceipt txReceipt, ref long logIndex) @@ -220,7 +220,7 @@ private void StoreLogs(LogFilter filter, TxReceipt txReceipt, ref long logIndex) return; } - if (_logger.IsDebug) _logger.Debug($"Filter with id: '{filter.Id}' contains {logs.Count} logs."); + if (_logger.IsDebug) _logger.Debug($"Filter with id: {filter.Id} contains {logs.Count} logs."); } private FilterLog? CreateLog(LogFilter logFilter, TxReceipt txReceipt, LogEntry logEntry, long index, int transactionLogIndex) diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs index 173b8262a48..4ff23807849 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs @@ -60,19 +60,19 @@ BlockHeader FindHeader(BlockParameter blockParameter, string name, bool headLimi if (fromBlock.Number > toBlock.Number && toBlock.Number != 0) { - throw new ArgumentException($"'From' block '{fromBlock.Number}' is later than 'to' block '{toBlock.Number}'."); + throw new ArgumentException($"From block {fromBlock.Number} is later than to block {toBlock.Number}."); } cancellationToken.ThrowIfCancellationRequested(); if (fromBlock.Number != 0 && fromBlock.ReceiptsRoot != Keccak.EmptyTreeHash && !_receiptStorage.HasBlock(fromBlock.Number, fromBlock.Hash!)) { - throw new ResourceNotFoundException($"Receipt not available for 'From' block '{fromBlock.Number}'."); + throw new ResourceNotFoundException($"Receipt not available for From block {fromBlock.Number}."); } cancellationToken.ThrowIfCancellationRequested(); if (toBlock.Number != 0 && toBlock.ReceiptsRoot != Keccak.EmptyTreeHash && !_receiptStorage.HasBlock(toBlock.Number, toBlock.Hash!)) { - throw new ResourceNotFoundException($"Receipt not available for 'To' block '{toBlock.Number}'."); + throw new ResourceNotFoundException($"Receipt not available for To block {toBlock.Number}."); } cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Nethermind/Nethermind.Init/InitializeStateDb.cs b/src/Nethermind/Nethermind.Init/InitializeStateDb.cs index a82b57becc2..467ea34d444 100644 --- a/src/Nethermind/Nethermind.Init/InitializeStateDb.cs +++ b/src/Nethermind/Nethermind.Init/InitializeStateDb.cs @@ -187,10 +187,7 @@ public Task Execute(CancellationToken cancellationToken) private static void InitBlockTraceDumper() { - BlockTraceDumper.Converters.AddRange(EthereumJsonSerializer.CommonConverters); - BlockTraceDumper.Converters.AddRange(DebugModuleFactory.Converters); - BlockTraceDumper.Converters.AddRange(TraceModuleFactory.Converters); - BlockTraceDumper.Converters.Add(new TxReceiptConverter()); + EthereumJsonSerializer.AddConverter(new TxReceiptConverter()); } private static void InitializeFullPruning( diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index b4aaab22365..f660a4054a0 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -28,6 +28,7 @@ using Nethermind.Logging; using Nethermind.Network.Config; using Nethermind.JsonRpc.Modules.Rpc; +using Nethermind.Serialization.Json; namespace Nethermind.Init.Steps; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.FileConsensusDataSource.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.FileConsensusDataSource.cs index 62850d7f357..894b1591dae 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.FileConsensusDataSource.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.FileConsensusDataSource.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; + using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Test diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.JsonRpcDataSource.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.JsonRpcDataSource.cs index cc24a2f2260..44a1f59d5fa 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.JsonRpcDataSource.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.JsonRpcDataSource.cs @@ -4,72 +4,74 @@ using System; using System.Net.Http; using System.Text; +using System.Text.Json.Serialization; using System.Threading.Tasks; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; -namespace Nethermind.JsonRpc.Test + +namespace Nethermind.JsonRpc.Test; + +public partial class ConsensusHelperTests { - public partial class ConsensusHelperTests + private abstract class JsonRpcDataSource : IConsensusDataSource { - private abstract class JsonRpcDataSource : IConsensusDataSource - { - private readonly Uri _uri; - protected readonly IJsonSerializer _serializer; - private readonly HttpClient _httpClient; + private readonly Uri _uri; + protected readonly IJsonSerializer _serializer; + private readonly HttpClient _httpClient; - protected JsonRpcDataSource(Uri uri, IJsonSerializer serializer) - { - _uri = uri; - _serializer = serializer; - _httpClient = new HttpClient(); - } + protected JsonRpcDataSource(Uri uri, IJsonSerializer serializer) + { + _uri = uri; + _serializer = serializer; + _httpClient = new HttpClient(); + } - protected async Task SendRequest(JsonRpcRequest request) + protected async Task SendRequest(JsonRpcRequest request) + { + using HttpRequestMessage message = new(HttpMethod.Post, _uri) { - using HttpRequestMessage message = new(HttpMethod.Post, _uri) - { - Content = new StringContent(_serializer.Serialize(request), Encoding.UTF8, "application/json") - - }; - using HttpResponseMessage result = await _httpClient.SendAsync(message); - string content = await result.Content.ReadAsStringAsync(); - return content; - } + Content = new StringContent(_serializer.Serialize(request), Encoding.UTF8, "application/json") - protected JsonRpcRequestWithParams CreateRequest(string methodName, params object[] parameters) => - new() - { - Id = 1, - JsonRpc = "2.0", - Method = methodName, - Params = parameters - }; + }; + using HttpResponseMessage result = await _httpClient.SendAsync(message); + string content = await result.Content.ReadAsStringAsync(); + return content; + } - public void Dispose() + protected JsonRpcRequestWithParams CreateRequest(string methodName, params object[] parameters) => + new() { - _httpClient.Dispose(); - } + Id = 1, + JsonRpc = "2.0", + Method = methodName, + Params = parameters + }; - protected class JsonRpcSuccessResponse : JsonRpcSuccessResponse - { - [JsonProperty(PropertyName = "result", NullValueHandling = NullValueHandling.Include, Order = 1)] - public new T Result { get { return (T)base.Result!; } set { base.Result = value; } } - } + public void Dispose() + { + _httpClient?.Dispose(); + } - public virtual async Task<(T2, string)> GetData() - { - string jsonData = await GetJsonData(); - return (_serializer.Deserialize>(jsonData).Result!, jsonData); - } + protected class JsonRpcSuccessResponse : JsonRpcSuccessResponse + { + [JsonPropertyOrder(1)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public new T Result { get { return (T)base.Result!; } set { base.Result = value; } } + } - public abstract Task GetJsonData(); + public virtual async Task<(T2, string)> GetData() + { + string jsonData = await GetJsonData(); + return (_serializer.Deserialize>(jsonData).Result, jsonData); + } - public class JsonRpcRequestWithParams : JsonRpcRequest - { - [JsonProperty(Required = Required.Default)] - public new object[]? Params { get; set; } - } + public abstract Task GetJsonData(); + + public class JsonRpcRequestWithParams : JsonRpcRequest + { + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public new object[]? Params { get; set; } } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ParityLikeBlockTraceJsonRpcDataSource.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ParityLikeBlockTraceJsonRpcDataSource.cs index 59428d51f49..7e8c3e58362 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ParityLikeBlockTraceJsonRpcDataSource.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ParityLikeBlockTraceJsonRpcDataSource.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; + using Nethermind.Core.Extensions; using Nethermind.JsonRpc.Modules.Trace; using Nethermind.Serialization.Json; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.cs index 1ed6f47c7a5..cc9a940ec4f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.cs @@ -4,18 +4,22 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using System.Threading.Tasks; + using FluentAssertions; using FluentAssertions.Equivalency; using FluentAssertions.Json; + using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing.GethStyle; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.DebugModule; using Nethermind.JsonRpc.Modules.Trace; using Nethermind.Serialization.Json; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; + using NUnit.Framework; namespace Nethermind.JsonRpc.Test @@ -73,8 +77,8 @@ public async Task CompareReceipt(Uri uri1, Uri uri2, Hash256? blockHash = null) public async Task CompareGethBlockTrace(Uri uri1, Uri uri2, Hash256? blockHash = null, GethTraceOptions? gethTraceOptions = null) { gethTraceOptions ??= GethTraceOptions.Default; - using IConsensusDataSource> receipt1Source = GetSource>(uri1, DebugModuleFactory.Converters); - using IConsensusDataSource> receipt2Source = GetSource>(uri2, DebugModuleFactory.Converters); + using IConsensusDataSource> receipt1Source = GetSource>(uri1); + using IConsensusDataSource> receipt2Source = GetSource>(uri2); TrySetData(blockHash, receipt1Source, receipt2Source); TrySetData(gethTraceOptions, receipt1Source, receipt2Source); await CompareCollection(receipt1Source, receipt2Source, true); @@ -84,8 +88,8 @@ public async Task CompareGethBlockTrace(Uri uri1, Uri uri2, Hash256? blockHash = public async Task CompareGethTxTrace(Uri uri1, Uri uri2, Hash256? transactionHash = null, GethTraceOptions? gethTraceOptions = null) { gethTraceOptions ??= GethTraceOptions.Default; - using IConsensusDataSource receipt1Source = GetSource(uri1, DebugModuleFactory.Converters); - using IConsensusDataSource receipt2Source = GetSource(uri2, DebugModuleFactory.Converters); + using IConsensusDataSource receipt1Source = GetSource(uri1); + using IConsensusDataSource receipt2Source = GetSource(uri2); TrySetData(transactionHash, receipt1Source, receipt2Source); TrySetData(gethTraceOptions, receipt1Source, receipt2Source); await Compare(receipt1Source, receipt2Source, true); @@ -94,8 +98,8 @@ public async Task CompareGethTxTrace(Uri uri1, Uri uri2, Hash256? transactionHas [TestCaseSource(nameof(Tests))] public async Task CompareParityBlockTrace(Uri uri1, Uri uri2, long blockNumber) { - using IConsensusDataSource> receipt1Source = GetSource>(uri1, TraceModuleFactory.Converters); - using IConsensusDataSource> receipt2Source = GetSource>(uri2, TraceModuleFactory.Converters); + using IConsensusDataSource> receipt1Source = GetSource>(uri1); + using IConsensusDataSource> receipt2Source = GetSource>(uri2); TrySetData(blockNumber, receipt1Source, receipt2Source); await CompareCollection(receipt1Source, receipt2Source, true); } @@ -111,9 +115,9 @@ private void TrySetData(TData blockHash, params object[] sources) } } - private IConsensusDataSource GetSource(Uri uri, IEnumerable? additionalConverters = null) + private IConsensusDataSource GetSource(Uri uri) { - var serializer = GetSerializer(additionalConverters!); + var serializer = GetSerializer(); if (uri.IsFile) { return new FileConsensusDataSource(uri, serializer); @@ -146,13 +150,9 @@ private IConsensusDataSource GetSource(Uri uri, IEnumerable throw new NotSupportedException($"Uri: {uri} is not supported"); } - private IJsonSerializer GetSerializer(IEnumerable? additionalConverters) + private IJsonSerializer GetSerializer() { IJsonSerializer jsonSerializer = new EthereumJsonSerializer(); - if (additionalConverters is not null) - { - jsonSerializer.RegisterConverters(additionalConverters); - } return jsonSerializer; } @@ -165,10 +165,10 @@ private static async Task Compare(IConsensusDataSource source1, if (compareJson) { - JToken data = JsonHelper.ParseNormalize(await source1.GetJsonData()); - JToken expectation = JsonHelper.ParseNormalize(await source2.GetJsonData()); + JsonNode data = JsonHelper.ParseNormalize(await source1.GetJsonData()); + JsonNode expectation = JsonHelper.ParseNormalize(await source2.GetJsonData()); data.Should().BeEquivalentTo(expectation); - data["error"].Should().BeNull(data["error"]?.ToString()); + data["error"].Should().BeNull(data["error"]!.ToString()); } else { @@ -195,10 +195,10 @@ private static async Task CompareCollection(IConsensusDataSource public static class JsonHelper { - public static JToken ParseNormalize(string json) => Normalize(JToken.Parse(json)); + public static JsonNode ParseNormalize(string json) => Normalize(JsonNode.Parse(json)); - public static JToken Normalize(JToken token) + public static JsonNode Normalize(JsonNode? token) { - if (token.Type == JTokenType.Object) + if (token is JsonObject jObject) { - JObject copy = new(); - foreach (JProperty prop in token.Children()) + JsonObject copy = new JsonObject(); + foreach (var prop in jObject) { - JToken child = prop.Value; - if (child.HasValues) + JsonNode? child = prop.Value; + if (child is JsonObject || child is JsonArray) { child = Normalize(child); } if (!IsEmpty(child)) { - copy.Add(prop.Name, child); + copy.Add(prop.Key, child); } } return copy; } - else if (token.Type == JTokenType.Array) + else if (token is JsonArray jArray) { - JArray copy = new(); - foreach (JToken item in token.Children()) + JsonArray copy = new JsonArray(); + foreach (JsonNode? item in jArray) { - JToken child = item; - if (child.HasValues) + JsonNode? child = item; + if (child is JsonObject || child is JsonArray) { child = Normalize(child); } @@ -285,12 +285,12 @@ public static JToken Normalize(JToken token) } return copy; } - return token; + return token!; } - public static bool IsEmpty(JToken token) + public static bool IsEmpty(JsonNode? token) { - return (token.Type == JTokenType.Null); + return (token is null); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/BlockParameterConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/BlockParameterConverterTests.cs index 96c36b53469..e4c3b59d84d 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/BlockParameterConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/BlockParameterConverterTests.cs @@ -5,7 +5,8 @@ using Nethermind.Blockchain.Find; using Nethermind.Core.Test.Builders; using Nethermind.JsonRpc.Data; -using Newtonsoft.Json; +using Nethermind.Serialization.Json; + using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Data @@ -14,9 +15,6 @@ namespace Nethermind.JsonRpc.Test.Data [TestFixture] public class BlockParameterConverterTests : SerializationTestBase { - [TestCase("0x0", 0)] - [TestCase("0xA", 10)] - [TestCase("0xa", 10)] [TestCase("0", 0)] [TestCase("100", 100)] [TestCase("\"0x0\"", 0)] @@ -26,13 +24,9 @@ public class BlockParameterConverterTests : SerializationTestBase [TestCase("\"100\"", 100)] public void Can_read_block_number(string input, long output) { - using StringReader reader = new(input); - using JsonTextReader textReader = new(reader); + IJsonSerializer serializer = new EthereumJsonSerializer(); - JsonSerializer serializer = new(); - BlockParameterConverter converter = new(); - serializer.Converters.Add(converter); - BlockParameter blockParameter = serializer.Deserialize(textReader)!; + BlockParameter blockParameter = serializer.Deserialize(input)!; Assert.That(blockParameter.BlockNumber, Is.EqualTo(output)); } @@ -51,13 +45,9 @@ public void Can_read_block_number(string input, long output) [TestCase("\"Safe\"", BlockParameterType.Safe)] public void Can_read_type(string input, BlockParameterType output) { - using StringReader reader = new(input); - using JsonTextReader textReader = new(reader); + IJsonSerializer serializer = new EthereumJsonSerializer(); - JsonSerializer serializer = new(); - BlockParameterConverter converter = new(); - serializer.Converters.Add(converter); - BlockParameter blockParameter = serializer.Deserialize(textReader)!; + BlockParameter blockParameter = serializer.Deserialize(input)!; Assert.That(blockParameter.Type, Is.EqualTo(output)); } @@ -71,15 +61,11 @@ public void Can_write_type(string output, BlockParameterType input) { BlockParameter blockParameter = new(input); - using StringWriter reader = new(); - using JsonTextWriter textWriter = new(reader); + IJsonSerializer serializer = new EthereumJsonSerializer(); - JsonSerializer serializer = new(); - BlockParameterConverter converter = new(); - serializer.Converters.Add(converter); - serializer.Serialize(textWriter, blockParameter); + var result = serializer.Serialize(blockParameter); - Assert.That(reader.ToString(), Is.EqualTo(output)); + Assert.That(result, Is.EqualTo(output)); } [TestCase("\"0x0\"", 0)] @@ -88,15 +74,11 @@ public void Can_write_number(string output, long input) { BlockParameter blockParameter = new(input); - using StringWriter reader = new(); - using JsonTextWriter textWriter = new(reader); + IJsonSerializer serializer = new EthereumJsonSerializer(); - JsonSerializer serializer = new(); - BlockParameterConverter converter = new(); - serializer.Converters.Add(converter); - serializer.Serialize(textWriter, blockParameter); + var result = serializer.Serialize(blockParameter); - Assert.That(reader.ToString(), Is.EqualTo(output)); + Assert.That(result, Is.EqualTo(output)); } [Test] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs index e06bff69d9c..f447c6cd77b 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs @@ -4,10 +4,12 @@ using System; using System.IO; using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; using FluentAssertions; using Nethermind.Int256; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Data @@ -26,7 +28,7 @@ public void Can_do_roundtrip_big() public void Can_handle_int() { IdConverter converter = new(); - converter.WriteJson(new JsonTextWriter(new StringWriter()), 1, JsonSerializer.CreateDefault()); + converter.Write(new Utf8JsonWriter(new MemoryStream()), 1, null); } [Test] @@ -34,7 +36,7 @@ public void Throws_on_writing_decimal() { IdConverter converter = new(); Assert.Throws( - () => converter.WriteJson(new JsonTextWriter(new StringWriter()), 1.1, JsonSerializer.CreateDefault())); + () => converter.Write(new Utf8JsonWriter(new MemoryStream()), 1.1, null)); } [TestCase(typeof(int))] @@ -91,7 +93,7 @@ public void Decimal_not_supported() public class SomethingWithId { [JsonConverter(typeof(IdConverter))] - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public object Id { get; set; } = null!; public string Something { get; set; } = null!; @@ -100,7 +102,7 @@ public class SomethingWithId public class SomethingWithDecimalId { [JsonConverter(typeof(IdConverter))] - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public decimal Id { get; set; } public string Something { get; set; } = null!; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/SerializationTestBase.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/SerializationTestBase.cs index c35f26291a7..b59b99ffe04 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/SerializationTestBase.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/SerializationTestBase.cs @@ -2,11 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json.Serialization; + using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Modules.Trace; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Data @@ -16,10 +18,6 @@ public class SerializationTestBase protected void TestRoundtrip(T item, Func? equalityComparer, JsonConverter? converter = null, string? description = null) { IJsonSerializer serializer = BuildSerializer(); - if (converter is not null) - { - serializer.RegisterConverter(converter); - } string result = serializer.Serialize(item); T deserialized = serializer.Deserialize(result); @@ -52,10 +50,6 @@ protected void TestRoundtrip(T item, Func? equalityComparer, stri protected void TestRoundtrip(string json, JsonConverter? converter = null) { IJsonSerializer serializer = BuildSerializer(); - if (converter is not null) - { - serializer.RegisterConverter(converter); - } T deserialized = serializer.Deserialize(json); string result = serializer.Serialize(deserialized); @@ -65,13 +59,9 @@ protected void TestRoundtrip(string json, JsonConverter? converter = null) private void TestToJson(T item, JsonConverter? converter, string expectedResult) { IJsonSerializer serializer = BuildSerializer(); - if (converter is not null) - { - serializer.RegisterConverter(converter); - } string result = serializer.Serialize(item); - Assert.That(result, Is.EqualTo(expectedResult), result.Replace("\"", "\\\"")); + Assert.That(result, Is.EqualTo(expectedResult.Replace("+", "\\u002B")), result.Replace("\"", "\\\"")); } protected void TestToJson(T item, string expectedResult) @@ -82,9 +72,6 @@ protected void TestToJson(T item, string expectedResult) private static IJsonSerializer BuildSerializer() { IJsonSerializer serializer = new EthereumJsonSerializer(); - serializer.RegisterConverters(EthModuleFactory.Converters); - serializer.RegisterConverters(TraceModuleFactory.Converters); - serializer.RegisterConverter(new BlockParameterConverter()); return serializer; } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorExtensions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorExtensions.cs index 8b36f80bea4..b23f56d9dc3 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorExtensions.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorExtensions.cs @@ -1,13 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.IO; +using System.IO.Pipelines; +using System.Text; namespace Nethermind.JsonRpc.Test { public static class JsonRpcProcessorExtensions { - public static IAsyncEnumerable ProcessAsync(this IJsonRpcProcessor processor, string request, JsonRpcContext context) => processor.ProcessAsync(new StringReader(request), context); + public static IAsyncEnumerable ProcessAsync(this IJsonRpcProcessor processor, string request, JsonRpcContext context) => processor.ProcessAsync(PipeReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(request))), context); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index 4fe9d78469e..c812906602e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -13,372 +13,382 @@ using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.JsonRpc.Modules; -using Newtonsoft.Json; + using NSubstitute; using NUnit.Framework; -namespace Nethermind.JsonRpc.Test +namespace Nethermind.JsonRpc.Test; + +[Parallelizable(ParallelScope.All)] +[TestFixture(true)] +[TestFixture(false)] +public class JsonRpcProcessorTests { - [Parallelizable(ParallelScope.All)] - [TestFixture(true)] - [TestFixture(false)] - public class JsonRpcProcessorTests + private readonly bool _returnErrors; + private readonly JsonRpcErrorResponse _errorResponse = new(); + + public JsonRpcProcessorTests(bool returnErrors) { - private readonly bool _returnErrors; - private readonly JsonRpcErrorResponse _errorResponse = new(); + _returnErrors = returnErrors; + } - public JsonRpcProcessorTests(bool returnErrors) - { - _returnErrors = returnErrors; - } + private JsonRpcProcessor Initialize(JsonRpcConfig? config = null) + { + IJsonRpcService service = Substitute.For(); + service.SendRequestAsync(Arg.Any(), Arg.Any()).Returns(ci => _returnErrors ? new JsonRpcErrorResponse { Id = ci.Arg().Id } : new JsonRpcSuccessResponse { Id = ci.Arg().Id }); + service.GetErrorResponse(0, null!).ReturnsForAnyArgs(_errorResponse); + service.GetErrorResponse(0, null!, null!, null!).ReturnsForAnyArgs(_errorResponse); - private JsonRpcProcessor Initialize(JsonRpcConfig? config = null) - { - IJsonRpcService service = Substitute.For(); - service.SendRequestAsync(Arg.Any(), Arg.Any()).Returns(ci => _returnErrors ? new JsonRpcErrorResponse { Id = ci.Arg().Id } : new JsonRpcSuccessResponse { Id = ci.Arg().Id }); - service.GetErrorResponse(0, null!, null!, null!).ReturnsForAnyArgs(_errorResponse); - service.Converters.Returns(new JsonConverter[] { new AddressConverter() }); // just to test converter loader + IFileSystem fileSystem = Substitute.For(); - IFileSystem fileSystem = Substitute.For(); + /* we enable recorder always to have an easy smoke test for recording + * and this is fine because recorder is non-critical component + */ + config ??= new JsonRpcConfig(); + config.RpcRecorderState = RpcRecorderState.All; - /* we enable recorder always to have an easy smoke test for recording - * and this is fine because recorder is non-critical component - */ - config ??= new JsonRpcConfig(); - config.RpcRecorderState = RpcRecorderState.All; + return new JsonRpcProcessor(service, config, fileSystem, LimboLogs.Instance); + } - return new JsonRpcProcessor(service, new EthereumJsonSerializer(), config, fileSystem, LimboLogs.Instance); - } + [Test] + public async Task Can_process_guid_ids() + { + IList result = await ProcessAsync("{\"id\":\"840b55c4-18b0-431c-be1d-6d22198b53f2\",\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo("840b55c4-18b0-431c-be1d-6d22198b53f2")); + } - [Test] - public async Task Can_process_guid_ids() - { - IList result = await ProcessAsync("{\"id\":\"840b55c4-18b0-431c-be1d-6d22198b53f2\",\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo("840b55c4-18b0-431c-be1d-6d22198b53f2")); - } + private ValueTask> ProcessAsync(string request, JsonRpcContext? context = null, JsonRpcConfig? config = null) => + Initialize(config).ProcessAsync(request, context ?? new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); - private ValueTask> ProcessAsync(string request, JsonRpcContext? context = null, JsonRpcConfig? config = null) => - Initialize(config).ProcessAsync(request, context ?? new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + [Test] + public async Task Can_process_non_hex_ids() + { + IList result = await ProcessAsync("{\"id\":12345678901234567890,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo(decimal.Parse("12345678901234567890"))); + } - [Test] - public async Task Can_process_non_hex_ids() - { - IList result = await ProcessAsync("{\"id\":12345678901234567890,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo(BigInteger.Parse("12345678901234567890"))); - } + [Test] + public async Task Can_process_hex_ids() + { + IList result = await ProcessAsync("{\"id\":\"0xa1aa12434\",\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo("0xa1aa12434")); + } - [Test] - public async Task Can_process_hex_ids() - { - IList result = await ProcessAsync("{\"id\":\"0xa1aa12434\",\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo("0xa1aa12434")); - } + [Test] + public async Task Can_process_int() + { + IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo(67)); + } - [Test] - public async Task Can_process_int() + [Test] + public async Task Can_process_uppercase_params() + { + IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"Params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo(67)); + if (_returnErrors) { - IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo(67)); + result[0].Response.Should().BeOfType(); } - - [Test] - public async Task Can_process_uppercase_params() + else { - IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"Params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo(67)); - if (_returnErrors) - { - result[0].Response.Should().BeOfType(); - } - else - { - result[0].Response.Should().BeOfType(); - } + result[0].Response.Should().BeOfType(); } + } - [Test] - public async Task Can_process_long_ids() - { - IList result = await ProcessAsync("{\"id\":9223372036854775807,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo(long.MaxValue)); - } + [Test] + public async Task Can_process_long_ids() + { + IList result = await ProcessAsync("{\"id\":9223372036854775807,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo(long.MaxValue)); + } - [Test] - public async Task Can_process_special_characters_in_ids() - { - IList result = await ProcessAsync("{\"id\":\";\\\\\\\"\",\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo(";\\\"")); - } + [Test] + public async Task Can_process_special_characters_in_ids() + { + IList result = await ProcessAsync("{\"id\":\";\\\\\\\"\",\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo(";\\\"")); + } - [Test] - public async Task Can_process_null_in_ids() - { - IList result = await ProcessAsync("{\"id\":null,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(1); - Assert.That(result[0].Response!.Id, Is.EqualTo(null)); - } + [Test] + public async Task Can_process_null_in_ids() + { + IList result = await ProcessAsync("{\"id\":null,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(1); + Assert.That(result[0].Response!.Id, Is.EqualTo(null)); + } - [Test] - public async Task Can_process_batch_request_with_nested_object_params() + [Test] + public async Task Can_process_batch_request_with_nested_object_params() + { + IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}]}]"); + result.Should().HaveCount(1); + result[0].BatchedResponses.Should().NotBeNull(); + if (_returnErrors) { - IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}]}]"); - result.Should().HaveCount(1); - result[0].BatchedResponses.Should().NotBeNull(); - if (_returnErrors) - { - (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); - } - else - { - (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); - } + (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); } - - [Test] - public async Task Can_process_batch_request_with_nested_array_params() + else { - IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}]]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}, 1]]}]"); - result.Should().HaveCount(1); - result[0].BatchedResponses.Should().NotBeNull(); - if (_returnErrors) - { - (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); - } - else - { - (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); - } + (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); } + } - [Test] - public async Task Can_process_batch_request_with_object_params() + [Test] + public async Task Can_process_batch_request_with_nested_array_params() + { + IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}]]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[[{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"b\":\"0x668c24\"}, 1]]}]"); + result.Should().HaveCount(1); + result[0].BatchedResponses.Should().NotBeNull(); + if (_returnErrors) { - IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"}},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"}}]"); - result.Should().HaveCount(1); - result[0].Response.Should().NotBeNull(); - result[0].Response.Should().BeOfType(); + (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); } - - [Test] - public async Task Can_process_batch_request_with_value_params() + else { - IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\"},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":\"0x668c24\"}]"); - result.Should().HaveCount(1); - result[0].Response.Should().NotBeNull(); - result[0].Response.Should().BeOfType(); + (await result[0].BatchedResponses!.Select(r => r.Response).ToListAsync()).Should().AllBeOfType(); } + } - [Test] - public async Task Can_process_batch_request() - { - IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}]"); - result.Should().HaveCount(1); - result[0].BatchedResponses.Should().NotBeNull(); - result[0].Response.Should().BeNull(); - } + [Test] + public async Task Can_process_batch_request_with_object_params() + { + IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"}},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":{\"a\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"}}]"); + result.Should().HaveCount(1); + result[0].Response.Should().NotBeNull(); + result[0].Response.Should().BeOfType(); + } - [Test] - public async Task Can_process_batch_request_with_some_params_missing() - { - IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\"}]"); - result.Should().HaveCount(1); - result[0].BatchedResponses.Should().NotBeNull(); - result[0].Response.Should().BeNull(); - } + [Test] + public async Task Can_process_batch_request_with_value_params() + { + IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\"},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":\"0x668c24\"}]"); + result.Should().HaveCount(1); + result[0].Response.Should().BeNull(); + result[0].BatchedResponses.Should().NotBeNull(); + var resultList = await result[0].BatchedResponses!.ToListAsync(); + resultList.Should().HaveCount(2); + Assert.IsTrue(resultList.All(r => r.Response != _errorResponse)); + } - [Test] - public async Task Can_process_batch_request_with_two_requests() - { - IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}{\"id\":68,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - result.Should().HaveCount(2); - result[0].Response.Should().NotBeNull(); - result[0].BatchedResponses.Should().BeNull(); - result[0].Response.Should().NotBeSameAs(_errorResponse); - result[1].Response.Should().NotBeNull(); - result[1].BatchedResponses.Should().BeNull(); - result[1].Response.Should().NotBeSameAs(_errorResponse); - } + [Test] + public async Task Can_process_batch_request() + { + IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}]"); + result.Should().HaveCount(1); + result[0].BatchedResponses.Should().NotBeNull(); + result[0].Response.Should().BeNull(); + } - [Test] - public async Task Can_process_batch_request_with_single_request_and_array_with_two() - { - IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}]"); - result.Should().HaveCount(2); - result[0].Response.Should().NotBeNull(); - result[0].Response.Should().NotBeSameAs(_errorResponse); - result[0].BatchedResponses.Should().BeNull(); - result[1].Response.Should().BeNull(); - result[1].BatchedResponses.Should().NotBeNull(); - List resultList = await result[1].BatchedResponses!.ToListAsync(); - resultList.Should().HaveCount(2); - Assert.IsTrue(resultList.All(r => r.Response != _errorResponse)); - } + [Test] + public async Task Can_process_batch_request_with_some_params_missing() + { + IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\"}]"); + result.Should().HaveCount(1); + result[0].BatchedResponses.Should().NotBeNull(); + result[0].Response.Should().BeNull(); + } - [Test] - public async Task Can_process_batch_request_with_second_not_closed_request() - { - IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}{\"id\":68,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]"); - result.Should().HaveCount(2); - result[0].Response.Should().NotBeNull(); - result[0].BatchedResponses.Should().BeNull(); - result[0].Response.Should().NotBeSameAs(_errorResponse); - result[1].Response.Should().NotBeNull(); - result[1].BatchedResponses.Should().BeNull(); - result[1].Response.Should().BeSameAs(_errorResponse); - } + [Test] + public async Task Can_process_batch_request_with_two_requests() + { + IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}{\"id\":68,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); + result.Should().HaveCount(2); + result[0].Response.Should().NotBeNull(); + result[0].BatchedResponses.Should().BeNull(); + result[0].Response.Should().NotBeSameAs(_errorResponse); + result[1].Response.Should().NotBeNull(); + result[1].BatchedResponses.Should().BeNull(); + result[1].Response.Should().NotBeSameAs(_errorResponse); + } - [Test] - public async Task Can_process_batch_request_with_single_request_and_incorrect() - { - IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}{aaa}"); - result.Should().HaveCount(2); - result[0].Response.Should().NotBeNull(); - result[0].BatchedResponses.Should().BeNull(); - result[1].Response.Should().BeSameAs(_errorResponse); - result[1].BatchedResponses.Should().BeNull(); - } + [Test] + public async Task Can_process_batch_request_with_single_request_and_array_with_two() + { + IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}]"); + result.Should().HaveCount(2); + result[0].Response.Should().NotBeNull(); + result[0].Response.Should().NotBeSameAs(_errorResponse); + result[0].BatchedResponses.Should().BeNull(); + result[1].Response.Should().BeNull(); + result[1].BatchedResponses.Should().NotBeNull(); + List resultList = await result[1].BatchedResponses!.ToListAsync(); + resultList.Should().HaveCount(2); + Assert.IsTrue(resultList.All(r => r.Response != _errorResponse)); + } - [Test] - public async Task Will_return_error_when_batch_request_is_too_large() - { - StringBuilder request = new(); - int maxBatchSize = new JsonRpcConfig().MaxBatchSize; - request.Append("["); - for (int i = 0; i < maxBatchSize + 1; i++) - { - if (i != 0) request.Append(","); - request.Append( - "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - } - request.Append("]"); - - IList result = await ProcessAsync(request.ToString()); - result.Should().HaveCount(1); - result[0].Response.Should().BeAssignableTo(); - } + [Test] + public async Task Can_process_batch_request_with_second_not_closed_request() + { + IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}{\"id\":68,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]"); + result.Should().HaveCount(2); + result[0].Response.Should().NotBeNull(); + result[0].BatchedResponses.Should().BeNull(); + result[0].Response.Should().NotBeSameAs(_errorResponse); + result[1].Response.Should().NotBeNull(); + result[1].BatchedResponses.Should().BeNull(); + result[1].Response.Should().BeSameAs(_errorResponse); + } - [Test] - public async Task Will_not_return_error_when_batch_request_is_too_large_but_endpoint_is_authenticated() - { - StringBuilder request = new(); - int maxBatchSize = new JsonRpcConfig().MaxBatchSize; - request.Append("["); - for (int i = 0; i < maxBatchSize + 1; i++) - { - if (i != 0) request.Append(","); - request.Append( - "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); - } - request.Append("]"); - - JsonRpcUrl url = new(string.Empty, string.Empty, 0, RpcEndpoint.Http, true, Array.Empty()); - JsonRpcContext context = new(RpcEndpoint.Http, url: url); - IList result = await ProcessAsync(request.ToString(), context, new JsonRpcConfig() { MaxBatchResponseBodySize = 1 }); - result.Should().HaveCount(1); - List batchedResults = await result[0].BatchedResponses!.ToListAsync(); - batchedResults.Should().HaveCount(maxBatchSize + 1); - batchedResults.Should().AllSatisfy(rpcResult => - rpcResult.Response.Should().BeOfType(_returnErrors ? typeof(JsonRpcErrorResponse) : typeof(JsonRpcSuccessResponse)) - ); - } + [Test] + public async Task Can_process_batch_request_with_single_request_and_incorrect() + { + IList result = await ProcessAsync("{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}{aaa}"); + result.Should().HaveCount(2); + result[0].Response.Should().NotBeNull(); + result[0].BatchedResponses.Should().BeNull(); + result[1].Response.Should().BeSameAs(_errorResponse); + result[1].BatchedResponses.Should().BeNull(); + } - [Test] - public async Task Can_process_batch_request_with_result_limit([Values(false, true)] bool limit) + [Test] + public async Task Will_return_error_when_batch_request_is_too_large() + { + StringBuilder request = new(); + int maxBatchSize = new JsonRpcConfig().MaxBatchSize; + request.Append("["); + for (int i = 0; i < maxBatchSize + 1; i++) { - IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":68,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}]"); - result[0].IsCollection.Should().BeTrue(); - result[0].BatchedResponses.Should().NotBeNull(); - JsonRpcBatchResultAsyncEnumerator enumerator = result[0].BatchedResponses!.GetAsyncEnumerator(CancellationToken.None); - (await enumerator.MoveNextAsync()).Should().BeTrue(); - if (_returnErrors) - { - enumerator.Current.Response.Should().BeOfType(); - } - else - { - enumerator.Current.Response.Should().NotBeOfType(); - } - - enumerator.IsStopped = limit; // limiting - (await enumerator.MoveNextAsync()).Should().BeTrue(); - if (limit || _returnErrors) - { - enumerator.Current.Response.Should().BeOfType(); - } - else - { - enumerator.Current.Response.Should().NotBeOfType(); - } - - (await enumerator.MoveNextAsync()).Should().BeFalse(); + if (i != 0) request.Append(","); + request.Append( + "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); } + request.Append("]"); - [Test] - public async Task Can_handle_invalid_request() - { - IList result = await ProcessAsync("invalid"); - result.Should().HaveCount(1); - result[0].Response.Should().BeSameAs(_errorResponse); - } + IList result = await ProcessAsync(request.ToString()); + result.Should().HaveCount(1); + result[0].Response.Should().BeAssignableTo(); + } - [Test] - public async Task Can_handle_empty_array_request() + [Test] + public async Task Will_not_return_error_when_batch_request_is_too_large_but_endpoint_is_authenticated() + { + StringBuilder request = new(); + int maxBatchSize = new JsonRpcConfig().MaxBatchSize; + request.Append("["); + for (int i = 0; i < maxBatchSize + 1; i++) { - IList result = await ProcessAsync("[]"); - result.Should().HaveCount(1); - result[0].Response.Should().BeNull(); - result[0].BatchedResponses.Should().NotBeNull(); - Assert.IsTrue((await result[0].BatchedResponses!.ToListAsync()).All(r => r.Response != _errorResponse)); + if (i != 0) request.Append(","); + request.Append( + "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"); } + request.Append("]"); + + JsonRpcUrl url = new(string.Empty, string.Empty, 0, RpcEndpoint.Http, true, Array.Empty()); + JsonRpcContext context = new(RpcEndpoint.Http, url: url); + IList result = await ProcessAsync(request.ToString(), context, new JsonRpcConfig() { MaxBatchResponseBodySize = 1 }); + result.Should().HaveCount(1); + List batchedResults = await result[0].BatchedResponses!.ToListAsync(); + batchedResults.Should().HaveCount(maxBatchSize + 1); + batchedResults.Should().AllSatisfy(rpcResult => + rpcResult.Response.Should().BeOfType(_returnErrors ? typeof(JsonRpcErrorResponse) : typeof(JsonRpcSuccessResponse)) + ); + } - [Test] - public async Task Can_handle_empty_object_request() + [Test] + public async Task Can_process_batch_request_with_result_limit([Values(false, true)] bool limit) + { + IList result = await ProcessAsync("[{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]},{\"id\":68,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}]"); + result[0].IsCollection.Should().BeTrue(); + result[0].BatchedResponses.Should().NotBeNull(); + JsonRpcBatchResultAsyncEnumerator enumerator = result[0].BatchedResponses!.GetAsyncEnumerator(CancellationToken.None); + (await enumerator.MoveNextAsync()).Should().BeTrue(); + if (_returnErrors) { - IList result = await ProcessAsync("{}"); - result.Should().HaveCount(1); - result[0].Response.Should().NotBeNull(); - result[0].BatchedResponses.Should().BeNull(); - result[0].Response.Should().NotBeSameAs(_errorResponse); + enumerator.Current.Response.Should().BeOfType(); } - - [Test] - public async Task Can_handle_array_of_empty_requests() + else { - IList result = await ProcessAsync("[{},{},{}]"); - result.Should().HaveCount(1); - result[0].Response.Should().BeNull(); - result[0].BatchedResponses.Should().NotBeNull(); - IList resultList = (await result[0].BatchedResponses!.ToListAsync()); - resultList.Should().HaveCount(3); - Assert.IsTrue(resultList.All(r => r.Response != _errorResponse)); + enumerator.Current.Response.Should().NotBeOfType(); } - [Test] - public async Task Can_handle_value_request() + enumerator.IsStopped = limit; // limiting + (await enumerator.MoveNextAsync()).Should().BeTrue(); + if (limit || _returnErrors) { - IList result = await ProcessAsync("\"aaa\""); - result.Should().HaveCount(1); - result[0].Response.Should().NotBeNull(); - result[0].BatchedResponses.Should().BeNull(); - result[0].Response.Should().BeSameAs(_errorResponse); + enumerator.Current.Response.Should().BeOfType(); } - - [Test] - public async Task Can_handle_null_request() + else { - IList result = await ProcessAsync("null"); - result.Should().HaveCount(1); - result[0].Response.Should().NotBeNull(); - result[0].BatchedResponses.Should().BeNull(); - result[0].Response.Should().BeSameAs(_errorResponse); + enumerator.Current.Response.Should().NotBeOfType(); } + + (await enumerator.MoveNextAsync()).Should().BeFalse(); + } + + [Test] + public async Task Can_handle_invalid_request() + { + IList result = await ProcessAsync("invalid"); + result.Should().HaveCount(1); + result[0].Response.Should().BeSameAs(_errorResponse); + } + + [Test] + public async Task Can_handle_empty_array_request() + { + IList result = await ProcessAsync("[]"); + result.Should().HaveCount(1); + result[0].Response.Should().BeNull(); + result[0].BatchedResponses.Should().NotBeNull(); + Assert.IsTrue((await result[0].BatchedResponses!.ToListAsync()).All(r => r.Response != _errorResponse)); + } + + [Test] + public async Task Can_handle_empty_object_request() + { + IList result = await ProcessAsync("{}"); + result.Should().HaveCount(1); + result[0].Response.Should().NotBeNull(); + result[0].BatchedResponses.Should().BeNull(); + result[0].Response.Should().NotBeSameAs(_errorResponse); + } + + [Test] + public async Task Can_handle_array_of_empty_requests() + { + IList result = await ProcessAsync("[{},{},{}]"); + result.Should().HaveCount(1); + result[0].Response.Should().BeNull(); + result[0].BatchedResponses.Should().NotBeNull(); + IList resultList = (await result[0].BatchedResponses!.ToListAsync()); + resultList.Should().HaveCount(3); + Assert.IsTrue(resultList.All(r => r.Response != _errorResponse)); + } + + [Test] + public async Task Can_handle_value_request() + { + IList result = await ProcessAsync("\"aaa\""); + result.Should().HaveCount(1); + result[0].Response.Should().NotBeNull(); + result[0].BatchedResponses.Should().BeNull(); + result[0].Response.Should().BeSameAs(_errorResponse); + } + + [Test] + public async Task Can_handle_null_request() + { + IList result = await ProcessAsync("null"); + result.Should().HaveCount(1); + result[0].Response.Should().NotBeNull(); + result[0].BatchedResponses.Should().BeNull(); + result[0].Response.Should().BeSameAs(_errorResponse); + } + + [Test] + public void Cannot_accept_null_file_system() + { + Assert.Throws(() => new JsonRpcProcessor(Substitute.For(), + Substitute.For(), + null!, LimboLogs.Instance)); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs index e344fea0cb6..91583f619e4 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; +using System.Reflection; +using System.Text.Json; + using FluentAssertions; using Nethermind.Blockchain.Find; using Nethermind.Config; @@ -21,208 +24,208 @@ using Nethermind.JsonRpc.Modules.Web3; using Nethermind.Logging; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + using NSubstitute; using NUnit.Framework; -namespace Nethermind.JsonRpc.Test +namespace Nethermind.JsonRpc.Test; + +[Parallelizable(ParallelScope.Self)] +[TestFixture] +public class JsonRpcServiceTests { - [Parallelizable(ParallelScope.Self)] - [TestFixture] - public class JsonRpcServiceTests + [SetUp] + public void Initialize() { - [SetUp] - public void Initialize() - { - _configurationProvider = new ConfigProvider(); - _logManager = LimboLogs.Instance; - _context = new JsonRpcContext(RpcEndpoint.Http); - } + Assembly jConfig = typeof(JsonRpcConfig).Assembly; + _configurationProvider = new ConfigProvider(); + _logManager = LimboLogs.Instance; + _context = new JsonRpcContext(RpcEndpoint.Http); + } - private IJsonRpcService _jsonRpcService = null!; - private IConfigProvider _configurationProvider = null!; - private ILogManager _logManager = null!; - private JsonRpcContext _context = null!; + private IJsonRpcService _jsonRpcService = null!; + private IConfigProvider _configurationProvider = null!; + private ILogManager _logManager = null!; + private JsonRpcContext _context = null!; - private JsonRpcResponse TestRequest(T module, string method, params string[] parameters) where T : IRpcModule - { - RpcModuleProvider moduleProvider = new(new FileSystem(), _configurationProvider.GetConfig(), LimboLogs.Instance); - moduleProvider.Register(new SingletonModulePool(new SingletonFactory(module), allowExclusive: true)); - _jsonRpcService = new JsonRpcService(moduleProvider, _logManager, _configurationProvider.GetConfig()); - JsonRpcRequest request = RpcTest.GetJsonRequest(method, parameters); - JsonRpcResponse response = _jsonRpcService.SendRequestAsync(request, _context).Result; - Assert.That(response.Id, Is.EqualTo(request.Id)); - return response; - } - - [Test] - public void GetBlockByNumberTest() - { - IEthRpcModule ethRpcModule = Substitute.For(); - ISpecProvider specProvider = Substitute.For(); - ethRpcModule.eth_getBlockByNumber(Arg.Any(), true).ReturnsForAnyArgs(_ => ResultWrapper.Success(new BlockForRpc(Build.A.Block.WithNumber(2).TestObject, true, specProvider))); - JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_getBlockByNumber", "0x1b4", "true") as JsonRpcSuccessResponse; - Assert.That((response?.Result as BlockForRpc)?.Number, Is.EqualTo(2L)); - } - - [Test] - public void Eth_module_populates_size_when_returning_block_data() - { - IEthRpcModule ethRpcModule = Substitute.For(); - ISpecProvider specProvider = Substitute.For(); - ethRpcModule.eth_getBlockByNumber(Arg.Any(), true).ReturnsForAnyArgs(_ => ResultWrapper.Success(new BlockForRpc(Build.A.Block.WithNumber(2).TestObject, true, specProvider))); - JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_getBlockByNumber", "0x1b4", "true") as JsonRpcSuccessResponse; - Assert.That((response?.Result as BlockForRpc)?.Size, Is.EqualTo(513L)); - } - - [Test] - public void CanHandleOptionalArguments() - { - EthereumJsonSerializer serializer = new(); - string serialized = serializer.Serialize(new TransactionForRpc()); - IEthRpcModule ethRpcModule = Substitute.For(); - ethRpcModule.eth_call(Arg.Any()).ReturnsForAnyArgs(_ => ResultWrapper.Success("0x1")); - JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_call", serialized) as JsonRpcSuccessResponse; - Assert.That(response?.Result, Is.EqualTo("0x1")); - } - - [Test] - public void Case_sensitivity_test() - { - IEthRpcModule ethRpcModule = Substitute.For(); - ethRpcModule.eth_chainId().ReturnsForAnyArgs(ResultWrapper.Success(1ul)); - TestRequest(ethRpcModule, "eth_chainID").Should().BeOfType(); - TestRequest(ethRpcModule, "eth_chainId").Should().BeOfType(); - } - - [Test] - public void GetNewFilterTest() - { - IEthRpcModule ethRpcModule = Substitute.For(); - ethRpcModule.eth_newFilter(Arg.Any()).ReturnsForAnyArgs(_ => ResultWrapper.Success(1)); + private JsonRpcResponse TestRequest(T module, string method, params string[] parameters) where T : IRpcModule + { + RpcModuleProvider moduleProvider = new(new FileSystem(), _configurationProvider.GetConfig(), LimboLogs.Instance); + moduleProvider.Register(new SingletonModulePool(new SingletonFactory(module), true)); + _jsonRpcService = new JsonRpcService(moduleProvider, _logManager, _configurationProvider.GetConfig()); + JsonRpcRequest request = RpcTest.GetJsonRequest(method, parameters); + JsonRpcResponse response = _jsonRpcService.SendRequestAsync(request, _context).Result; + Assert.That(response.Id, Is.EqualTo(request.Id)); + return response; + } - var parameters = new + [Test] + public void GetBlockByNumberTest() + { + IEthRpcModule ethRpcModule = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + ethRpcModule.eth_getBlockByNumber(Arg.Any(), true).ReturnsForAnyArgs(x => ResultWrapper.Success(new BlockForRpc(Build.A.Block.WithNumber(2).TestObject, true, specProvider))); + JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_getBlockByNumber", "0x1b4", "true") as JsonRpcSuccessResponse; + Assert.That((response?.Result as BlockForRpc)?.Number, Is.EqualTo(2L)); + } + + [Test] + public void Eth_module_populates_size_when_returning_block_data() + { + IEthRpcModule ethRpcModule = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + ethRpcModule.eth_getBlockByNumber(Arg.Any(), true).ReturnsForAnyArgs(x => ResultWrapper.Success(new BlockForRpc(Build.A.Block.WithNumber(2).TestObject, true, specProvider))); + JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_getBlockByNumber", "0x1b4", "true") as JsonRpcSuccessResponse; + Assert.That((response?.Result as BlockForRpc)?.Size, Is.EqualTo(513L)); + } + + [Test] + public void CanHandleOptionalArguments() + { + EthereumJsonSerializer serializer = new(); + string serialized = serializer.Serialize(new TransactionForRpc()); + IEthRpcModule ethRpcModule = Substitute.For(); + ethRpcModule.eth_call(Arg.Any()).ReturnsForAnyArgs(x => ResultWrapper.Success("0x1")); + JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_call", serialized) as JsonRpcSuccessResponse; + Assert.That(response?.Result, Is.EqualTo("0x1")); + } + + [Test] + public void Case_sensitivity_test() + { + IEthRpcModule ethRpcModule = Substitute.For(); + ethRpcModule.eth_chainId().ReturnsForAnyArgs(ResultWrapper.Success(1ul)); + TestRequest(ethRpcModule, "eth_chainID").Should().BeOfType(); + TestRequest(ethRpcModule, "eth_chainId").Should().BeOfType(); + } + + [Test] + public void GetNewFilterTest() + { + IEthRpcModule ethRpcModule = Substitute.For(); + ethRpcModule.eth_newFilter(Arg.Any()).ReturnsForAnyArgs(x => ResultWrapper.Success(1)); + + var parameters = new + { + fromBlock = "0x1", + toBlock = "latest", + address = "0x1f88f1f195afa192cfee860698584c030f4c9db2", + topics = new List { - fromBlock = "0x1", - toBlock = "latest", - address = "0x1f88f1f195afa192cfee860698584c030f4c9db2", - topics = new List + "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", null!, + new[] { - "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", null!, - new[] - { - "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "0x0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc" - } + "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "0x0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc" } - }; + } + }; - JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_newFilter", JsonConvert.SerializeObject(parameters)) as JsonRpcSuccessResponse; - Assert.That(response?.Result, Is.EqualTo(UInt256.One)); - } + JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_newFilter", JsonSerializer.Serialize(parameters)) as JsonRpcSuccessResponse; + Assert.That(response?.Result, Is.EqualTo(UInt256.One)); + } - [Test] - public void Eth_call_is_working_with_implicit_null_as_the_last_argument() - { - EthereumJsonSerializer serializer = new(); - IEthRpcModule ethRpcModule = Substitute.For(); - ethRpcModule.eth_call(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(_ => ResultWrapper.Success("0x")); + [Test] + public void Eth_call_is_working_with_implicit_null_as_the_last_argument() + { + EthereumJsonSerializer serializer = new(); + IEthRpcModule ethRpcModule = Substitute.For(); + ethRpcModule.eth_call(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(x => ResultWrapper.Success("0x")); - string serialized = serializer.Serialize(new TransactionForRpc()); + string serialized = serializer.Serialize(new TransactionForRpc()); - JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_call", serialized) as JsonRpcSuccessResponse; - Assert.That(response?.Result, Is.EqualTo("0x")); - } + JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_call", serialized) as JsonRpcSuccessResponse; + Assert.That(response?.Result, Is.EqualTo("0x")); + } - [TestCase("")] - [TestCase(null)] - public void Eth_call_is_working_with_explicit_null_as_the_last_argument(string? nullValue) - { - EthereumJsonSerializer serializer = new(); - IEthRpcModule ethRpcModule = Substitute.For(); - ethRpcModule.eth_call(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(_ => ResultWrapper.Success("0x")); + [TestCase("")] + [TestCase(null)] + public void Eth_call_is_working_with_explicit_null_as_the_last_argument(string? nullValue) + { + EthereumJsonSerializer serializer = new(); + IEthRpcModule ethRpcModule = Substitute.For(); + ethRpcModule.eth_call(Arg.Any(), Arg.Any()).ReturnsForAnyArgs(x => ResultWrapper.Success("0x")); - string serialized = serializer.Serialize(new TransactionForRpc()); + string serialized = serializer.Serialize(new TransactionForRpc()); - JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_call", serialized, nullValue!) as JsonRpcSuccessResponse; - Assert.That(response?.Result, Is.EqualTo("0x")); - } + JsonRpcSuccessResponse? response = TestRequest(ethRpcModule, "eth_call", serialized, nullValue!) as JsonRpcSuccessResponse; + Assert.That(response?.Result, Is.EqualTo("0x")); + } - [Test] - public void Eth_getTransactionReceipt_properly_fails_given_wrong_parameters() - { - IEthRpcModule ethRpcModule = Substitute.For(); + [Test] + public void Eth_getTransactionReceipt_properly_fails_given_wrong_parameters() + { + IEthRpcModule ethRpcModule = Substitute.For(); - string[] parameters = { + string[] parameters = { """["0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71"]""" }; - JsonRpcResponse response = TestRequest(ethRpcModule, "eth_getTransactionReceipt", parameters); - - response.Should() - .BeAssignableTo() - .Which - .Error.Should().NotBeNull(); - Error error = (response as JsonRpcErrorResponse)!.Error!; - error.Code.Should().Be(ErrorCodes.InvalidParams); - } - - [Test] - public void IncorrectMethodNameTest() - { - JsonRpcErrorResponse? response = TestRequest(Substitute.For(), "incorrect_method") as JsonRpcErrorResponse; - Assert.That(response?.Error?.Code, Is.EqualTo(ErrorCodes.MethodNotFound)); - } + JsonRpcResponse response = TestRequest(ethRpcModule, "eth_getTransactionReceipt", parameters); + + response.Should() + .BeAssignableTo() + .Which + .Error.Should().NotBeNull(); + Error error = (response as JsonRpcErrorResponse)!.Error!; + error.Code.Should().Be(ErrorCodes.InvalidParams); + } - [Test] - public void NetVersionTest() - { - INetRpcModule netRpcModule = Substitute.For(); - netRpcModule.net_version().ReturnsForAnyArgs(_ => ResultWrapper.Success("1")); - JsonRpcSuccessResponse? response = TestRequest(netRpcModule, "net_version") as JsonRpcSuccessResponse; - Assert.That(response?.Result, Is.EqualTo("1")); - Assert.IsNotInstanceOf(response); - } - - [Test] - public void Web3ShaTest() - { - IWeb3RpcModule web3RpcModule = Substitute.For(); - web3RpcModule.web3_sha3(Arg.Any()).ReturnsForAnyArgs(_ => ResultWrapper.Success(TestItem.KeccakA)); - JsonRpcSuccessResponse? response = TestRequest(web3RpcModule, "web3_sha3", "0x68656c6c6f20776f726c64") as JsonRpcSuccessResponse; - Assert.That(response?.Result, Is.EqualTo(TestItem.KeccakA)); - } - - [TestCaseSource(nameof(BlockForRpcTestSource))] - public void BlockForRpc_should_expose_withdrawals_if_any((bool Expected, Block Block) item) - { - ISpecProvider? specProvider = Substitute.For(); - BlockForRpc rpcBlock = new(item.Block, false, specProvider); + [Test] + public void IncorrectMethodNameTest() + { + JsonRpcErrorResponse? response = TestRequest(Substitute.For(), "incorrect_method") as JsonRpcErrorResponse; + Assert.That(response?.Error?.Code, Is.EqualTo(ErrorCodes.MethodNotFound)); + } + + [Test] + public void NetVersionTest() + { + INetRpcModule netRpcModule = Substitute.For(); + netRpcModule.net_version().ReturnsForAnyArgs(x => ResultWrapper.Success("1")); + JsonRpcSuccessResponse? response = TestRequest(netRpcModule, "net_version") as JsonRpcSuccessResponse; + Assert.That(response?.Result, Is.EqualTo("1")); + Assert.IsNotInstanceOf(response); + } + + [Test] + public void Web3ShaTest() + { + IWeb3RpcModule web3RpcModule = Substitute.For(); + web3RpcModule.web3_sha3(Arg.Any()).ReturnsForAnyArgs(_ => ResultWrapper.Success(TestItem.KeccakA)); + JsonRpcSuccessResponse? response = TestRequest(web3RpcModule, "web3_sha3", "0x68656c6c6f20776f726c64") as JsonRpcSuccessResponse; + Assert.That(response?.Result, Is.EqualTo(TestItem.KeccakA)); + } - rpcBlock.WithdrawalsRoot.Should().BeEquivalentTo(item.Block.WithdrawalsRoot); - rpcBlock.Withdrawals.Should().BeEquivalentTo(item.Block.Withdrawals); + [TestCaseSource(nameof(BlockForRpcTestSource))] + public void BlockForRpc_should_expose_withdrawals_if_any((bool Expected, Block Block) item) + { + var specProvider = Substitute.For(); + var rpcBlock = new BlockForRpc(item.Block, false, specProvider); - string? json = new EthereumJsonSerializer().Serialize(rpcBlock); + rpcBlock.WithdrawalsRoot.Should().BeEquivalentTo(item.Block.WithdrawalsRoot); + rpcBlock.Withdrawals.Should().BeEquivalentTo(item.Block.Withdrawals); - json.Contains("withdrawals\"", StringComparison.Ordinal).Should().Be(item.Expected); - json.Contains("withdrawalsRoot", StringComparison.Ordinal).Should().Be(item.Expected); - } + var json = new EthereumJsonSerializer().Serialize(rpcBlock); - // With (Block, bool), tests don't run for some reason. Flipped to (bool, Block). - private static IEnumerable<(bool, Block)> BlockForRpcTestSource() => - new[] - { - (true, Build.A.Block - .WithWithdrawals(new[] - { - Build.A.Withdrawal - .WithAmount(1) - .WithRecipient(TestItem.AddressA) - .TestObject - }) - .TestObject - ), - - (false, Build.A.Block.WithWithdrawals(null).TestObject) - }; + json.Contains("withdrawals\"", StringComparison.Ordinal).Should().Be(item.Expected); + json.Contains("withdrawalsRoot", StringComparison.Ordinal).Should().Be(item.Expected); } + + // With (Block, bool), tests don't run for some reason. Flipped to (bool, Block). + private static IEnumerable<(bool, Block)> BlockForRpcTestSource() => + new[] + { + (true, Build.A.Block + .WithWithdrawals(new[] + { + Build.A.Withdrawal + .WithAmount(1) + .WithRecipient(TestItem.AddressA) + .TestObject + }) + .TestObject + ), + + (false, Build.A.Block.WithWithdrawals(null).TestObject) + }; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcSocketsClientTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcSocketsClientTests.cs index 8fe59998d75..eb1ec8c825c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcSocketsClientTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcSocketsClientTests.cs @@ -49,7 +49,7 @@ public async Task Can_handle_very_large_objects() jsonRpcProcessor: null!, jsonRpcService: null!, jsonRpcLocalStats: new NullJsonRpcLocalStats(), - jsonSerializer: new EthereumJsonSerializer(converters: new GethLikeTxTraceConverter()) + jsonSerializer: new EthereumJsonSerializer() ); JsonRpcResult result = JsonRpcResult.Single(bigObject, default); @@ -88,7 +88,7 @@ public async Task Can_send_multiple_messages(int messageCount) jsonRpcProcessor: null!, jsonRpcService: null!, jsonRpcLocalStats: new NullJsonRpcLocalStats(), - jsonSerializer: new EthereumJsonSerializer(converters: new GethLikeTxTraceConverter()) + jsonSerializer: new EthereumJsonSerializer() ); JsonRpcResult result = JsonRpcResult.Single(RandomSuccessResponse(1_000), default); @@ -132,7 +132,7 @@ public async Task Can_send_collections(int elements) jsonRpcProcessor: null!, jsonRpcService: null!, jsonRpcLocalStats: new NullJsonRpcLocalStats(), - jsonSerializer: new EthereumJsonSerializer(converters: new GethLikeTxTraceConverter()) + jsonSerializer: new EthereumJsonSerializer() ); JsonRpcResult result = JsonRpcResult.Collection(RandomBatchResult(10, 100)); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index 7b114498289..ca1b879b013 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Concurrent; -using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; using Nethermind.Blockchain; @@ -16,8 +16,6 @@ using Nethermind.Network.Config; using Nethermind.Serialization.Json; using Nethermind.Stats.Model; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NSubstitute; using NUnit.Framework; @@ -63,10 +61,7 @@ public async Task Test_node_info() { string serialized = await RpcTest.TestSerializedRequest(_adminRpcModule, "admin_nodeInfo"); JsonRpcSuccessResponse response = _serializer.Deserialize(serialized); - JsonSerializerSettings settings = new(); - settings.Converters = EthereumJsonSerializer.CommonConverters.ToList(); - - NodeInfo nodeInfo = ((JObject)response.Result!).ToObject(JsonSerializer.Create(settings))!; + NodeInfo nodeInfo = ((JsonElement)response.Result!).Deserialize(EthereumJsonSerializer.JsonOptions)!; nodeInfo.Enode.Should().Be(_enodeString); nodeInfo.Id.Should().Be("ae3623ef35c06ab49e9ae4b9f5a2b0f1983c28f85de1ccc98e2174333fdbdf1f"); nodeInfo.Ip.Should().Be("127.0.0.1"); @@ -79,7 +74,7 @@ public async Task Test_node_info() nodeInfo.Protocols["eth"].Difficulty.Should().Be(_blockTree.Head?.TotalDifficulty ?? 0); nodeInfo.Protocols["eth"].HeadHash.Should().Be(_blockTree.HeadHash); nodeInfo.Protocols["eth"].GenesisHash.Should().Be(_blockTree.GenesisHash); - nodeInfo.Protocols["eth"].ChainId.Should().Be(_blockTree.ChainId); + nodeInfo.Protocols["eth"].NewtorkId.Should().Be(_blockTree.ChainId); } [Test] @@ -87,7 +82,7 @@ public async Task Test_data_dir() { string serialized = await RpcTest.TestSerializedRequest(_adminRpcModule, "admin_dataDir"); JsonRpcSuccessResponse response = _serializer.Deserialize(serialized); - response.Result.Should().Be(_exampleDataDir); + response.Result!.ToString().Should().Be(_exampleDataDir); } [Test] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs index 1ffee8f6599..217a9f9b563 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugModuleTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -31,7 +32,7 @@ public class DebugModuleTests private IDebugBridge debugBridge = Substitute.For(); [Test] - public void Get_from_db() + public async Task Get_from_db() { byte[] key = new byte[] { 1, 2, 3 }; byte[] value = new byte[] { 4, 5, 6 }; @@ -40,22 +41,22 @@ public void Get_from_db() IConfigProvider configProvider = Substitute.For(); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); JsonRpcSuccessResponse? response = - RpcTest.TestRequest(rpcModule, "debug_getFromDb", "STATE", key.ToHexString(true)) as + await RpcTest.TestRequest(rpcModule, "debug_getFromDb", "STATE", key.ToHexString(true)) as JsonRpcSuccessResponse; byte[]? result = response?.Result as byte[]; } [Test] - public void Get_from_db_null_value() + public async Task Get_from_db_null_value() { - byte[] key = new byte[] { 1, 2, 3 }; debugBridge.GetDbValue(Arg.Any(), Arg.Any()).Returns((byte[])null!); IConfigProvider configProvider = Substitute.For(); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); + byte[] key = new byte[] { 1, 2, 3 }; JsonRpcSuccessResponse? response = - RpcTest.TestRequest(rpcModule, "debug_getFromDb", "STATE", key.ToHexString(true)) as + await RpcTest.TestRequest(rpcModule, "debug_getFromDb", "STATE", key.ToHexString(true)) as JsonRpcSuccessResponse; Assert.NotNull(response); @@ -63,7 +64,7 @@ public void Get_from_db_null_value() [TestCase("1")] [TestCase("0x1")] - public void Get_chain_level(string parameter) + public async Task Get_chain_level(string parameter) { debugBridge.GetLevelInfo(1).Returns( new ChainLevelInfo( @@ -75,7 +76,7 @@ public void Get_chain_level(string parameter) })); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - JsonRpcSuccessResponse? response = RpcTest.TestRequest(rpcModule, "debug_getChainLevel", parameter) as JsonRpcSuccessResponse; + JsonRpcSuccessResponse? response = await RpcTest.TestRequest(rpcModule, "debug_getChainLevel", parameter) as JsonRpcSuccessResponse; ChainLevelForRpc? chainLevel = response?.Result as ChainLevelForRpc; Assert.NotNull(chainLevel); Assert.That(chainLevel?.HasBlockOnMainChain, Is.EqualTo(true)); @@ -83,19 +84,19 @@ public void Get_chain_level(string parameter) } [Test] - public void Get_block_rlp_by_hash() + public async Task Get_block_rlp_by_hash() { BlockDecoder decoder = new(); Rlp rlp = decoder.Encode(Build.A.Block.WithNumber(1).TestObject); debugBridge.GetBlockRlp(Keccak.Zero).Returns(rlp.Bytes); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - JsonRpcSuccessResponse? response = RpcTest.TestRequest(rpcModule, "debug_getBlockRlpByHash", $"{Keccak.Zero.Bytes.ToHexString()}") as JsonRpcSuccessResponse; + JsonRpcSuccessResponse? response = await RpcTest.TestRequest(rpcModule, "debug_getBlockRlpByHash", $"{Keccak.Zero.Bytes.ToHexString()}") as JsonRpcSuccessResponse; Assert.That((byte[]?)response?.Result, Is.EqualTo(rlp.Bytes)); } [Test] - public void Get_block_rlp() + public async Task Get_block_rlp() { BlockDecoder decoder = new(); IDebugBridge debugBridge = Substitute.For(); @@ -103,31 +104,31 @@ public void Get_block_rlp() debugBridge.GetBlockRlp(1).Returns(rlp.Bytes); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - JsonRpcSuccessResponse? response = RpcTest.TestRequest(rpcModule, "debug_getBlockRlp", "1") as JsonRpcSuccessResponse; + JsonRpcSuccessResponse? response = await RpcTest.TestRequest(rpcModule, "debug_getBlockRlp", "1") as JsonRpcSuccessResponse; Assert.That((byte[]?)response?.Result, Is.EqualTo(rlp.Bytes)); } [Test] - public void Get_block_rlp_when_missing() + public async Task Get_block_rlp_when_missing() { debugBridge.GetBlockRlp(1).Returns((byte[])null!); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - JsonRpcErrorResponse? response = RpcTest.TestRequest(rpcModule, "debug_getBlockRlp", "1") as JsonRpcErrorResponse; + JsonRpcErrorResponse? response = await RpcTest.TestRequest(rpcModule, "debug_getBlockRlp", "1") as JsonRpcErrorResponse; Assert.That(response?.Error?.Code, Is.EqualTo(-32001)); } [Test] - public void Get_block_rlp_by_hash_when_missing() + public async Task Get_block_rlp_by_hash_when_missing() { BlockDecoder decoder = new(); Rlp rlp = decoder.Encode(Build.A.Block.WithNumber(1).TestObject); debugBridge.GetBlockRlp(Keccak.Zero).Returns((byte[])null!); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - JsonRpcErrorResponse? response = RpcTest.TestRequest(rpcModule, "debug_getBlockRlpByHash", $"{Keccak.Zero.Bytes.ToHexString()}") as JsonRpcErrorResponse; + JsonRpcErrorResponse? response = await RpcTest.TestRequest(rpcModule, "debug_getBlockRlpByHash", $"{Keccak.Zero.Bytes.ToHexString()}") as JsonRpcErrorResponse; Assert.That(response?.Error?.Code, Is.EqualTo(-32001)); } @@ -142,12 +143,12 @@ public async Task Get_trace() {"1".PadLeft(64, '0'), "2".PadLeft(64, '0')}, {"3".PadLeft(64, '0'), "4".PadLeft(64, '0')}, }, - Memory = new List + Memory = new string[] { "5".PadLeft(64, '0'), "6".PadLeft(64, '0') }, - Stack = new List + Stack = new string[] { "7".PadLeft(64, '0'), "8".PadLeft(64, '0') @@ -158,13 +159,14 @@ public async Task Get_trace() Depth = 1 }; - GethLikeTxTrace trace = new() { ReturnValue = Bytes.FromHexString("a2") }; + var trace = new GethLikeTxTrace(); + trace.ReturnValue = Bytes.FromHexString("a2"); trace.Entries.Add(entry); debugBridge.GetTransactionTrace(Arg.Any(), Arg.Any(), Arg.Any()).Returns(trace); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - string response = await RpcTest.TestSerializedRequest(DebugModuleFactory.Converters, rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{}"); + string response = await RpcTest.TestSerializedRequest(rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{}"); Assert.That(response, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"gas\":\"0x0\",\"failed\":false,\"returnValue\":\"0xa2\",\"structLogs\":[{\"pc\":0,\"op\":\"STOP\",\"gas\":22000,\"gasCost\":1,\"depth\":1,\"error\":null,\"stack\":[\"0000000000000000000000000000000000000000000000000000000000000007\",\"0000000000000000000000000000000000000000000000000000000000000008\"],\"memory\":[\"0000000000000000000000000000000000000000000000000000000000000005\",\"0000000000000000000000000000000000000000000000000000000000000006\"],\"storage\":{\"0000000000000000000000000000000000000000000000000000000000000001\":\"0000000000000000000000000000000000000000000000000000000000000002\",\"0000000000000000000000000000000000000000000000000000000000000003\":\"0000000000000000000000000000000000000000000000000000000000000004\"}}]},\"id\":67}")); } @@ -177,7 +179,7 @@ public async Task Get_js_trace() debugBridge.GetTransactionTrace(Arg.Any(), Arg.Any(), Arg.Any()).Returns(trace); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - string response = await RpcTest.TestSerializedRequest(DebugModuleFactory.Converters, rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{}"); + string response = await RpcTest.TestSerializedRequest(rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{}"); Assert.That(response, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"customProperty\":1},\"id\":67}")); } @@ -192,12 +194,12 @@ public async Task Get_trace_with_options() {"1".PadLeft(64, '0'), "2".PadLeft(64, '0')}, {"3".PadLeft(64, '0'), "4".PadLeft(64, '0')}, }, - Memory = new List + Memory = new string[] { "5".PadLeft(64, '0'), "6".PadLeft(64, '0') }, - Stack = new List + Stack = new string[] { }, Opcode = "STOP", @@ -206,13 +208,14 @@ public async Task Get_trace_with_options() Depth = 1 }; + GethLikeTxTrace trace = new() { ReturnValue = Bytes.FromHexString("a2") }; trace.Entries.Add(entry); debugBridge.GetTransactionTrace(Arg.Any(), Arg.Any(), Arg.Any()).Returns(trace); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - string response = await RpcTest.TestSerializedRequest(DebugModuleFactory.Converters, rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{disableStack : true}"); + string response = await RpcTest.TestSerializedRequest(rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{\"disableStack\" : true}"); Assert.That(response, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"gas\":\"0x0\",\"failed\":false,\"returnValue\":\"0xa2\",\"structLogs\":[{\"pc\":0,\"op\":\"STOP\",\"gas\":22000,\"gasCost\":1,\"depth\":1,\"error\":null,\"stack\":[],\"memory\":[\"0000000000000000000000000000000000000000000000000000000000000005\",\"0000000000000000000000000000000000000000000000000000000000000006\"],\"storage\":{\"0000000000000000000000000000000000000000000000000000000000000001\":\"0000000000000000000000000000000000000000000000000000000000000002\",\"0000000000000000000000000000000000000000000000000000000000000003\":\"0000000000000000000000000000000000000000000000000000000000000004\"}}]},\"id\":67}")); } @@ -224,7 +227,7 @@ public async Task Get_trace_with_javascript_setup() debugBridge.GetTransactionTrace(Arg.Any(), Arg.Any(), Arg.Do(arg => passedOption = arg)) .Returns(new GethLikeTxTrace()); DebugRpcModule rpcModule = new(LimboLogs.Instance, debugBridge, jsonRpcConfig); - await RpcTest.TestSerializedRequest(DebugModuleFactory.Converters, rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{disableStack : true, tracerConfig : { a : true } }"); + await RpcTest.TestSerializedRequest(rpcModule, "debug_traceTransaction", TestItem.KeccakA.ToString(true), "{\"disableStack\" : true, \"tracerConfig\" : {\"a\":true} }"); passedOption.TracerConfig!.ToString().Should().Be("{\"a\":true}"); } @@ -239,13 +242,13 @@ public void Debug_traceCall_test() {"3".PadLeft(64, '0'), "4".PadLeft(64, '0')}, }; - entry.Memory = new List + entry.Memory = new string[] { "5".PadLeft(64, '0'), "6".PadLeft(64, '0') }; - entry.Stack = new List { }; + entry.Stack = new string[] { }; entry.Opcode = "STOP"; entry.Gas = 22000; entry.GasCost = 1; @@ -275,7 +278,7 @@ public void Debug_traceCall_test() Gas = 22000, GasCost = 1, Depth = 1, - Memory = new List() + Memory = new string[] { "0000000000000000000000000000000000000000000000000000000000000005", "0000000000000000000000000000000000000000000000000000000000000006" @@ -314,11 +317,11 @@ public async Task Migrate_receipts() } [Test] - public void Update_head_block() + public async Task Update_head_block() { debugBridge.UpdateHeadBlock(Arg.Any()); IDebugRpcModule rpcModule = new DebugRpcModule(LimboLogs.Instance, debugBridge, jsonRpcConfig); - RpcTest.TestSerializedRequest(rpcModule, "debug_resetHead", TestItem.KeccakA.ToString()); + await RpcTest.TestSerializedRequest(rpcModule, "debug_resetHead", TestItem.KeccakA.ToString()); debugBridge.Received().UpdateHeadBlock(TestItem.KeccakA); } @@ -452,14 +455,14 @@ private static GethLikeTxTrace MockGethLikeTrace() Depth = 1, Gas = 22000, GasCost = 1, - Memory = new List + Memory = new string[] { "5".PadLeft(64, '0'), "6".PadLeft(64, '0') }, Opcode = "STOP", ProgramCounter = 32, - Stack = new List + Stack = new string[] { "7".PadLeft(64, '0'), "8".PadLeft(64, '0') diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 80979b6299c..2b34ff628fc 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -306,7 +306,7 @@ void handleNewBlock(object? sender, BlockReplacementEventArgs e) } test.BlockTree.BlockAddedToMain += handleNewBlock; - var newFilterResp = RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", "{\"fromBlock\":\"latest\"}"); + var newFilterResp = await RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", "{\"fromBlock\":\"latest\"}"); string getFilterLogsSerialized1 = await test.TestEthRpc("eth_getFilterChanges", (newFilterResp as JsonRpcSuccessResponse)!.Result?.ToString() ?? "0x0"); //expect empty - no changes so far @@ -459,7 +459,7 @@ public async Task Eth_get_filter_logs_filter_not_found() ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockchainBridge(bridge).Build(); string serialized = await ctx.Test.TestEthRpc("eth_getFilterLogs", "0x05"); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32603,\"message\":\"Filter with id: '5' does not exist.\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32603,\"message\":\"Filter with id: 5 does not exist.\"},\"id\":67}")); } [Test] @@ -472,7 +472,7 @@ public async Task Eth_get_filter_logs_filterId_overflow() ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).Build(); string serialized = await ctx.Test.TestEthRpc("eth_getFilterLogs", $"0x{filterId.ToString("X")}"); - Assert.That(serialized, Is.EqualTo($"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32603,\"message\":\"Filter with id: '{filterId}' does not exist.\"}},\"id\":67}}")); + Assert.That(serialized, Is.EqualTo($"{{\"jsonrpc\":\"2.0\",\"error\":{{\"code\":-32603,\"message\":\"Filter with id: {filterId} does not exist.\"}},\"id\":67}}")); } [Test] @@ -503,7 +503,7 @@ void handleNewBlock(object? sender, BlockReplacementEventArgs e) string getLogsSerialized = await test.TestEthRpc("eth_getLogs", $"{{\"fromBlock\":\"{blockHash}\"}}"); - var newFilterResp = RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", $"{{\"fromBlock\":\"{blockHash}\"}}"); + var newFilterResp = await RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", $"{{\"fromBlock\":\"{blockHash}\"}}"); Assert.IsTrue(newFilterResp is not null && newFilterResp is JsonRpcSuccessResponse); @@ -518,7 +518,7 @@ void handleNewBlock(object? sender, BlockReplacementEventArgs e) [TestCase("{\"topics\":[null, [\"0x00000000000000000000000000000001\", \"0x00000000000000000000000000000002\"]]}", "{\"jsonrpc\":\"2.0\",\"result\":[{\"address\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"blockHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockNumber\":\"0x1\",\"data\":\"0x010203\",\"logIndex\":\"0x1\",\"removed\":false,\"topics\":[\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2\"],\"transactionHash\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"transactionIndex\":\"0x1\",\"transactionLogIndex\":\"0x0\"}],\"id\":67}")] [TestCase("{\"fromBlock\":\"0x10\",\"toBlock\":\"latest\",\"address\":\"0x00000000000000000001\",\"topics\":[\"0x00000000000000000000000000000001\"]}", "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"16 could not be found\"},\"id\":67}")] [TestCase("{\"fromBlock\":\"0x2\",\"toBlock\":\"0x11\",\"address\":\"0x00000000000000000001\",\"topics\":[\"0x00000000000000000000000000000001\"]}", "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"17 could not be found\"},\"id\":67}")] - [TestCase("{\"fromBlock\":\"0x2\",\"toBlock\":\"0x1\",\"address\":\"0x00000000000000000001\",\"topics\":[\"0x00000000000000000000000000000001\"]}", "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"'From' block '2' is later than 'to' block '1'.\"},\"id\":67}")] + [TestCase("{\"fromBlock\":\"0x2\",\"toBlock\":\"0x1\",\"address\":\"0x00000000000000000001\",\"topics\":[\"0x00000000000000000000000000000001\"]}", "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"From block 2 is later than to block 1.\"},\"id\":67}")] public async Task Eth_get_logs(string parameter, string expected) { using Context ctx = await Context.Create(); @@ -1184,12 +1184,12 @@ public async Task eth_getBlockByNumber_should_return_withdrawals_correctly() ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockFinder).WithReceiptFinder(receiptFinder).Build(); string result = await ctx.Test.TestEthRpc("eth_getBlockByNumber", TestItem.KeccakA.ToString(), "true"); - result.Should().Be(new EthereumJsonSerializer().Serialize(new + Assert.That((new EthereumJsonSerializer().Serialize(new { jsonrpc = "2.0", result = new BlockForRpc(block, true, Substitute.For()), id = 67 - })); + })), Is.EqualTo(result)); } private static (byte[] ByteCode, AccessListItemForRpc[] AccessList) GetTestAccessList(long loads = 2, bool allowSystemUser = true) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs index a05a19d2ebc..2ab35c9ae37 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs @@ -2,11 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections; +using System.Text.Json; + using FluentAssertions; using Nethermind.Blockchain.Find; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; -using Newtonsoft.Json; +using Nethermind.Serialization.Json; + using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Modules.Eth; @@ -25,7 +28,7 @@ public static IEnumerable JsonTests }); yield return new TestCaseData( - JsonConvert.SerializeObject( + JsonSerializer.Serialize( new { fromBlock = "earliest", @@ -58,7 +61,7 @@ public static IEnumerable JsonTests }); yield return new TestCaseData( - JsonConvert.SerializeObject( + JsonSerializer.Serialize( new { address = "0xc2d77d118326c33bbe36ebeabf4f7ed6bc2dda5c", @@ -90,7 +93,7 @@ public static IEnumerable JsonTests var blockParam = BlockParameterConverter.GetBlockParameter(blockHash); yield return new TestCaseData( - JsonConvert.SerializeObject(new { blockHash }), + JsonSerializer.Serialize(new { blockHash }), new Filter { FromBlock = blockParam, @@ -103,7 +106,8 @@ public static IEnumerable JsonTests public void FromJson_parses_correctly(string json, Filter expectation) { Filter filter = new(); - filter.ReadJson(JsonSerializer.CreateDefault(), json); + using JsonDocument doc = JsonDocument.Parse(json); + filter.ReadJson(doc.RootElement, EthereumJsonSerializer.JsonOptions); filter.Should().BeEquivalentTo(expectation); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs index 98d5f0462d9..123497a5aa1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs @@ -30,7 +30,7 @@ using Nethermind.Specs; using Nethermind.Synchronization.ParallelSync; using Nethermind.TxPool; -using Newtonsoft.Json; + using NSubstitute; using NUnit.Framework; @@ -67,8 +67,7 @@ public void Setup() _receiptCanonicalityMonitor = new ReceiptCanonicalityMonitor(_blockTree, _receiptStorage, _logManager); _syncConfig = new SyncConfig(); - JsonSerializer jsonSerializer = new(); - jsonSerializer.Converters.AddRange(EthereumJsonSerializer.CommonConverters); + IJsonSerializer jsonSerializer = new EthereumJsonSerializer(); SubscriptionFactory subscriptionFactory = new( _logManager, @@ -245,9 +244,7 @@ public async Task NewHeadSubscription_with_includeTransactions_arg() } [TestCase("true")] - [TestCase("True")] [TestCase("false")] - [TestCase("False")] public async Task NewHeadSubscription_with_bool_arg(string boolArg) { string serialized = await RpcTest.TestSerializedRequest(_subscribeRpcModule, "eth_subscribe", "newHeads", boolArg); @@ -508,10 +505,10 @@ public void LogsSubscription_with_null_arguments_on_NewHeadBlock_event_with_few_ TxReceipt[] txReceipts = { - Build.A.Receipt.WithBlockNumber(blockNumber).WithIndex(11).WithLogs(logEntryA).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithIndex(22).WithLogs(logEntryA, logEntryB).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithIndex(33).WithLogs(logEntryB, logEntryC).TestObject - }; + Build.A.Receipt.WithBlockNumber(blockNumber).WithIndex(11).WithLogs(logEntryA).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithIndex(22).WithLogs(logEntryA, logEntryB).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithIndex(33).WithLogs(logEntryB, logEntryC).TestObject + }; _receiptStorage.Get(Arg.Any()).Returns(txReceipts); @@ -560,12 +557,12 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ TxReceipt[] txReceipts = { - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryB, logEntryC).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs().TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryB).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryC, logEntryC, logEntryB, logEntryC, logEntryC, logEntryB).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryC, logEntryB, logEntryA, logEntryC).TestObject, - }; + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryB, logEntryC).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs().TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryB).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryC, logEntryC, logEntryB, logEntryC, logEntryC, logEntryB).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryC, logEntryB, logEntryA, logEntryC).TestObject, + }; _receiptStorage.Get(Arg.Any()).Returns(txReceipts); @@ -608,12 +605,12 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ TxReceipt[] txReceipts = { - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryB, logEntryC).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs().TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryB).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryC, logEntryC, logEntryB, logEntryC, logEntryC, logEntryB).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryC, logEntryB, logEntryA, logEntryC).TestObject, - }; + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryB, logEntryC).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs().TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryB).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryC, logEntryC, logEntryB, logEntryC, logEntryC, logEntryB).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryC, logEntryB, logEntryA, logEntryC).TestObject, + }; _receiptStorage.Get(Arg.Any()).Returns(txReceipts); @@ -658,12 +655,12 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ TxReceipt[] txReceipts = { - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryB, logEntryC).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs().TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryB).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryE, logEntryE, logEntryB, logEntryD, logEntryE, logEntryB).TestObject, - Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryC, logEntryB, logEntryE, logEntryA, logEntryB).TestObject, - }; + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryA, logEntryB, logEntryC).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs().TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryB).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryE, logEntryE, logEntryB, logEntryD, logEntryE, logEntryB).TestObject, + Build.A.Receipt.WithBlockNumber(blockNumber).WithLogs(logEntryC, logEntryB, logEntryE, logEntryA, logEntryB).TestObject, + }; _receiptStorage.Get(Arg.Any()).Returns(txReceipts); @@ -741,9 +738,7 @@ public async Task NewPendingTransactionsSubscription_creating_result_with_includ } [TestCase("true")] - [TestCase("True")] [TestCase("false")] - [TestCase("False")] public async Task NewPendingTransactionsSubscription_creating_result_with_bool_arg(string boolArg) { string serialized = await RpcTest.TestSerializedRequest(_subscribeRpcModule, "eth_subscribe", "newPendingTransactions", boolArg); @@ -1062,7 +1057,7 @@ public void SyncingSubscription_on_NewHeadBlock_event_when_sync_changed_to_true( jsonRpcResult.Response.Should().NotBeNull(); string serialized = _jsonSerializer.Serialize(jsonRpcResult.Response); - var expectedResult = string.Concat("{\"jsonrpc\":\"2.0\",\"method\":\"eth_subscription\",\"params\":{\"subscription\":\"", syncingSubscription.Id, "\",\"result\":{\"isSyncing\":true,\"startingBlock\":\"0x0\",\"currentBlock\":\"0x2728\",\"highestBlock\":\"0x273a\"}}}"); + var expectedResult = string.Concat("{\"jsonrpc\":\"2.0\",\"method\":\"eth_subscription\",\"params\":{\"subscription\":\"", syncingSubscription.Id, "\",\"result\":{\"startingBlock\":\"0x0\",\"currentBlock\":\"0x2728\",\"highestBlock\":\"0x273a\"}}}"); expectedResult.Should().Be(serialized); } @@ -1080,7 +1075,7 @@ public void SyncingSubscription_on_NewBestSuggestedBlock_event_when_sync_changed jsonRpcResult.Response.Should().NotBeNull(); string serialized = _jsonSerializer.Serialize(jsonRpcResult.Response); - var expectedResult = string.Concat("{\"jsonrpc\":\"2.0\",\"method\":\"eth_subscription\",\"params\":{\"subscription\":\"", syncingSubscription.Id, "\",\"result\":{\"isSyncing\":true,\"startingBlock\":\"0x0\",\"currentBlock\":\"0x2738\",\"highestBlock\":\"0x2773\"}}}"); + var expectedResult = string.Concat("{\"jsonrpc\":\"2.0\",\"method\":\"eth_subscription\",\"params\":{\"subscription\":\"", syncingSubscription.Id, "\",\"result\":{\"startingBlock\":\"0x0\",\"currentBlock\":\"0x2738\",\"highestBlock\":\"0x2773\"}}}"); expectedResult.Should().Be(serialized); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index 753dcd8ec13..7d0eff56e92 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -29,7 +29,7 @@ using Nethermind.Trie.Pruning; using Nethermind.TxPool; using Nethermind.Wallet; -using Newtonsoft.Json; + using Nethermind.Config; using Nethermind.Synchronization.ParallelSync; @@ -159,9 +159,9 @@ protected override async Task Build(ISpecProvider? specProvider } public Task TestEthRpc(string method, params string[] parameters) => - RpcTest.TestSerializedRequest(EthModuleFactory.Converters, EthRpcModule, method, parameters); + RpcTest.TestSerializedRequest(EthRpcModule, method, parameters); public Task TestSerializedRequest(T module, string method, params string[] parameters) where T : class, IRpcModule => - RpcTest.TestSerializedRequest(Array.Empty(), module, method, parameters); + RpcTest.TestSerializedRequest(module, method, parameters); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcModuleProvider.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcModuleProvider.cs index abf6bf8de2e..efb17723cca 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcModuleProvider.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcModuleProvider.cs @@ -15,7 +15,9 @@ using Nethermind.JsonRpc.Modules.Trace; using Nethermind.JsonRpc.Modules.Web3; using Nethermind.Logging; -using Newtonsoft.Json; +using Nethermind.Serialization.Json; + + using NSubstitute; namespace Nethermind.JsonRpc.Test.Modules @@ -55,13 +57,12 @@ private void EnableModule() where TOther : IRpcModule } } - public JsonSerializer Serializer => _provider.Serializer; - public IReadOnlyCollection Converters => _provider.Converters; + public IJsonSerializer Serializer => _provider.Serializer; public IReadOnlyCollection Enabled => _provider.All; public IReadOnlyCollection All => _provider.All; public ModuleResolution Check(string methodName, JsonRpcContext context) => _provider.Check(methodName, context); - public (MethodInfo, bool) Resolve(string methodName) => _provider.Resolve(methodName); + public (MethodInfo, ParameterInfo[], bool) Resolve(string methodName) => _provider.Resolve(methodName); public Task Rent(string methodName, bool readOnly) => _provider.Rent(methodName, readOnly); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityAccountChangeConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityAccountChangeConverterTests.cs index 2c4fac2b273..749550f5cc5 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityAccountChangeConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityAccountChangeConverterTests.cs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json; + using Nethermind.Evm.Tracing.ParityStyle; using Nethermind.Int256; -using Nethermind.JsonRpc.Modules.Trace; -using Newtonsoft.Json; -using NSubstitute; +using Nethermind.Serialization.Json; + using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Modules.Trace @@ -14,48 +15,36 @@ namespace Nethermind.JsonRpc.Test.Modules.Trace [TestFixture] public class ParityAccountChangeConverterTests { - private ParityAccountStateChangeConverter converter = null!; - [SetUp] public void SetUp() { - converter = new ParityAccountStateChangeConverter(); } [Test] public void Does_not_throw_on_change_when_code_after_is_null() { - JsonWriter writer = Substitute.For(); - JsonSerializer serializer = Substitute.For(); - ParityAccountStateChange change = new() { Code = new ParityStateChange(new byte[] { 1 }, null!) }; - Assert.DoesNotThrow(() => converter.WriteJson(writer, change, serializer)); + Assert.DoesNotThrow(() => JsonSerializer.Serialize(change, EthereumJsonSerializer.JsonOptions)); } [Test] public void Does_not_throw_on_change_when_code_before_is_null() { - JsonWriter writer = Substitute.For(); - JsonSerializer serializer = Substitute.For(); - ParityAccountStateChange change = new() { Code = new ParityStateChange(null!, new byte[] { 1 }) }; - Assert.DoesNotThrow(() => converter.WriteJson(writer, change, serializer)); + Assert.DoesNotThrow(() => JsonSerializer.Serialize(change, EthereumJsonSerializer.JsonOptions)); } [Test] public void Does_not_throw_on_change_storage() { - JsonWriter writer = Substitute.For(); - JsonSerializer serializer = Substitute.For(); - ParityAccountStateChange change = new() { Storage = new Dictionary> @@ -64,7 +53,7 @@ public void Does_not_throw_on_change_storage() } }; - Assert.DoesNotThrow(() => converter.WriteJson(writer, change, serializer)); + Assert.DoesNotThrow(() => JsonSerializer.Serialize(change, EthereumJsonSerializer.JsonOptions)); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTraceAddressConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTraceAddressConverterTests.cs index 3e53fd85508..70d0012ebbe 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTraceAddressConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTraceAddressConverterTests.cs @@ -32,7 +32,7 @@ bool Comparer(int[] a, int[] b) return true; } - TestRoundtrip(new[] { 1, 2, 3, 1000, 10000 }, Comparer, new ParityTraceAddressConverter()); + TestRoundtrip(new[] { 1, 2, 3, 1000, 10000 }, Comparer, null); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTxTraceFromReplayConverterTest.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTxTraceFromReplayConverterTest.cs index cb827ca48d0..26bcfc0b983 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTxTraceFromReplayConverterTest.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityTxTraceFromReplayConverterTest.cs @@ -94,7 +94,7 @@ public void Can_serialize_reward() [Test] public void Can_serialize_creation_method() { - string expectedResult = "{\"output\":null,\"stateDiff\":{\"0x76e68a8696537e4141926f3e528733af9e237d69\":{\"balance\":{\"*\":{\"from\":\"0x1\",\"to\":\"0x2\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x0\",\"to\":\"0x1\"}},\"storage\":{\"0x0000000000000000000000000000000000000000000000000000000000000001\":{\"*\":{\"from\":\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"to\":\"0x0000000000000000000000000000000000000000000000000000000000000002\"}}}}},\"trace\":[{\"action\":{\"creationMethod\":\"create2\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"gas\":\"0x9c40\",\"init\":\"0x010203040506\",\"value\":\"0x3039\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":null},\"subtraces\":1,\"traceAddress\":[1,2,3],\"type\":\"create\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"gas\":\"0x2710\",\"input\":\"0x\",\"to\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"value\":\"0x10932\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":null},\"subtraces\":0,\"traceAddress\":[0,0],\"type\":null}],\"vmTrace\":null}"; + string expectedResult = "{\"output\":null,\"stateDiff\":{\"0x76e68a8696537e4141926f3e528733af9e237d69\":{\"balance\":{\"*\":{\"from\":\"0x1\",\"to\":\"0x2\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x0\",\"to\":\"0x1\"}},\"storage\":{\"0x0000000000000000000000000000000000000000000000000000000000000001\":{\"*\":{\"from\":\"0x0000000000000000000000000000000000000000000000000000000000000001\",\"to\":\"0x0000000000000000000000000000000000000000000000000000000000000002\"}}}}},\"trace\":[{\"action\":{\"creationMethod\":\"create2\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"gas\":\"0x9c40\",\"input\":\"0x010203040506\",\"to\":null,\"value\":\"0x3039\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":null},\"subtraces\":1,\"traceAddress\":[1,2,3],\"type\":\"create\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"gas\":\"0x2710\",\"input\":\"0x\",\"to\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"value\":\"0x10932\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":null},\"subtraces\":0,\"traceAddress\":[0,0],\"type\":null}],\"vmTrace\":null}"; ParityTraceAction subtrace = new() { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs index e7c13817945..f4567383645 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs @@ -31,820 +31,819 @@ using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules; -namespace Nethermind.JsonRpc.Test.Modules +namespace Nethermind.JsonRpc.Test.Modules; + +[Parallelizable(ParallelScope.All)] +[TestFixture] +public class TraceRpcModuleTests { - [Parallelizable(ParallelScope.All)] - [TestFixture] - public class TraceRpcModuleTests + private class Context { - private class Context + public async Task Build(ISpecProvider? specProvider = null, bool isAura = false) { - public async Task Build(ISpecProvider? specProvider = null, bool isAura = false) + JsonRpcConfig = new JsonRpcConfig(); + Blockchain = await TestRpcBlockchain.ForTest(isAura ? SealEngineType.AuRa : SealEngineType.NethDev).Build(specProvider); + await Blockchain.AddFunds(TestItem.AddressA, 1000.Ether()); + await Blockchain.AddFunds(TestItem.AddressB, 1000.Ether()); + await Blockchain.AddFunds(TestItem.AddressC, 1000.Ether()); + ReceiptsRecovery receiptsRecovery = + new(Blockchain.EthereumEcdsa, Blockchain.SpecProvider); + IReceiptFinder receiptFinder = new FullInfoReceiptFinder(Blockchain.ReceiptStorage, receiptsRecovery, Blockchain.BlockFinder); + ReadOnlyTxProcessingEnv txProcessingEnv = + new(Blockchain.WorldStateManager, Blockchain.BlockTree.AsReadOnly(), Blockchain.SpecProvider, Blockchain.LogManager); + RewardCalculator rewardCalculatorSource = new(Blockchain.SpecProvider); + + IRewardCalculator rewardCalculator = rewardCalculatorSource.Get(txProcessingEnv.TransactionProcessor); + + RpcBlockTransactionsExecutor rpcBlockTransactionsExecutor = new(txProcessingEnv.TransactionProcessor, txProcessingEnv.StateProvider); + BlockProcessor.BlockValidationTransactionsExecutor executeBlockTransactionsExecutor = new(txProcessingEnv.TransactionProcessor, + txProcessingEnv.StateProvider); + ReadOnlyChainProcessingEnv CreateChainProcessingEnv(IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor) => new( + txProcessingEnv, + Always.Valid, + Blockchain.BlockPreprocessorStep, + rewardCalculator, + Blockchain.ReceiptStorage, + Blockchain.SpecProvider, + Blockchain.LogManager, + transactionsExecutor); + + ReadOnlyChainProcessingEnv traceProcessingEnv = CreateChainProcessingEnv(rpcBlockTransactionsExecutor); + ReadOnlyChainProcessingEnv executeProcessingEnv = CreateChainProcessingEnv(executeBlockTransactionsExecutor); + + Tracer tracer = new(txProcessingEnv.StateProvider, traceProcessingEnv.ChainProcessor, executeProcessingEnv.ChainProcessor); + TraceRpcModule = new TraceRpcModule(receiptFinder, tracer, Blockchain.BlockFinder, JsonRpcConfig, MainnetSpecProvider.Instance, LimboLogs.Instance, txProcessingEnv.StateReader); + + for (int i = 1; i < 10; i++) { - JsonRpcConfig = new JsonRpcConfig(); - Blockchain = await TestRpcBlockchain.ForTest(isAura ? SealEngineType.AuRa : SealEngineType.NethDev).Build(specProvider); - await Blockchain.AddFunds(TestItem.AddressA, 1000.Ether()); - await Blockchain.AddFunds(TestItem.AddressB, 1000.Ether()); - await Blockchain.AddFunds(TestItem.AddressC, 1000.Ether()); - ReceiptsRecovery receiptsRecovery = - new(Blockchain.EthereumEcdsa, Blockchain.SpecProvider); - IReceiptFinder receiptFinder = new FullInfoReceiptFinder(Blockchain.ReceiptStorage, receiptsRecovery, Blockchain.BlockFinder); - ReadOnlyTxProcessingEnv txProcessingEnv = - new(Blockchain.WorldStateManager, Blockchain.BlockTree.AsReadOnly(), Blockchain.SpecProvider, Blockchain.LogManager); - RewardCalculator rewardCalculatorSource = new(Blockchain.SpecProvider); - - IRewardCalculator rewardCalculator = rewardCalculatorSource.Get(txProcessingEnv.TransactionProcessor); - - RpcBlockTransactionsExecutor rpcBlockTransactionsExecutor = new(txProcessingEnv.TransactionProcessor, txProcessingEnv.StateProvider); - BlockProcessor.BlockValidationTransactionsExecutor executeBlockTransactionsExecutor = new(txProcessingEnv.TransactionProcessor, - txProcessingEnv.StateProvider); - ReadOnlyChainProcessingEnv CreateChainProcessingEnv(IBlockProcessor.IBlockTransactionsExecutor transactionsExecutor) => new( - txProcessingEnv, - Always.Valid, - Blockchain.BlockPreprocessorStep, - rewardCalculator, - Blockchain.ReceiptStorage, - Blockchain.SpecProvider, - Blockchain.LogManager, - transactionsExecutor); - - ReadOnlyChainProcessingEnv traceProcessingEnv = CreateChainProcessingEnv(rpcBlockTransactionsExecutor); - ReadOnlyChainProcessingEnv executeProcessingEnv = CreateChainProcessingEnv(executeBlockTransactionsExecutor); - - Tracer tracer = new(txProcessingEnv.StateProvider, traceProcessingEnv.ChainProcessor, executeProcessingEnv.ChainProcessor); - TraceRpcModule = new TraceRpcModule(receiptFinder, tracer, Blockchain.BlockFinder, JsonRpcConfig, MainnetSpecProvider.Instance, LimboLogs.Instance, txProcessingEnv.StateReader); - - for (int i = 1; i < 10; i++) + List transactions = new(); + for (int j = 0; j < i; j++) { - List transactions = new(); - for (int j = 0; j < i; j++) - { - transactions.Add(Core.Test.Builders.Build.A.Transaction.WithNonce(Blockchain.State.GetAccount(TestItem.AddressB).Nonce + (UInt256)j).SignedAndResolved(Blockchain.EthereumEcdsa, TestItem.PrivateKeyB).TestObject); - } - await Blockchain.AddBlock(transactions.ToArray()); + transactions.Add(Core.Test.Builders.Build.A.Transaction.WithNonce(Blockchain.State.GetAccount(TestItem.AddressB).Nonce + (UInt256)j).SignedAndResolved(Blockchain.EthereumEcdsa, TestItem.PrivateKeyB).TestObject); } + await Blockchain.AddBlock(transactions.ToArray()); } + } - public ITraceRpcModule TraceRpcModule { get; private set; } = null!; - public IJsonRpcConfig JsonRpcConfig { get; private set; } = null!; - public TestRpcBlockchain Blockchain { get; set; } = null!; + public ITraceRpcModule TraceRpcModule { get; private set; } = null!; + public IJsonRpcConfig JsonRpcConfig { get; private set; } = null!; + public TestRpcBlockchain Blockchain { get; set; } = null!; - } - [Test] - public async Task Tx_positions_are_fine() - { - Context context = new(); - await context.Build(); - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_block", "latest"); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xa1e0e640b433d5a8931881b8eee7b1a125474b04e430c0bf8afff52584c53273\",\"transactionPosition\":0,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x5cf5d4a0a93000beb1cfb373508ce4c0153ab491be99b3c927f482346c86a0e1\",\"transactionPosition\":1,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x02d2cde9120e37722f607771ebaa0d4e98c5d99a8a9e7df6872e8c8c9f5c0bc5\",\"transactionPosition\":2,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xe50a2a2d170011b1f9ee080c3810bed0c63dbb1b2b2c541c78ada5b222cc3fd2\",\"transactionPosition\":3,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xff0d4524d379fc15c41a9b0444b943e1a530779b7d09c8863858267c5ef92b24\",\"transactionPosition\":4,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xf9b69366c82084e3799dc4a7ad87dc173ef4923d853bc250de86b81786f2972a\",\"transactionPosition\":5,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x28171c29b23cd96f032fe43f444402af4555ee5f074d5d0d0a1089d940f136e7\",\"transactionPosition\":6,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x09b01caf4b7ecfe9d02251b2e478f2da0fdf08412e3fa1ff963fa80635dab031\",\"transactionPosition\":7,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xd82382905afbe4ca4c2b8e54cea43818c91e0014c3827e3020fbd82b732b8239\",\"transactionPosition\":8,\"type\":\"call\"},{\"action\":{\"author\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"rewardType\":\"block\",\"value\":\"0x1bc16d674ec80000\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"subtraces\":0,\"traceAddress\":[],\"type\":\"reward\"}],\"id\":67}")); - } + } + [Test] + public async Task Tx_positions_are_fine() + { + Context context = new(); + await context.Build(); + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_block", "latest"); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xa1e0e640b433d5a8931881b8eee7b1a125474b04e430c0bf8afff52584c53273\",\"transactionPosition\":0,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x5cf5d4a0a93000beb1cfb373508ce4c0153ab491be99b3c927f482346c86a0e1\",\"transactionPosition\":1,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x02d2cde9120e37722f607771ebaa0d4e98c5d99a8a9e7df6872e8c8c9f5c0bc5\",\"transactionPosition\":2,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xe50a2a2d170011b1f9ee080c3810bed0c63dbb1b2b2c541c78ada5b222cc3fd2\",\"transactionPosition\":3,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xff0d4524d379fc15c41a9b0444b943e1a530779b7d09c8863858267c5ef92b24\",\"transactionPosition\":4,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xf9b69366c82084e3799dc4a7ad87dc173ef4923d853bc250de86b81786f2972a\",\"transactionPosition\":5,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x28171c29b23cd96f032fe43f444402af4555ee5f074d5d0d0a1089d940f136e7\",\"transactionPosition\":6,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x09b01caf4b7ecfe9d02251b2e478f2da0fdf08412e3fa1ff963fa80635dab031\",\"transactionPosition\":7,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xd82382905afbe4ca4c2b8e54cea43818c91e0014c3827e3020fbd82b732b8239\",\"transactionPosition\":8,\"type\":\"call\"},{\"action\":{\"author\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"rewardType\":\"block\",\"value\":\"0x1bc16d674ec80000\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"subtraces\":0,\"traceAddress\":[],\"type\":\"reward\"}],\"id\":67}")); + } - [Test] - public async Task Trace_filter_return_fail_with_not_existing_block() - { - Context context = new(); - await context.Build(); - string request = "{\"fromBlock\":\"0x154\",\"after\":0}"; - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_filter", request); - Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"Block 340 could not be found\"},\"id\":67}"), serialized.Replace("\"", "\\\"")); - } + [Test] + public async Task Trace_filter_return_fail_with_not_existing_block() + { + Context context = new(); + await context.Build(); + string request = "{\"fromBlock\":\"0x154\",\"after\":0}"; + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_filter", request); + Assert.That( + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32001,\"message\":\"Block 340 could not be found\"},\"id\":67}"), serialized.Replace("\"", "\\\"")); + } - [Test] - public async Task Trace_filter_return_fail_from_block_higher_than_to_block() - { - Context context = new(); - await context.Build(); - string request = "{\"fromBlock\":\"0x8\",\"toBlock\":\"0x6\"}"; - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_filter", request); - Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"From block number: 8 is greater than to block number 6\"},\"id\":67}"), serialized.Replace("\"", "\\\"")); - } + [Test] + public async Task Trace_filter_return_fail_from_block_higher_than_to_block() + { + Context context = new(); + await context.Build(); + string request = "{\"fromBlock\":\"0x8\",\"toBlock\":\"0x6\"}"; + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_filter", request); + Assert.That( + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"From block number: 8 is greater than to block number 6\"},\"id\":67}"), serialized.Replace("\"", "\\\"")); + } - [Test] - public async Task Trace_filter_return_empty_result_with_count_0() - { - Context context = new(); - await context.Build(); - string request = "{\"count\":0x0, \"fromBlock\":\"0x3\",\"toBlock\":\"0x3\"}"; - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_filter", request); - Assert.That( - serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[],\"id\":67}"), serialized.Replace("\"", "\\\"")); - } + [Test] + public async Task Trace_filter_return_empty_result_with_count_0() + { + Context context = new(); + await context.Build(); + string request = "{\"count\":\"0x0\", \"fromBlock\":\"0x3\",\"toBlock\":\"0x3\"}"; + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_filter", request); + Assert.That( + serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[],\"id\":67}"), serialized.Replace("\"", "\\\"")); + } - [Test] - public async Task Trace_filter_return_expected_json() - { - Context context = new(); - await context.Build(); - TraceFilterForRpc traceFilterRequest = new(); - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_filter", new EthereumJsonSerializer().Serialize(traceFilterRequest)); - - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xa1e0e640b433d5a8931881b8eee7b1a125474b04e430c0bf8afff52584c53273\",\"transactionPosition\":0,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x5cf5d4a0a93000beb1cfb373508ce4c0153ab491be99b3c927f482346c86a0e1\",\"transactionPosition\":1,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x02d2cde9120e37722f607771ebaa0d4e98c5d99a8a9e7df6872e8c8c9f5c0bc5\",\"transactionPosition\":2,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xe50a2a2d170011b1f9ee080c3810bed0c63dbb1b2b2c541c78ada5b222cc3fd2\",\"transactionPosition\":3,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xff0d4524d379fc15c41a9b0444b943e1a530779b7d09c8863858267c5ef92b24\",\"transactionPosition\":4,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xf9b69366c82084e3799dc4a7ad87dc173ef4923d853bc250de86b81786f2972a\",\"transactionPosition\":5,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x28171c29b23cd96f032fe43f444402af4555ee5f074d5d0d0a1089d940f136e7\",\"transactionPosition\":6,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x09b01caf4b7ecfe9d02251b2e478f2da0fdf08412e3fa1ff963fa80635dab031\",\"transactionPosition\":7,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xd82382905afbe4ca4c2b8e54cea43818c91e0014c3827e3020fbd82b732b8239\",\"transactionPosition\":8,\"type\":\"call\"},{\"action\":{\"author\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"rewardType\":\"block\",\"value\":\"0x1bc16d674ec80000\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"subtraces\":0,\"traceAddress\":[],\"type\":\"reward\"}],\"id\":67}"), serialized.Replace("\"", "\\\"")); - } + [Test] + public async Task Trace_filter_return_expected_json() + { + Context context = new(); + await context.Build(); + TraceFilterForRpc traceFilterRequest = new(); + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_filter", new EthereumJsonSerializer().Serialize(traceFilterRequest)); + + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xa1e0e640b433d5a8931881b8eee7b1a125474b04e430c0bf8afff52584c53273\",\"transactionPosition\":0,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x5cf5d4a0a93000beb1cfb373508ce4c0153ab491be99b3c927f482346c86a0e1\",\"transactionPosition\":1,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x02d2cde9120e37722f607771ebaa0d4e98c5d99a8a9e7df6872e8c8c9f5c0bc5\",\"transactionPosition\":2,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xe50a2a2d170011b1f9ee080c3810bed0c63dbb1b2b2c541c78ada5b222cc3fd2\",\"transactionPosition\":3,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xff0d4524d379fc15c41a9b0444b943e1a530779b7d09c8863858267c5ef92b24\",\"transactionPosition\":4,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xf9b69366c82084e3799dc4a7ad87dc173ef4923d853bc250de86b81786f2972a\",\"transactionPosition\":5,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x28171c29b23cd96f032fe43f444402af4555ee5f074d5d0d0a1089d940f136e7\",\"transactionPosition\":6,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0x09b01caf4b7ecfe9d02251b2e478f2da0fdf08412e3fa1ff963fa80635dab031\",\"transactionPosition\":7,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x0\",\"input\":\"0x\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"transactionHash\":\"0xd82382905afbe4ca4c2b8e54cea43818c91e0014c3827e3020fbd82b732b8239\",\"transactionPosition\":8,\"type\":\"call\"},{\"action\":{\"author\":\"0x475674cb523a0a2736b7f7534390288fce16982c\",\"rewardType\":\"block\",\"value\":\"0x1bc16d674ec80000\"},\"blockHash\":\"0xcd3d2c10309822aec4cbbfa80ba905ba1de62834a3b40f8012520734db2763ca\",\"blockNumber\":15,\"subtraces\":0,\"traceAddress\":[],\"type\":\"reward\"}],\"id\":67}"), serialized.Replace("\"", "\\\"")); + } - [Test] - public async Task Trace_filter_skip_expected_number_of_traces() - { - Context context = new(); - await context.Build(); - TraceFilterForRpc traceRequest = new(); - traceRequest.After = 3; - ResultWrapper> secondTraces = context.TraceRpcModule.trace_filter(traceRequest); - Assert.That(secondTraces.Data.Count(), Is.EqualTo(7)); - } - [Test] - public async Task Trace_filter_get_given_amount_of_traces() - { - Context context = new(); - await context.Build(); - TraceFilterForRpc traceFilterRequest = new(); - traceFilterRequest.Count = 3; - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - Assert.That(traces.Data.Count(), Is.EqualTo(3)); - } - [Test] - public async Task Trace_filter_skip_and_get_the_rest_of_traces() - { - Context context = new(); - await context.Build(); - TraceFilterForRpc traceFilterRequest = new(); - traceFilterRequest.Count = 3; - traceFilterRequest.After = 7; - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - // Total 9 transactions in block + 1 reward trace - after skipping 7 - it should be 3 - Assert.That(traces.Data.Count(), Is.EqualTo(3)); - } - [Test] - public async Task Trace_filter_with_filtering_by_receiver_address() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA) - .WithTo(TestItem.AddressA) - .SignedAndResolved(blockchain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; - await context.Blockchain.AddBlock(transaction); - - TraceFilterForRpc traceFilterRequest = new(); - long lastBLockNumber = blockchain.BlockTree.Head!.Number; - traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber); - traceFilterRequest.ToBlock = new BlockParameter(lastBLockNumber); - traceFilterRequest.ToAddress = new[] { TestItem.AddressA }; - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - Assert.That(traces.Data.Count(), Is.EqualTo(1)); - } - [Test] - public async Task Trace_filter_with_filtering_by_sender() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA) - .WithTo(TestItem.AddressA) - .SignedAndResolved(blockchain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; - await context.Blockchain.AddBlock(transaction); - await context.Blockchain.AddBlock(); - long lastBLockNumber = blockchain.BlockTree.Head!.Number; - TraceFilterForRpc traceFilterRequest = new(); - traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber - 1); - traceFilterRequest.ToBlock = BlockParameter.Latest; - traceFilterRequest.FromAddress = new[] { TestItem.PrivateKeyA.Address }; - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - Assert.That(traces.Data.Count(), Is.EqualTo(1)); - } + [Test] + public async Task Trace_filter_skip_expected_number_of_traces() + { + Context context = new(); + await context.Build(); + TraceFilterForRpc traceRequest = new(); + traceRequest.After = 3; + ResultWrapper> secondTraces = context.TraceRpcModule.trace_filter(traceRequest); + Assert.That(secondTraces.Data.Count(), Is.EqualTo(7)); + } + [Test] + public async Task Trace_filter_get_given_amount_of_traces() + { + Context context = new(); + await context.Build(); + TraceFilterForRpc traceFilterRequest = new(); + traceFilterRequest.Count = 3; + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + Assert.That(traces.Data.Count(), Is.EqualTo(3)); + } + [Test] + public async Task Trace_filter_skip_and_get_the_rest_of_traces() + { + Context context = new(); + await context.Build(); + TraceFilterForRpc traceFilterRequest = new(); + traceFilterRequest.Count = 3; + traceFilterRequest.After = 7; + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + // Total 9 transactions in block + 1 reward trace - after skipping 7 - it should be 3 + Assert.That(traces.Data.Count(), Is.EqualTo(3)); + } + [Test] + public async Task Trace_filter_with_filtering_by_receiver_address() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA) + .WithTo(TestItem.AddressA) + .SignedAndResolved(blockchain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; + await context.Blockchain.AddBlock(transaction); + + TraceFilterForRpc traceFilterRequest = new(); + long lastBLockNumber = blockchain.BlockTree.Head!.Number; + traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber); + traceFilterRequest.ToBlock = new BlockParameter(lastBLockNumber); + traceFilterRequest.ToAddress = new[] { TestItem.AddressA }; + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + Assert.That(traces.Data.Count(), Is.EqualTo(1)); + } + [Test] + public async Task Trace_filter_with_filtering_by_sender() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA) + .WithTo(TestItem.AddressA) + .SignedAndResolved(blockchain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; + await context.Blockchain.AddBlock(transaction); + await context.Blockchain.AddBlock(); + long lastBLockNumber = blockchain.BlockTree.Head!.Number; + TraceFilterForRpc traceFilterRequest = new(); + traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber - 1); + traceFilterRequest.ToBlock = BlockParameter.Latest; + traceFilterRequest.FromAddress = new[] { TestItem.PrivateKeyA.Address }; + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + Assert.That(traces.Data.Count(), Is.EqualTo(1)); + } - [Test] - public async Task Trace_filter_with_filtering_by_internal_transaction_receiver() - { + [Test] + public async Task Trace_filter_with_filtering_by_internal_transaction_receiver() + { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - byte[] deployedCode = new byte[3]; - byte[] initCode = Prepare.EvmCode - .ForInitOf(deployedCode) - .Done; - byte[] createCode = Prepare.EvmCode - .Create(initCode, 0) - .Op(Instruction.STOP) - .Done; - - Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) - .WithData(createCode) - .WithTo(null) - .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; - await blockchain.AddBlock(transaction1); - Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); - byte[] code = Prepare.EvmCode - .Call(contractAddress, 50000) - .Call(contractAddress, 50000) - .Op(Instruction.STOP) - .Done; - - Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) - .WithTo(null) - .WithGasLimit(93548).TestObject; - await blockchain.AddBlock(transaction2); - await blockchain.AddBlock(); - long lastBLockNumber = blockchain.BlockTree.Head!.Number; - - TraceFilterForRpc traceFilterRequest = new(); - traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber - 1); - traceFilterRequest.ToBlock = BlockParameter.Latest; - traceFilterRequest.ToAddress = new[] { contractAddress }; - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - Assert.That(traces.Data.Count(), Is.EqualTo(2)); + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + byte[] deployedCode = new byte[3]; + byte[] initCode = Prepare.EvmCode + .ForInitOf(deployedCode) + .Done; + byte[] createCode = Prepare.EvmCode + .Create(initCode, 0) + .Op(Instruction.STOP) + .Done; + + Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) + .WithData(createCode) + .WithTo(null) + .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; + await blockchain.AddBlock(transaction1); + Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); + byte[] code = Prepare.EvmCode + .Call(contractAddress, 50000) + .Call(contractAddress, 50000) + .Op(Instruction.STOP) + .Done; + + Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) + .WithTo(null) + .WithGasLimit(93548).TestObject; + await blockchain.AddBlock(transaction2); + await blockchain.AddBlock(); + long lastBLockNumber = blockchain.BlockTree.Head!.Number; + + TraceFilterForRpc traceFilterRequest = new(); + traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber - 1); + traceFilterRequest.ToBlock = BlockParameter.Latest; + traceFilterRequest.ToAddress = new[] { contractAddress }; + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + Assert.That(traces.Data.Count(), Is.EqualTo(2)); - } - [Test] - public async Task Trace_filter_with_filtering_by_sender_and_receiver() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - await context.Blockchain.AddBlock( - new[] - { - Build.A.Transaction.WithNonce(currentNonceAddressA + 1).WithTo(TestItem.AddressD) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, - Build.A.Transaction.WithNonce(blockchain.State.GetAccount(TestItem.AddressB).Nonce + 1).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyB).TestObject, - Build.A.Transaction.WithNonce(currentNonceAddressA).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject - } - ); - await context.Blockchain.AddBlock(); - TraceFilterForRpc traceFilterRequest = new(); - long lastBLockNumber = blockchain.BlockTree.Head!.Number; - traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber - 1); - traceFilterRequest.ToBlock = BlockParameter.Latest; - traceFilterRequest.FromAddress = new[] { TestItem.PrivateKeyA.Address }; - traceFilterRequest.ToAddress = new[] { TestItem.AddressC }; - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - Assert.That(traces.Data.Count(), Is.EqualTo(1)); - } - [Test] - public async Task Trace_filter_complex_scenario() - { - Context context = new(); - await context.Build(); - TraceFilterForRpc traceFilterRequest = new(); - TestRpcBlockchain blockchain = context.Blockchain; - long lastBLockNumber = blockchain.BlockTree.Head!.Number; - traceFilterRequest.After = 3; - traceFilterRequest.Count = 4; - traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber + 1); - traceFilterRequest.ToBlock = BlockParameter.Latest; - traceFilterRequest.FromAddress = new[] { TestItem.PrivateKeyA.Address, TestItem.PrivateKeyD.Address }; - traceFilterRequest.ToAddress = new[] { TestItem.AddressC, TestItem.AddressA, TestItem.AddressB }; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - UInt256 currentNonceAddressC = blockchain.State.GetAccount(TestItem.AddressC).Nonce; - UInt256 currentNonceAddressD = blockchain.State.GetAccount(TestItem.AddressD).Nonce; - // first block skipped: After 3 -> 1 - await blockchain.AddBlock( - new[] - { - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressD) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // skipped - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // --After - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressB) // --After - .SignedAndResolved(TestItem.PrivateKeyA).TestObject - } - ); - // second block: After 1 -> 0, Count 4 -> 3 - await blockchain.AddBlock( - new[] - { - Build.A.Transaction.WithNonce(currentNonceAddressC++).WithTo(TestItem.AddressA) - .SignedAndResolved(TestItem.PrivateKeyC).TestObject, // skipped - Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyD).TestObject, // --After - Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressB) - .SignedAndResolved(TestItem.PrivateKeyD).TestObject, // --Count - Build.A.Transaction.WithNonce(currentNonceAddressD++) - .SignedAndResolved(TestItem.PrivateKeyD).TestObject // skipped - } - ); - // third block: Count 3 -> 1 - await blockchain.AddBlock( - new[] - { - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressB) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // --Count - Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressD) - .SignedAndResolved(TestItem.PrivateKeyD).TestObject, // skipped - Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyD).TestObject // skipped - } - ); - // fourth block: Count 1 -> 0 - await blockchain.AddBlock( - new[] - { - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressD) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // skipped - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // --Count - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject // skipped (Count == 0) - } - ); - // the last block: skipped - await blockchain.AddBlock( - new[] - { - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, - Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressB) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject - } - ); - await blockchain.AddBlock(); - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - Assert.That(traces.Data.Count(), Is.EqualTo(traceFilterRequest.Count)); - } + } + [Test] + public async Task Trace_filter_with_filtering_by_sender_and_receiver() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + await context.Blockchain.AddBlock( + new[] + { + Build.A.Transaction.WithNonce(currentNonceAddressA + 1).WithTo(TestItem.AddressD) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject, + Build.A.Transaction.WithNonce(blockchain.State.GetAccount(TestItem.AddressB).Nonce + 1).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject, + Build.A.Transaction.WithNonce(currentNonceAddressA).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject + } + ); + await context.Blockchain.AddBlock(); + TraceFilterForRpc traceFilterRequest = new(); + long lastBLockNumber = blockchain.BlockTree.Head!.Number; + traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber - 1); + traceFilterRequest.ToBlock = BlockParameter.Latest; + traceFilterRequest.FromAddress = new[] { TestItem.PrivateKeyA.Address }; + traceFilterRequest.ToAddress = new[] { TestItem.AddressC }; + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + Assert.That(traces.Data.Count(), Is.EqualTo(1)); + } + [Test] + public async Task Trace_filter_complex_scenario() + { + Context context = new(); + await context.Build(); + TraceFilterForRpc traceFilterRequest = new(); + TestRpcBlockchain blockchain = context.Blockchain; + long lastBLockNumber = blockchain.BlockTree.Head!.Number; + traceFilterRequest.After = 3; + traceFilterRequest.Count = 4; + traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber + 1); + traceFilterRequest.ToBlock = BlockParameter.Latest; + traceFilterRequest.FromAddress = new[] { TestItem.PrivateKeyA.Address, TestItem.PrivateKeyD.Address }; + traceFilterRequest.ToAddress = new[] { TestItem.AddressC, TestItem.AddressA, TestItem.AddressB }; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + UInt256 currentNonceAddressC = blockchain.State.GetAccount(TestItem.AddressC).Nonce; + UInt256 currentNonceAddressD = blockchain.State.GetAccount(TestItem.AddressD).Nonce; + // first block skipped: After 3 -> 1 + await blockchain.AddBlock( + new[] + { + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressD) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // skipped + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // --After + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressB) // --After + .SignedAndResolved(TestItem.PrivateKeyA).TestObject + } + ); + // second block: After 1 -> 0, Count 4 -> 3 + await blockchain.AddBlock( + new[] + { + Build.A.Transaction.WithNonce(currentNonceAddressC++).WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyC).TestObject, // skipped + Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyD).TestObject, // --After + Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressB) + .SignedAndResolved(TestItem.PrivateKeyD).TestObject, // --Count + Build.A.Transaction.WithNonce(currentNonceAddressD++) + .SignedAndResolved(TestItem.PrivateKeyD).TestObject // skipped + } + ); + // third block: Count 3 -> 1 + await blockchain.AddBlock( + new[] + { + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressB) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // --Count + Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressD) + .SignedAndResolved(TestItem.PrivateKeyD).TestObject, // skipped + Build.A.Transaction.WithNonce(currentNonceAddressD++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyD).TestObject // skipped + } + ); + // fourth block: Count 1 -> 0 + await blockchain.AddBlock( + new[] + { + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressD) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // skipped + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject, // --Count + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject // skipped (Count == 0) + } + ); + // the last block: skipped + await blockchain.AddBlock( + new[] + { + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject, + Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressB) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject + } + ); + await blockchain.AddBlock(); + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + Assert.That(traces.Data.Count(), Is.EqualTo(traceFilterRequest.Count)); + } - [Test] - public async Task Trace_filter_complex_scenario_openethereum() - { - Context context = new(); - await context.Build(); - TraceFilterForRpc traceFilterRequest = new(); - TestRpcBlockchain blockchain = context.Blockchain; - long lastBLockNumber = blockchain.BlockTree.Head!.Number; - // traceFilterRequest.After = 3; - // traceFilterRequest.Count = 4; - traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber + 1); - traceFilterRequest.ToBlock = BlockParameter.Latest; - // traceFilterRequest.FromAddress = new[] {TestItem.PrivateKeyA.Address, TestItem.PrivateKeyD.Address}; - // traceFilterRequest.ToAddress = new[] {TestItem.AddressC, TestItem.AddressA, TestItem.AddressB}; - UInt256 currentNonceAddressC = blockchain.State.GetAccount(TestItem.AddressC).Nonce; - await blockchain.AddBlock(); - await blockchain.AddBlock(); - await blockchain.AddBlock( - new[] - { - Build.A.Transaction.WithNonce(currentNonceAddressC++).WithTo(TestItem.AddressA) - .SignedAndResolved(TestItem.PrivateKeyC).TestObject, - } - ); - ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); - Assert.That(traces.Data.Count(), Is.EqualTo(4)); - } + [Test] + public async Task Trace_filter_complex_scenario_openethereum() + { + Context context = new(); + await context.Build(); + TraceFilterForRpc traceFilterRequest = new(); + TestRpcBlockchain blockchain = context.Blockchain; + long lastBLockNumber = blockchain.BlockTree.Head!.Number; + // traceFilterRequest.After = 3; + // traceFilterRequest.Count = 4; + traceFilterRequest.FromBlock = new BlockParameter(lastBLockNumber + 1); + traceFilterRequest.ToBlock = BlockParameter.Latest; + // traceFilterRequest.FromAddress = new[] {TestItem.PrivateKeyA.Address, TestItem.PrivateKeyD.Address}; + // traceFilterRequest.ToAddress = new[] {TestItem.AddressC, TestItem.AddressA, TestItem.AddressB}; + UInt256 currentNonceAddressC = blockchain.State.GetAccount(TestItem.AddressC).Nonce; + await blockchain.AddBlock(); + await blockchain.AddBlock(); + await blockchain.AddBlock( + new[] + { + Build.A.Transaction.WithNonce(currentNonceAddressC++).WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyC).TestObject, + } + ); + ResultWrapper> traces = context.TraceRpcModule.trace_filter(traceFilterRequest); + Assert.That(traces.Data.Count(), Is.EqualTo(4)); + } - [Test] - public async Task trace_transaction_and_get_simple_tx() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject; - await blockchain.AddBlock(transaction); - ResultWrapper> traces = context.TraceRpcModule.trace_transaction(transaction.Hash!); - traces.Data.Should().BeEquivalentTo(new[] { new { TransactionHash = transaction.Hash } }, o => o.Including(o => o.TransactionHash)); - - long[] positions = { 0 }; - ResultWrapper> traceGet = context.TraceRpcModule.trace_get(transaction.Hash!, positions); - traceGet.Data.Should().BeEmpty(); - } + [Test] + public async Task trace_transaction_and_get_simple_tx() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject; + await blockchain.AddBlock(transaction); + ResultWrapper> traces = context.TraceRpcModule.trace_transaction(transaction.Hash!); + traces.Data.Should().BeEquivalentTo(new[] { new { TransactionHash = transaction.Hash } }, o => o.Including(o => o.TransactionHash)); + + long[] positions = { 0 }; + ResultWrapper> traceGet = context.TraceRpcModule.trace_get(transaction.Hash!, positions); + traceGet.Data.Should().BeEmpty(); + } - [Test] - public async Task Trace_get_can_trace_simple_tx() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - - Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject; - await blockchain.AddBlock(transaction); - - long[] positions = { 0 }; - ResultWrapper> traces = context.TraceRpcModule.trace_get(transaction.Hash!, positions); - traces.Data.Should().BeEmpty(); - } + [Test] + public async Task Trace_get_can_trace_simple_tx() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + + Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject; + await blockchain.AddBlock(transaction); + + long[] positions = { 0 }; + ResultWrapper> traces = context.TraceRpcModule.trace_get(transaction.Hash!, positions); + traces.Data.Should().BeEmpty(); + } - [Test] - public async Task trace_transaction_and_get_internal_tx() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - byte[] deployedCode = new byte[3]; - byte[] initCode = Prepare.EvmCode - .ForInitOf(deployedCode) - .Done; - byte[] createCode = Prepare.EvmCode - .Create(initCode, 0) - .Op(Instruction.STOP) - .Done; - - Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) - .WithData(createCode) - .WithTo(null) - .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; - await blockchain.AddBlock(transaction1); - Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); - byte[] code = Prepare.EvmCode - .Call(contractAddress, 50000) - .Call(contractAddress, 50000) - .Op(Instruction.STOP) - .Done; - - Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) - .WithTo(null) - .WithGasLimit(93548).TestObject; - await blockchain.AddBlock(transaction2); - - ResultWrapper> traces = context.TraceRpcModule.trace_transaction(transaction2.Hash!); - traces.Data.Should().HaveCount(3); - traces.Data.ElementAt(0).TransactionHash.Should().Be(transaction2.Hash!); - - long[] positions = { 0 }; - ResultWrapper> tracesGet = context.TraceRpcModule.trace_get(transaction2.Hash!, positions); - traces.Data.ElementAt(0).TransactionHash.Should().BeEquivalentTo(transaction2.Hash); - traces.Data.ElementAt(1).Should().BeEquivalentTo(tracesGet.Data.ElementAt(0)); - } + [Test] + public async Task trace_transaction_and_get_internal_tx() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + byte[] deployedCode = new byte[3]; + byte[] initCode = Prepare.EvmCode + .ForInitOf(deployedCode) + .Done; + byte[] createCode = Prepare.EvmCode + .Create(initCode, 0) + .Op(Instruction.STOP) + .Done; + + Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) + .WithData(createCode) + .WithTo(null) + .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; + await blockchain.AddBlock(transaction1); + Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); + byte[] code = Prepare.EvmCode + .Call(contractAddress, 50000) + .Call(contractAddress, 50000) + .Op(Instruction.STOP) + .Done; + + Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) + .WithTo(null) + .WithGasLimit(93548).TestObject; + await blockchain.AddBlock(transaction2); + + ResultWrapper> traces = context.TraceRpcModule.trace_transaction(transaction2.Hash!); + traces.Data.Should().HaveCount(3); + traces.Data.ElementAt(0).TransactionHash.Should().Be(transaction2.Hash!); + + long[] positions = { 0 }; + ResultWrapper> tracesGet = context.TraceRpcModule.trace_get(transaction2.Hash!, positions); + traces.Data.ElementAt(0).TransactionHash.Should().BeEquivalentTo(transaction2.Hash); + traces.Data.ElementAt(1).Should().BeEquivalentTo(tracesGet.Data.ElementAt(0)); + } - [Test] - public async Task Trace_transaction_with_error_reverted() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - byte[] deployedCode = new byte[3]; - byte[] initCode = Prepare.EvmCode - .ForInitOf(deployedCode) - .Done; - byte[] createCode = Prepare.EvmCode - .Create(initCode, 0) - .Op(Instruction.STOP) - .Done; - - Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) - .WithData(createCode) - .WithTo(null) - .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; - await blockchain.AddBlock(transaction1); - Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); - byte[] code = Prepare.EvmCode - .Call(contractAddress, 50000) - .Call(contractAddress, 50000) - .Op(Instruction.REVERT) - .Done; - - Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) - .WithTo(null) - .WithGasLimit(93548).TestObject; - await blockchain.AddBlock(transaction2); - ResultWrapper> traces = context.TraceRpcModule.trace_transaction(transaction2.Hash!); - traces.Data.Should().HaveCount(3); - traces.Data.ElementAt(0).TransactionHash.Should().Be(transaction2.Hash!); - string serialized = new EthereumJsonSerializer().Serialize(traces.Data); - - Assert.That(serialized, Is.EqualTo("[{\"action\":{\"traceAddress\":[],\"callType\":\"create\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"create\",\"creationMethod\":\"create\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"to\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"gas\":\"0x9a6c\",\"value\":\"0x1\",\"input\":\"0x60006000600060006000736b5887043de753ecfa6269f947129068263ffbe261c350f160006000600060006000736b5887043de753ecfa6269f947129068263ffbe261c350f1fd\",\"subtraces\":[{\"traceAddress\":[0],\"callType\":\"call\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"call\",\"from\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"gas\":\"0x2dcd\",\"value\":\"0x0\",\"input\":\"0x\",\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":[]},{\"traceAddress\":[1],\"callType\":\"call\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"call\",\"from\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"gas\":\"0x2d56\",\"value\":\"0x0\",\"input\":\"0x\",\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":[]}],\"error\":\"Reverted\"},\"blockHash\":\"0xfa74e932520ee416ecb12171c115b3ad14112ffd2d612646ceaff69e54e06a94\",\"blockNumber\":18,\"subtraces\":2,\"traceAddress\":[],\"transactionHash\":\"0x787616b8756424622f162fc3817331517ef941366f28db452defc0214bc36b22\",\"transactionPosition\":0,\"type\":\"create\",\"error\":\"Reverted\"},{\"action\":{\"traceAddress\":[0],\"callType\":\"call\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"call\",\"from\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"gas\":\"0x2dcd\",\"value\":\"0x0\",\"input\":\"0x\",\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":[]},\"blockHash\":\"0xfa74e932520ee416ecb12171c115b3ad14112ffd2d612646ceaff69e54e06a94\",\"blockNumber\":18,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[0],\"transactionHash\":\"0x787616b8756424622f162fc3817331517ef941366f28db452defc0214bc36b22\",\"transactionPosition\":0,\"type\":\"call\"},{\"action\":{\"traceAddress\":[1],\"callType\":\"call\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"call\",\"from\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"gas\":\"0x2d56\",\"value\":\"0x0\",\"input\":\"0x\",\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":[]},\"blockHash\":\"0xfa74e932520ee416ecb12171c115b3ad14112ffd2d612646ceaff69e54e06a94\",\"blockNumber\":18,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[1],\"transactionHash\":\"0x787616b8756424622f162fc3817331517ef941366f28db452defc0214bc36b22\",\"transactionPosition\":0,\"type\":\"call\"}]"), serialized.Replace("\"", "\\\"")); - } - [Test] - public async Task trace_timeout_is_separate_for_rpc_calls() - { - Context context = new(); - await context.Build(); - IJsonRpcConfig jsonRpcConfig = context.JsonRpcConfig; - jsonRpcConfig.Timeout = 25; - ITraceRpcModule traceRpcModule = context.TraceRpcModule; - BlockParameter searchParameter = new(number: 0); - Assert.DoesNotThrow(() => traceRpcModule.trace_block(searchParameter)); - await Task.Delay(jsonRpcConfig.Timeout + - 25); //additional second just to show that in this time span timeout should occur if given one for whole class - Assert.DoesNotThrow(() => traceRpcModule.trace_block(searchParameter)); - } - [Test] - public async Task Trace_replayTransaction_test() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - byte[] deployedCode = new byte[3]; - byte[] initCode = Prepare.EvmCode - .ForInitOf(deployedCode) - .Done; - - Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); - byte[] code = Prepare.EvmCode - .Call(contractAddress, 50000) - .Call(contractAddress, 50000) - .Op(Instruction.STOP) - .Done; - - Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressB++) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) - .WithTo(TestItem.AddressC) - .WithGasLimit(93548).TestObject; - await blockchain.AddBlock(transaction); - string[] traceTypes = { "trace" }; - ResultWrapper traces = context.TraceRpcModule.trace_replayTransaction(transaction.Hash!, traceTypes); - Assert.That(traces.Data.Action!.From, Is.EqualTo(TestItem.AddressB)); - Assert.That(traces.Data.Action.To, Is.EqualTo(TestItem.AddressC)); - Assert.That(traces.Data.Action.CallType, Is.EqualTo("call")); - Assert.IsTrue(traces.Result == Result.Success); - } + [Test] + public async Task Trace_transaction_with_error_reverted() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + byte[] deployedCode = new byte[3]; + byte[] initCode = Prepare.EvmCode + .ForInitOf(deployedCode) + .Done; + byte[] createCode = Prepare.EvmCode + .Create(initCode, 0) + .Op(Instruction.STOP) + .Done; + + Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) + .WithData(createCode) + .WithTo(null) + .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; + await blockchain.AddBlock(transaction1); + Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); + byte[] code = Prepare.EvmCode + .Call(contractAddress, 50000) + .Call(contractAddress, 50000) + .Op(Instruction.REVERT) + .Done; + + Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) + .WithTo(null) + .WithGasLimit(93548).TestObject; + await blockchain.AddBlock(transaction2); + ResultWrapper> traces = context.TraceRpcModule.trace_transaction(transaction2.Hash!); + traces.Data.Should().HaveCount(3); + traces.Data.ElementAt(0).TransactionHash.Should().Be(transaction2.Hash!); + string serialized = new EthereumJsonSerializer().Serialize(traces.Data); + + Assert.That(serialized, Is.EqualTo("[{\"action\":{\"creationMethod\":\"create\",\"from\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"gas\":\"0x9a6c\",\"input\":\"0x60006000600060006000736b5887043de753ecfa6269f947129068263ffbe261c350f160006000600060006000736b5887043de753ecfa6269f947129068263ffbe261c350f1fd\",\"to\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"value\":\"0x1\"},\"blockHash\":\"0xfa74e932520ee416ecb12171c115b3ad14112ffd2d612646ceaff69e54e06a94\",\"blockNumber\":18,\"subtraces\":2,\"traceAddress\":[],\"transactionHash\":\"0x787616b8756424622f162fc3817331517ef941366f28db452defc0214bc36b22\",\"transactionPosition\":0,\"type\":\"create\",\"error\":\"Reverted\"},{\"action\":{\"callType\":\"call\",\"from\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"gas\":\"0x2dcd\",\"input\":\"0x\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"value\":\"0x0\"},\"blockHash\":\"0xfa74e932520ee416ecb12171c115b3ad14112ffd2d612646ceaff69e54e06a94\",\"blockNumber\":18,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[0],\"transactionHash\":\"0x787616b8756424622f162fc3817331517ef941366f28db452defc0214bc36b22\",\"transactionPosition\":0,\"type\":\"call\"},{\"action\":{\"callType\":\"call\",\"from\":\"0xd6a48bcd4c5ad5adacfab677519c25ce7b2805a5\",\"gas\":\"0x2d56\",\"input\":\"0x\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"value\":\"0x0\"},\"blockHash\":\"0xfa74e932520ee416ecb12171c115b3ad14112ffd2d612646ceaff69e54e06a94\",\"blockNumber\":18,\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[1],\"transactionHash\":\"0x787616b8756424622f162fc3817331517ef941366f28db452defc0214bc36b22\",\"transactionPosition\":0,\"type\":\"call\"}]"), serialized.Replace("\"", "\\\"")); + } + [Test] + public async Task trace_timeout_is_separate_for_rpc_calls() + { + Context context = new(); + await context.Build(); + IJsonRpcConfig jsonRpcConfig = context.JsonRpcConfig; + jsonRpcConfig.Timeout = 25; + ITraceRpcModule traceRpcModule = context.TraceRpcModule; + BlockParameter searchParameter = new(number: 0); + Assert.DoesNotThrow(() => traceRpcModule.trace_block(searchParameter)); + await Task.Delay(jsonRpcConfig.Timeout + + 25); //additional second just to show that in this time span timeout should occur if given one for whole class + Assert.DoesNotThrow(() => traceRpcModule.trace_block(searchParameter)); + } + [Test] + public async Task Trace_replayTransaction_test() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + byte[] deployedCode = new byte[3]; + byte[] initCode = Prepare.EvmCode + .ForInitOf(deployedCode) + .Done; + + Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); + byte[] code = Prepare.EvmCode + .Call(contractAddress, 50000) + .Call(contractAddress, 50000) + .Op(Instruction.STOP) + .Done; + + Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressB++) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) + .WithTo(TestItem.AddressC) + .WithGasLimit(93548).TestObject; + await blockchain.AddBlock(transaction); + string[] traceTypes = { "trace" }; + ResultWrapper traces = context.TraceRpcModule.trace_replayTransaction(transaction.Hash!, traceTypes); + Assert.That(traces.Data.Action!.From, Is.EqualTo(TestItem.AddressB)); + Assert.That(traces.Data.Action.To, Is.EqualTo(TestItem.AddressC)); + Assert.That(traces.Data.Action.CallType, Is.EqualTo("call")); + Assert.IsTrue(traces.Result.ResultType == ResultType.Success); + } - [Test] - public async Task Trace_replayTransaction_reward_test() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); - byte[] code = Prepare.EvmCode - .Call(contractAddress, 50000) - .Call(contractAddress, 50000) - .Op(Instruction.STOP) - .Done; - - Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressB++) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) - .WithTo(TestItem.AddressC) - .WithGasLimit(93548).TestObject; - await blockchain.AddBlock(transaction); - string[] traceTypes = { "rewards" }; - ResultWrapper traces = context.TraceRpcModule.trace_replayTransaction(transaction.Hash!, traceTypes); - Assert.That(traces.Data.Action!.CallType, Is.EqualTo("reward")); - Assert.That(traces.Data.Action.Value, Is.EqualTo(UInt256.Parse("2000000000000000000"))); - Assert.IsTrue(traces.Result == Result.Success); - } + [Test] + public async Task Trace_replayTransaction_reward_test() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); + byte[] code = Prepare.EvmCode + .Call(contractAddress, 50000) + .Call(contractAddress, 50000) + .Op(Instruction.STOP) + .Done; + + Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressB++) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) + .WithTo(TestItem.AddressC) + .WithGasLimit(93548).TestObject; + await blockchain.AddBlock(transaction); + string[] traceTypes = { "rewards" }; + ResultWrapper traces = context.TraceRpcModule.trace_replayTransaction(transaction.Hash!, traceTypes); + Assert.That(traces.Data.Action!.CallType, Is.EqualTo("reward")); + Assert.That(traces.Data.Action.Value, Is.EqualTo(UInt256.Parse("2000000000000000000"))); + Assert.IsTrue(traces.Result.ResultType == ResultType.Success); + } - [Test] - public async Task trace_replayBlockTransactions_zeroGasUsed_test() - { - Context context = new(); - OverridableReleaseSpec releaseSpec = new(London.Instance); - releaseSpec.Eip1559TransitionBlock = 1; - TestSpecProvider specProvider = new(releaseSpec); - await context.Build(specProvider, isAura: true); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressC = blockchain.State.GetAccount(TestItem.AddressC).Nonce; - - Transaction serviceTransaction = Build.A.Transaction.WithNonce(currentNonceAddressC++) - .WithTo(TestItem.AddressE) - .SignedAndResolved(TestItem.PrivateKeyC) - .WithIsServiceTransaction(true).TestObject; - await blockchain.AddBlock(serviceTransaction); - BlockParameter blockParameter = new BlockParameter(BlockParameterType.Latest); - string[] traceTypes = { "trace" }; - ResultWrapper> traces = context.TraceRpcModule.trace_replayBlockTransactions(blockParameter, traceTypes); - traces.Data.First().Action!.Result!.GasUsed.Should().Be(0); - } + [Test] + public async Task trace_replayBlockTransactions_zeroGasUsed_test() + { + Context context = new(); + OverridableReleaseSpec releaseSpec = new(London.Instance); + releaseSpec.Eip1559TransitionBlock = 1; + TestSpecProvider specProvider = new(releaseSpec); + await context.Build(specProvider, isAura: true); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressC = blockchain.State.GetAccount(TestItem.AddressC).Nonce; + + Transaction serviceTransaction = Build.A.Transaction.WithNonce(currentNonceAddressC++) + .WithTo(TestItem.AddressE) + .SignedAndResolved(TestItem.PrivateKeyC) + .WithIsServiceTransaction(true).TestObject; + await blockchain.AddBlock(serviceTransaction); + BlockParameter blockParameter = new BlockParameter(BlockParameterType.Latest); + string[] traceTypes = { "trace" }; + ResultWrapper> traces = context.TraceRpcModule.trace_replayBlockTransactions(blockParameter, traceTypes); + traces.Data.First().Action!.Result!.GasUsed.Should().Be(0); + } - [Test] - public async Task Trace_call_without_blockParameter_provided_test() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - - Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); - byte[] code = Prepare.EvmCode - .Call(contractAddress, 50000) - .Call(contractAddress, 50000) - .Op(Instruction.STOP) - .Done; - - Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressB++) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) - .WithTo(TestItem.AddressC) - .WithGasLimit(93548).TestObject; - await blockchain.AddBlock(transaction); - - - Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) - .WithTo(TestItem.AddressC) - .WithGasLimit(93548).TestObject; - await blockchain.AddBlock(transaction2); - - TransactionForRpc transactionRpc = new(transaction2); - - string[] traceTypes = { "trace" }; - - ResultWrapper traces = context.TraceRpcModule.trace_call(transactionRpc, traceTypes); - Assert.That(traces.Data.Action!.CallType, Is.EqualTo("call")); - Assert.That(traces.Data.Action.From, Is.EqualTo(TestItem.AddressB)); - Assert.That(traces.Data.Action.To, Is.EqualTo(TestItem.AddressC)); - } + [Test] + public async Task Trace_call_without_blockParameter_provided_test() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + UInt256 currentNonceAddressB = blockchain.State.GetAccount(TestItem.AddressB).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + + Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); + byte[] code = Prepare.EvmCode + .Call(contractAddress, 50000) + .Call(contractAddress, 50000) + .Op(Instruction.STOP) + .Done; + + Transaction transaction = Build.A.Transaction.WithNonce(currentNonceAddressB++) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) + .WithTo(TestItem.AddressC) + .WithGasLimit(93548).TestObject; + await blockchain.AddBlock(transaction); + + + Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressB++) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyB) + .WithTo(TestItem.AddressC) + .WithGasLimit(93548).TestObject; + await blockchain.AddBlock(transaction2); + + TransactionForRpc transactionRpc = new(transaction2); + + string[] traceTypes = { "trace" }; + + ResultWrapper traces = context.TraceRpcModule.trace_call(transactionRpc, traceTypes); + Assert.That(traces.Data.Action!.CallType, Is.EqualTo("call")); + Assert.That(traces.Data.Action.From, Is.EqualTo(TestItem.AddressB)); + Assert.That(traces.Data.Action.To, Is.EqualTo(TestItem.AddressC)); + } - [Test] - public async Task Trace_call_simple_tx_test() - { - Context context = new(); - await context.Build(); - string transaction = "{\"from\":\"0xaaaaaaaa8583de65cc752fe3fad5098643244d22\",\"to\":\"0xd6a8d04cb9846759416457e2c593c99390092df6\"}"; - string traceTypes = "[\"trace\"]"; - string blockParameter = "latest"; - string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xaaaaaaaa8583de65cc752fe3fad5098643244d22\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xd6a8d04cb9846759416457e2c593c99390092df6\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}"; - - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_call", transaction, traceTypes, blockParameter); - - Assert.That(serialized, Is.EqualTo(expectedResult), serialized.Replace("\"", "\\\"")); - } + [Test] + public async Task Trace_call_simple_tx_test() + { + Context context = new(); + await context.Build(); + string transaction = "{\"from\":\"0xaaaaaaaa8583de65cc752fe3fad5098643244d22\",\"to\":\"0xd6a8d04cb9846759416457e2c593c99390092df6\"}"; + string traceTypes = "[\"trace\"]"; + string blockParameter = "latest"; + string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xaaaaaaaa8583de65cc752fe3fad5098643244d22\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xd6a8d04cb9846759416457e2c593c99390092df6\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}"; + + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_call", transaction, traceTypes, blockParameter); + + Assert.That(serialized, Is.EqualTo(expectedResult), serialized.Replace("\"", "\\\"")); + } - [TestCase("{\"from\":\"0x7f554713be84160fdf0178cc8df86f5aabd33397\",\"to\":\"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f\",\"value\":\"0x0\",\"gasPrice\":\"0x119e04a40a\"}", "[\"trace\"]", "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x7f554713be84160fdf0178cc8df86f5aabd33397\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}")] - [TestCase("{\"from\":\"0xc71acc7863f3bc7347b24c3b835643bd89d4d161\",\"to\":\"0xa760e26aa76747020171fcf8bda108dfde8eb930\",\"value\":\"0x0\",\"gasPrice\":\"0x2108eea5bc\"}", "[\"trace\"]", "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xc71acc7863f3bc7347b24c3b835643bd89d4d161\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xa760e26aa76747020171fcf8bda108dfde8eb930\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}")] - public async Task Trace_call_without_blockParameter_test(string transaction, string traceTypes, string expectedResult) - { - Context context = new(); - await context.Build(); - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_call", transaction, traceTypes); + [TestCase("{\"from\":\"0x7f554713be84160fdf0178cc8df86f5aabd33397\",\"to\":\"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f\",\"value\":\"0x0\",\"gasPrice\":\"0x119e04a40a\"}", "[\"trace\"]", "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x7f554713be84160fdf0178cc8df86f5aabd33397\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}")] + [TestCase("{\"from\":\"0xc71acc7863f3bc7347b24c3b835643bd89d4d161\",\"to\":\"0xa760e26aa76747020171fcf8bda108dfde8eb930\",\"value\":\"0x0\",\"gasPrice\":\"0x2108eea5bc\"}", "[\"trace\"]", "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xc71acc7863f3bc7347b24c3b835643bd89d4d161\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xa760e26aa76747020171fcf8bda108dfde8eb930\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}")] + public async Task Trace_call_without_blockParameter_test(string transaction, string traceTypes, string expectedResult) + { + Context context = new(); + await context.Build(); + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_call", transaction, traceTypes); - Assert.That(serialized, Is.EqualTo(expectedResult), serialized.Replace("\"", "\\\"")); - } + Assert.That(serialized, Is.EqualTo(expectedResult), serialized.Replace("\"", "\\\"")); + } - [Test] - public async Task Trace_callMany_internal_transactions_test() - { - Context context = new(); - await context.Build(); + [Test] + public async Task Trace_callMany_internal_transactions_test() + { + Context context = new(); + await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject; - TransactionForRpc txForRpc1 = new(transaction1); - string[] traceTypes1 = { "Trace" }; + Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressC) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject; + TransactionForRpc txForRpc1 = new(transaction1); + string[] traceTypes1 = { "Trace" }; - Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressD) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject; - await blockchain.AddBlock(transaction1, transaction2); + Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressA++).WithTo(TestItem.AddressD) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject; + await blockchain.AddBlock(transaction1, transaction2); - TransactionForRpc txForRpc2 = new(transaction2); - string[] traceTypes2 = { "Trace" }; + TransactionForRpc txForRpc2 = new(transaction2); + string[] traceTypes2 = { "Trace" }; - BlockParameter numberOrTag = new(16); - TransactionForRpcWithTraceTypes tr1 = new(); - TransactionForRpcWithTraceTypes tr2 = new(); - tr1.Transaction = txForRpc1; - tr1.TraceTypes = traceTypes1; - tr2.Transaction = txForRpc2; - tr2.TraceTypes = traceTypes2; + BlockParameter numberOrTag = new(16); + TransactionForRpcWithTraceTypes tr1 = new(); + TransactionForRpcWithTraceTypes tr2 = new(); + tr1.Transaction = txForRpc1; + tr1.TraceTypes = traceTypes1; + tr2.Transaction = txForRpc2; + tr2.TraceTypes = traceTypes2; - TransactionForRpcWithTraceTypes[] a = { tr1, tr2 }; + TransactionForRpcWithTraceTypes[] a = { tr1, tr2 }; - ResultWrapper> tr = context.TraceRpcModule.trace_callMany(a, numberOrTag); - tr.Data.Should().HaveCount(2); - } + ResultWrapper> tr = context.TraceRpcModule.trace_callMany(a, numberOrTag); + tr.Data.Should().HaveCount(2); + } - [Test] - public async Task Trace_callMany_is_blockParameter_optional_test() - { - Context context = new(); - await context.Build(); - string calls = "[[{\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\"},[\"trace\"]],[{\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"gasPrice\":\"0xb2b29a6dc\"},[\"trace\"]]]"; + [Test] + public async Task Trace_callMany_is_blockParameter_optional_test() + { + Context context = new(); + await context.Build(); + string calls = "[[{\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\"},[\"trace\"]],[{\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"gasPrice\":\"0xb2b29a6dc\"},[\"trace\"]]]"; - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_callMany", calls, "latest"); + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_callMany", calls, "latest"); - string serialized_without_blockParameter_param = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_callMany", calls); + string serialized_without_blockParameter_param = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_callMany", calls); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); - Assert.That(serialized_without_blockParameter_param, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null}],\"id\":67}"), serialized_without_blockParameter_param.Replace("\"", "\\\"")); - } + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); + Assert.That(serialized_without_blockParameter_param, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"gas\":\"0x5f58ef8\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null}],\"id\":67}"), serialized_without_blockParameter_param.Replace("\"", "\\\"")); + } - [Test] - public async Task Trace_callMany_accumulates_state_changes() - { - Context context = new(); - await context.Build(); + [Test] + public async Task Trace_callMany_accumulates_state_changes() + { + Context context = new(); + await context.Build(); - string calls = $"[[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\"}},[\"statediff\"]],[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\"}},[\"statediff\"]]]"; + string calls = $"[[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\"}},[\"statediff\"]],[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"1\"}},[\"statediff\"]]]"; - string serialized = await RpcTest.TestSerializedRequest( - EthModuleFactory.Converters.Union(TraceModuleFactory.Converters).ToList(), context.TraceRpcModule, - "trace_callMany", calls); + string serialized = await RpcTest.TestSerializedRequest( + context.TraceRpcModule, + "trace_callMany", calls); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x24\",\"to\":\"0x25\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e5\",\"to\":\"0x3635c9adc5de9f09e4\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x3\",\"to\":\"0x4\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null},{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x25\",\"to\":\"0x26\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e4\",\"to\":\"0x3635c9adc5de9f09e3\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x4\",\"to\":\"0x5\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); - } + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x24\",\"to\":\"0x25\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e5\",\"to\":\"0x3635c9adc5de9f09e4\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x3\",\"to\":\"0x4\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null},{\"output\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000\":{\"balance\":{\"*\":{\"from\":\"0x25\",\"to\":\"0x26\"}},\"code\":\"=\",\"nonce\":\"=\",\"storage\":{}},\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\":{\"balance\":{\"*\":{\"from\":\"0x3635c9adc5de9f09e4\",\"to\":\"0x3635c9adc5de9f09e3\"}},\"code\":\"=\",\"nonce\":{\"*\":{\"from\":\"0x4\",\"to\":\"0x5\"}},\"storage\":{}}},\"trace\":[],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); + } - [Test] - public async Task Trace_replayBlockTransactions_transactions_deploying_contract() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); - - byte[] code = Prepare.EvmCode - .Call(contractAddress, 50000) - .Done; - - Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) - .WithSenderAddress(TestItem.AddressA) - .WithData(code) - .WithTo(null) - .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; - - Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressA++) - .WithSenderAddress(TestItem.AddressA) - .WithData(code).SignedAndResolved(TestItem.PrivateKeyA) - .WithTo(null) - .WithGasLimit(93548).TestObject; - - string[] traceTypes = { "trace" }; - - await blockchain.AddBlock(transaction1, transaction2); - - ResultWrapper> traces = context.TraceRpcModule.trace_replayBlockTransactions(new BlockParameter(blockchain.BlockFinder.FindLatestBlock()!.Number), traceTypes); - traces.Data.Should().HaveCount(2); - traces.Data.ElementAt(0).Action!.From.Should().BeEquivalentTo(traces.Data.ElementAt(1).Action!.From); - string serialized = new EthereumJsonSerializer().Serialize(traces.Data); - Assert.That(serialized, Is.EqualTo("[{\"output\":\"0x\",\"transactionHash\":\"0x8513c9083ec27fa8e3ca7e3ffa732d61562e2d17e2e1af6e773bc810dc4c3452\",\"action\":{\"traceAddress\":[],\"callType\":\"create\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"create\",\"creationMethod\":\"create\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"gas\":\"0x9c70\",\"value\":\"0x1\",\"input\":\"0x60006000600060006000730ffd3e46594919c04bcfd4e146203c825567082861c350f1\",\"result\":{\"gasUsed\":\"0x79\",\"output\":\"0x\",\"address\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"code\":\"0x\"},\"subtraces\":[{\"traceAddress\":[0],\"callType\":\"call\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"call\",\"from\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"to\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"gas\":\"0x9988\",\"value\":\"0x0\",\"input\":\"0x\",\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":[]}]}},{\"output\":\"0x\",\"transactionHash\":\"0xa6a56c7927deae778a749bcdab7bbf409c0d8a5d2420021a3ba328240ae832d8\",\"action\":{\"traceAddress\":[],\"callType\":\"create\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"create\",\"creationMethod\":\"create\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"gas\":\"0x9c70\",\"value\":\"0x1\",\"input\":\"0x60006000600060006000730ffd3e46594919c04bcfd4e146203c825567082861c350f1\",\"result\":{\"gasUsed\":\"0xa3d\",\"output\":\"0x\",\"address\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"code\":\"0x\"},\"subtraces\":[{\"traceAddress\":[0],\"callType\":\"call\",\"includeInTrace\":true,\"isPrecompiled\":false,\"type\":\"call\",\"from\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"to\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"gas\":\"0x8feb\",\"value\":\"0x0\",\"input\":\"0x\",\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":[]}]}}]")); - } + [Test] + public async Task Trace_replayBlockTransactions_transactions_deploying_contract() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + Address? contractAddress = ContractAddress.From(TestItem.AddressA, currentNonceAddressA); + + byte[] code = Prepare.EvmCode + .Call(contractAddress, 50000) + .Done; + + Transaction transaction1 = Build.A.Transaction.WithNonce(currentNonceAddressA++) + .WithSenderAddress(TestItem.AddressA) + .WithData(code) + .WithTo(null) + .WithGasLimit(93548).SignedAndResolved(TestItem.PrivateKeyA).TestObject; + + Transaction transaction2 = Build.A.Transaction.WithNonce(currentNonceAddressA++) + .WithSenderAddress(TestItem.AddressA) + .WithData(code).SignedAndResolved(TestItem.PrivateKeyA) + .WithTo(null) + .WithGasLimit(93548).TestObject; + + string[] traceTypes = { "trace" }; + + await blockchain.AddBlock(transaction1, transaction2); + + ResultWrapper> traces = context.TraceRpcModule.trace_replayBlockTransactions(new BlockParameter(blockchain.BlockFinder.FindLatestBlock()!.Number), traceTypes); + traces.Data.Should().HaveCount(2); + traces.Data.ElementAt(0).Action!.From.Should().BeEquivalentTo(traces.Data.ElementAt(1).Action!.From); + string serialized = new EthereumJsonSerializer().Serialize(traces.Data); + Assert.That(serialized, Is.EqualTo("[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"creationMethod\":\"create\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"gas\":\"0x9c70\",\"input\":\"0x60006000600060006000730ffd3e46594919c04bcfd4e146203c825567082861c350f1\",\"to\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"value\":\"0x1\"},\"result\":{\"address\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"code\":\"0x\",\"gasUsed\":\"0x79\"},\"subtraces\":1,\"traceAddress\":[],\"type\":\"create\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"gas\":\"0x9988\",\"input\":\"0x\",\"to\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[0],\"type\":\"call\"}],\"transactionHash\":\"0x8513c9083ec27fa8e3ca7e3ffa732d61562e2d17e2e1af6e773bc810dc4c3452\",\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"creationMethod\":\"create\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"gas\":\"0x9c70\",\"input\":\"0x60006000600060006000730ffd3e46594919c04bcfd4e146203c825567082861c350f1\",\"to\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"value\":\"0x1\"},\"result\":{\"address\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"code\":\"0x\",\"gasUsed\":\"0xa3d\"},\"subtraces\":1,\"traceAddress\":[],\"type\":\"create\"},{\"action\":{\"callType\":\"call\",\"from\":\"0x6b5887043de753ecfa6269f947129068263ffbe2\",\"gas\":\"0x8feb\",\"input\":\"0x\",\"to\":\"0x0ffd3e46594919c04bcfd4e146203c8255670828\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[0],\"type\":\"call\"}],\"transactionHash\":\"0xa6a56c7927deae778a749bcdab7bbf409c0d8a5d2420021a3ba328240ae832d8\",\"vmTrace\":null}]")); + } - [Test] - public async Task Trace_replayBlockTransactions_stateDiff() - { - Context context = new(); - await context.Build(); - TestRpcBlockchain blockchain = context.Blockchain; - UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; - await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); - - Transaction tx = Build.A.Transaction.WithNonce(currentNonceAddressA++) - .WithValue(10) - .WithTo(TestItem.AddressF) - .WithGasLimit(50000) - .WithGasPrice(10).SignedAndResolved(TestItem.PrivateKeyA).TestObject; - - Account accountA = blockchain.State.GetAccount(TestItem.AddressA); - Account accountD = blockchain.State.GetAccount(TestItem.AddressD); - Account accountF = blockchain.State.GetAccount(TestItem.AddressF); - - string[] traceTypes = { "stateDiff" }; - - await blockchain.AddBlock(tx); - - ResultWrapper> traces = context.TraceRpcModule.trace_replayBlockTransactions(new BlockParameter(blockchain.BlockFinder.FindLatestBlock()!.Number), traceTypes); - traces.Data.Should().HaveCount(1); - Dictionary state = traces.Data.ElementAt(0).StateChanges!; - - state.Count.Should().Be(3); - state[TestItem.AddressA].Nonce!.Before.Should().Be(accountA.Nonce); - state[TestItem.AddressD].Balance!.Before.Should().Be(accountD.Balance); - state[TestItem.AddressA].Balance!.Before.Should().Be(accountA.Balance); - state[TestItem.AddressF].Balance!.Before.Should().Be(null); - - state[TestItem.AddressA].Nonce!.After.Should().Be(accountA.Nonce + 1); - state[TestItem.AddressD].Balance!.After.Should().Be(accountD.Balance + 21000 * tx.GasPrice); - state[TestItem.AddressA].Balance!.After.Should().Be(accountA.Balance - 21000 * tx.GasPrice - tx.Value); - state[TestItem.AddressF].Balance!.After.Should().Be(accountF.Balance + tx.Value); - } + [Test] + public async Task Trace_replayBlockTransactions_stateDiff() + { + Context context = new(); + await context.Build(); + TestRpcBlockchain blockchain = context.Blockchain; + UInt256 currentNonceAddressA = blockchain.State.GetAccount(TestItem.AddressA).Nonce; + await blockchain.AddFunds(TestItem.AddressA, 10000.Ether()); + + Transaction tx = Build.A.Transaction.WithNonce(currentNonceAddressA++) + .WithValue(10) + .WithTo(TestItem.AddressF) + .WithGasLimit(50000) + .WithGasPrice(10).SignedAndResolved(TestItem.PrivateKeyA).TestObject; + + Account accountA = blockchain.State.GetAccount(TestItem.AddressA); + Account accountD = blockchain.State.GetAccount(TestItem.AddressD); + Account accountF = blockchain.State.GetAccount(TestItem.AddressF); + + string[] traceTypes = { "stateDiff" }; + + await blockchain.AddBlock(tx); + + ResultWrapper> traces = context.TraceRpcModule.trace_replayBlockTransactions(new BlockParameter(blockchain.BlockFinder.FindLatestBlock()!.Number), traceTypes); + traces.Data.Should().HaveCount(1); + var state = traces.Data.ElementAt(0).StateChanges!; + + state.Count.Should().Be(3); + state[TestItem.AddressA].Nonce!.Before.Should().Be(accountA.Nonce); + state[TestItem.AddressD].Balance!.Before.Should().Be(accountD.Balance); + state[TestItem.AddressA].Balance!.Before.Should().Be(accountA.Balance); + state[TestItem.AddressF].Balance!.Before.Should().Be(null); + + state[TestItem.AddressA].Nonce!.After.Should().Be(accountA.Nonce + 1); + state[TestItem.AddressD].Balance!.After.Should().Be(accountD.Balance + 21000 * tx.GasPrice); + state[TestItem.AddressA].Balance!.After.Should().Be(accountA.Balance - 21000 * tx.GasPrice - tx.Value); + state[TestItem.AddressF].Balance!.After.Should().Be(accountF.Balance + tx.Value); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Nethermind.JsonRpc.Test.csproj b/src/Nethermind/Nethermind.JsonRpc.Test/Nethermind.JsonRpc.Test.csproj index 912df2cdbf0..6299240cf76 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Nethermind.JsonRpc.Test.csproj +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Nethermind.JsonRpc.Test.csproj @@ -10,7 +10,6 @@ all - diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs b/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs index 667a947f9b4..43a8d311af4 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/RpcTest.cs @@ -2,100 +2,75 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; + using FluentAssertions; + using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Test.Modules; using Nethermind.Logging; using Nethermind.Serialization.Json; -using Newtonsoft.Json; + +using NUnit.Framework; namespace Nethermind.JsonRpc.Test; public static class RpcTest { - public static JsonRpcResponse TestRequest(T module, string method, params string[] parameters) where T : class, IRpcModule + public static async Task TestRequest(T module, string method, params string[] parameters) where T : class, IRpcModule { IJsonRpcService service = BuildRpcService(module); JsonRpcRequest request = GetJsonRequest(method, parameters); - return service.SendRequestAsync(request, new JsonRpcContext(RpcEndpoint.Http)).Result; + return await service.SendRequestAsync(request, new JsonRpcContext(RpcEndpoint.Http)); } - public static async Task TestSerializedRequest(IReadOnlyCollection converters, T module, string method, params string[] parameters) where T : class, IRpcModule + public static async Task TestSerializedRequest(T module, string method, params string[] parameters) where T : class, IRpcModule { - long Serialize(EthereumJsonSerializer ethereumJsonSerializer, JsonRpcResponse jsonRpcResponse, IJsonRpcService jsonRpcService, Stream stream, bool indented = false) - { - try - { - return ethereumJsonSerializer.SerializeWaitForEnumeration(stream, jsonRpcResponse, indented); - } - catch (Exception e) when (e.InnerException is OperationCanceledException) - { - return SerializeTimeoutException(ethereumJsonSerializer, jsonRpcResponse, jsonRpcService, stream); - } - catch (OperationCanceledException) - { - return SerializeTimeoutException(ethereumJsonSerializer, jsonRpcResponse, jsonRpcService, stream); - } - } - - static long SerializeTimeoutException(IJsonSerializer jsonSerializer, JsonRpcResponse response, IJsonRpcService service, Stream resultStream) => - jsonSerializer.Serialize(resultStream, service.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout.", response.Id, response.MethodName)); - - IJsonRpcService service = BuildRpcService(module, converters); + IJsonRpcService service = BuildRpcService(module); JsonRpcRequest request = GetJsonRequest(method, parameters); - JsonRpcContext context = module is IContextAwareRpcModule { Context: not null } contextAwareModule - ? contextAwareModule.Context - : new JsonRpcContext(RpcEndpoint.Http); - + JsonRpcContext context = (module is IContextAwareRpcModule contextAwareModule && contextAwareModule.Context is not null) ? + contextAwareModule.Context : + new JsonRpcContext(RpcEndpoint.Http); JsonRpcResponse response = await service.SendRequestAsync(request, context); EthereumJsonSerializer serializer = new(); - foreach (JsonConverter converter in converters) - { - serializer.RegisterConverter(converter); - } - await using Stream stream = new MemoryStream(); - long size = Serialize(serializer, response, service, stream); + Stream stream = new MemoryStream(); + long size = serializer.Serialize(stream, response); // for coverage (and to prove that it does not throw - await using Stream indentedStream = new MemoryStream(); - Serialize(serializer, response, service, indentedStream, true); + Stream indentedStream = new MemoryStream(); + serializer.Serialize(indentedStream, response, true); stream.Seek(0, SeekOrigin.Begin); - string serialized = await new StreamReader(stream).ReadToEndAsync(); + string serialized = new StreamReader(stream).ReadToEnd(); size.Should().Be(serialized.Length); return serialized; } - public static Task TestSerializedRequest(T module, string method, params string[] parameters) where T : class, IRpcModule - { - return TestSerializedRequest(Array.Empty(), module, method, parameters); - } - - public static IJsonRpcService BuildRpcService(T module, IReadOnlyCollection? converters = null) where T : class, IRpcModule + public static IJsonRpcService BuildRpcService(T module) where T : class, IRpcModule { var moduleProvider = new TestRpcModuleProvider(module); - moduleProvider.Register(new SingletonModulePool(new TestSingletonFactory(module, converters), true)); + moduleProvider.Register(new SingletonModulePool(new TestSingletonFactory(module), true)); IJsonRpcService service = new JsonRpcService(moduleProvider, LimboLogs.Instance, new JsonRpcConfig()); return service; } public static JsonRpcRequest GetJsonRequest(string method, params string[] parameters) { + var doc = JsonDocument.Parse(JsonSerializer.Serialize(parameters?.ToArray() ?? Array.Empty())); var request = new JsonRpcRequest() { JsonRpc = "2.0", Method = method, - Params = parameters?.ToArray() ?? Array.Empty(), + Params = doc.RootElement, Id = 67 }; @@ -104,13 +79,8 @@ public static JsonRpcRequest GetJsonRequest(string method, params string[] param private class TestSingletonFactory : SingletonFactory where T : IRpcModule { - private readonly IReadOnlyCollection? _converters; - - public TestSingletonFactory(T module, IReadOnlyCollection? converters) : base(module) + public TestSingletonFactory(T module) : base(module) { - _converters = converters; } - - public override IReadOnlyCollection GetConverters() => _converters ?? base.GetConverters(); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceSerializerTests.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceSerializerTests.cs index ce574ad8538..1024777243a 100644 --- a/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceSerializerTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.TraceStore.Test/TraceSerializerTests.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.Json; + using FluentAssertions; using Nethermind.Evm.Tracing.ParityStyle; using Nethermind.Logging; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.JsonRpc.TraceStore.Tests; @@ -25,7 +27,7 @@ public void can_deserialize_deep_graph() public void cant_deserialize_deep_graph() { Func?> traces = () => Deserialize(new ParityLikeTraceSerializer(LimboLogs.Instance, 128)); - traces.Should().Throw(); + traces.Should().Throw(); } private List? Deserialize(ITraceSerializer serializer) diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityLikeTraceSerializer.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityLikeTraceSerializer.cs index 8d54aef92e6..77f22416f83 100644 --- a/src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityLikeTraceSerializer.cs +++ b/src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityLikeTraceSerializer.cs @@ -20,7 +20,7 @@ public class ParityLikeTraceSerializer : ITraceSerializer public ParityLikeTraceSerializer(ILogManager logManager, int maxDepth = 1024, bool verifySerialized = false) { - _jsonSerializer = new EthereumJsonSerializer(maxDepth, new ParityTraceActionCreationConverter()); + _jsonSerializer = new EthereumJsonSerializer(maxDepth); _maxDepth = maxDepth; _verifySerialized = verifySerialized; _logger = logManager?.GetClassLogger(); diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityTraceActionCreationConverter.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityTraceActionCreationConverter.cs deleted file mode 100644 index b5af77d7ac2..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc.TraceStore/ParityTraceActionCreationConverter.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Evm.Tracing.ParityStyle; -using Newtonsoft.Json.Converters; - -namespace Nethermind.JsonRpc.TraceStore; - -public class ParityTraceActionCreationConverter : CustomCreationConverter -{ - public override ParityTraceAction Create(Type objectType) => new() { Result = null }; -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Client/JsonRpcResponse.cs b/src/Nethermind/Nethermind.JsonRpc/Client/JsonRpcResponse.cs index 718ee43674a..2eb5d041dd0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Client/JsonRpcResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Client/JsonRpcResponse.cs @@ -2,23 +2,31 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Serialization.Json; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Client { public class JsonRpcResponse { - [JsonProperty(PropertyName = "jsonrpc", Order = 1)] + [JsonPropertyOrder(1)] + [JsonPropertyName("jsonrpc")] public string JsonRpc { get; set; } - [JsonProperty(PropertyName = "result", NullValueHandling = NullValueHandling.Ignore, Order = 2)] + [JsonPropertyName("result")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyOrder(2)] public T Result { get; set; } - [JsonProperty(PropertyName = "error", NullValueHandling = NullValueHandling.Ignore, Order = 3)] + [JsonPropertyName("error")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyOrder(3)] public Error Error { get; set; } [JsonConverter(typeof(IdConverter))] - [JsonProperty(PropertyName = "id", Order = 0, NullValueHandling = NullValueHandling.Include)] + [JsonPropertyName("id")] + [JsonPropertyOrder(0)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public object Id { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs index 34da8afada2..3364e6306a6 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs @@ -5,20 +5,20 @@ using Nethermind.Core; using Nethermind.Int256; using Nethermind.JsonRpc.Data; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.JsonRpc.Converters +namespace Nethermind.JsonRpc.Converters; + +public class TxReceiptConverter : JsonConverter { - public class TxReceiptConverter : JsonConverter + public override TxReceipt? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - public override void WriteJson(JsonWriter writer, TxReceipt value, JsonSerializer serializer) - { - serializer.Serialize(writer, new ReceiptForRpc(value.TxHash!, value, new(UInt256.Zero))); - } + return JsonSerializer.Deserialize(ref reader, options)?.ToReceipt(); + } - public override TxReceipt ReadJson(JsonReader reader, Type objectType, TxReceipt existingValue, bool hasExistingValue, JsonSerializer serializer) - { - return serializer.Deserialize(reader)?.ToReceipt() ?? existingValue; - } + public override void Write(Utf8JsonWriter writer, TxReceipt value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, new ReceiptForRpc(value.TxHash!, value, default), options); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs index f4d05e7575b..84e569a4965 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListForRpc.cs @@ -6,7 +6,7 @@ namespace Nethermind.JsonRpc.Data { - public struct AccessListForRpc + public readonly struct AccessListForRpc { public AccessListForRpc(IEnumerable accessList, in UInt256 gasUsed) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListItemForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListItemForRpc.cs index 1eed1d5a03d..b0fc6893087 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/AccessListItemForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/AccessListItemForRpc.cs @@ -9,12 +9,16 @@ using Nethermind.Core.Eip2930; using Nethermind.Int256; using Nethermind.Serialization.Json; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Data { public struct AccessListItemForRpc : IEquatable { + [JsonConstructor] + public AccessListItemForRpc() { } + public AccessListItemForRpc(Address address, IEnumerable? storageKeys) { Address = address; @@ -22,8 +26,7 @@ public AccessListItemForRpc(Address address, IEnumerable? storageKeys) } public Address Address { get; set; } - - [JsonProperty(ItemConverterType = typeof(StorageCellIndexConverter))] + [JsonConverter(typeof(StorageCellIndexConverter))] public IEnumerable? StorageKeys { get; set; } public static IEnumerable FromAccessList(AccessList accessList) => diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/BlockParameterConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Data/BlockParameterConverter.cs deleted file mode 100644 index d960088f430..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Data/BlockParameterConverter.cs +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Blockchain.Find; -using Nethermind.Core.Crypto; -using Nethermind.JsonRpc.Modules.Trace; -using Nethermind.Serialization.Json; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Data -{ - public class BlockParameterConverter : JsonConverter - { - private NullableLongConverter _longConverter = new(); - - private KeccakConverter _keccakConverter = new(); - - public override void WriteJson(JsonWriter writer, BlockParameter value, JsonSerializer serializer) - { - if (value.Type == BlockParameterType.BlockNumber) - { - _longConverter.WriteJson(writer, value.BlockNumber, serializer); - return; - } - - if (value.Type == BlockParameterType.BlockHash) - { - if (value.RequireCanonical) - { - writer.WriteStartObject(); - writer.WriteProperty("requireCanonical", true); - writer.WriteProperty("blockHash", value.BlockHash, serializer); - writer.WriteEndObject(); - } - else - { - _keccakConverter.WriteJson(writer, value.BlockHash, serializer); - } - - return; - } - - switch (value.Type) - { - case BlockParameterType.Earliest: - writer.WriteValue("earliest"); - break; - case BlockParameterType.Latest: - writer.WriteValue("latest"); - break; - case BlockParameterType.Pending: - writer.WriteValue("pending"); - break; - case BlockParameterType.Finalized: - writer.WriteValue("finalized"); - break; - case BlockParameterType.Safe: - writer.WriteValue("safe"); - break; - case BlockParameterType.BlockNumber: - throw new InvalidOperationException("block number should be handled separately"); - case BlockParameterType.BlockHash: - throw new InvalidOperationException("block hash should be handled separately"); - default: - throw new InvalidOperationException("unknown block parameter type"); - } - } - - private static bool IsNonStringAndLongish(object item) - { - return Convert.GetTypeCode(item) switch - { - TypeCode.Int16 => true, - TypeCode.Int32 => true, - TypeCode.Int64 => true, - TypeCode.UInt16 => true, - TypeCode.UInt64 => true, - TypeCode.UInt32 => true, - TypeCode.Single => true, - TypeCode.Double => true, - TypeCode.Decimal => true, - TypeCode.Byte => true, - TypeCode.SByte => true, - _ => false - }; - } - - public override BlockParameter ReadJson(JsonReader reader, Type objectType, BlockParameter existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.StartObject) - { - bool requireCanonical = false; - Hash256 blockHash = null; - for (int i = 0; i < 2; i++) - { - reader.Read(); - if (string.Equals((string)reader.Value, "requireCanonical", StringComparison.InvariantCultureIgnoreCase)) - { - requireCanonical = reader.ReadAsBoolean().Value; - } - else if (string.Equals((string)reader.Value, "blockHash", StringComparison.InvariantCultureIgnoreCase)) - { - blockHash = new Hash256(reader.ReadAsString()); - } - } - - BlockParameter parameter = new(blockHash, requireCanonical); - return parameter; - } - - if (reader.Value is null) - { - return BlockParameter.Latest; - } - - if (IsNonStringAndLongish(reader.Value)) - { - return new BlockParameter((long)reader.Value); - } - - string value = reader.Value as string; - return GetBlockParameter(value); - } - - public static BlockParameter GetBlockParameter(string? value) - { - switch (value) - { - case null: - case { } empty when string.IsNullOrWhiteSpace(empty): - case { } latest when latest.Equals("latest", StringComparison.InvariantCultureIgnoreCase): - return BlockParameter.Latest; - case { } earliest when earliest.Equals("earliest", StringComparison.InvariantCultureIgnoreCase): - return BlockParameter.Earliest; - case { } pending when pending.Equals("pending", StringComparison.InvariantCultureIgnoreCase): - return BlockParameter.Pending; - case { } finalized when finalized.Equals("finalized", StringComparison.InvariantCultureIgnoreCase): - return BlockParameter.Finalized; - case { } safe when safe.Equals("safe", StringComparison.InvariantCultureIgnoreCase): - return BlockParameter.Safe; - case { Length: 66 } hash when hash.StartsWith("0x"): - return new BlockParameter(new Hash256(hash)); - default: - return new BlockParameter(LongConverter.FromString(value)); - } - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/ProofConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Data/ProofConverter.cs deleted file mode 100644 index a12b51fbe4d..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Data/ProofConverter.cs +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core.Extensions; -using Nethermind.JsonRpc.Modules.Trace; -using Nethermind.State.Proofs; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Data -{ - /// - ///{ - /// "id": 1, - /// "jsonrpc": "2.0", - /// "method": "eth_getProof", - /// "params": [ - /// "0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842", - /// [ "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" ], - /// "latest" - /// ] - ///} - /// - ///{ - /// "id": 1, - /// "jsonrpc": "2.0", - /// "result": { - /// "accountProof": [ - /// "0xf90211a...0701bc80", - /// "0xf90211a...0d832380", - /// "0xf90211a...5fb20c80", - /// "0xf90211a...0675b80", - /// "0xf90151a0...ca08080" - /// ], - /// "balance": "0x0", - /// "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - /// "nonce": "0x0", - /// "storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - /// "storageProof": [ - /// { - /// "key": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - /// "proof": [ - /// "0xf90211a...0701bc80", - /// "0xf90211a...0d832380" - /// ], - /// "value": "0x1" - /// } - /// ] - /// } - ///} - /// - public class ProofConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, AccountProof value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("accountProof"); - writer.WriteStartArray(); - for (int i = 0; i < value.Proof.Length; i++) - { - writer.WriteValue(value.Proof[i].ToHexString(true)); - } - writer.WriteEnd(); - writer.WriteProperty("address", value.Address, serializer); - writer.WriteProperty("balance", value.Balance, serializer); - writer.WriteProperty("codeHash", value.CodeHash, serializer); - writer.WriteProperty("nonce", value.Nonce, serializer); - writer.WriteProperty("storageHash", value.StorageRoot, serializer); - writer.WritePropertyName("storageProof"); - writer.WriteStartArray(); - for (int i = 0; i < value.StorageProofs.Length; i++) - { - writer.WriteStartObject(); - writer.WriteProperty("key", value.StorageProofs[i].Key, serializer); - writer.WritePropertyName("proof"); - writer.WriteStartArray(); - for (int ip = 0; ip < value.StorageProofs[i].Proof.Length; ip++) - { - writer.WriteValue(value.StorageProofs[i].Proof[ip].ToHexString(true)); - } - writer.WriteEnd(); - writer.WriteProperty("value", value.StorageProofs[i].Value, serializer); - writer.WriteEnd(); - } - writer.WriteEnd(); - writer.WriteEnd(); - - } - - public override AccountProof ReadJson(JsonReader reader, Type objectType, AccountProof existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs index 953ac9bf5a8..0921f9ed8ab 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs @@ -6,7 +6,8 @@ using Nethermind.Core.Crypto; using Nethermind.Evm; using Nethermind.Int256; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Data { @@ -45,19 +46,19 @@ public ReceiptForRpc(Hash256 txHash, TxReceipt receipt, TxGasInfo gasInfo, int l public long CumulativeGasUsed { get; set; } public long GasUsed { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ulong? BlobGasUsed { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public UInt256? BlobGasPrice { get; set; } public UInt256? EffectiveGasPrice { get; set; } public Address From { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address To { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address ContractAddress { get; set; } public LogEntryForRpc[] Logs { get; set; } public Bloom? LogsBloom { get; set; } @@ -68,22 +69,24 @@ public ReceiptForRpc(Hash256 txHash, TxReceipt receipt, TxGasInfo gasInfo, int l public TxReceipt ToReceipt() { - TxReceipt receipt = new(); - receipt.Bloom = LogsBloom; - receipt.Error = Error; - receipt.Index = (int)TransactionIndex; - receipt.Logs = Logs.Select(l => l.ToLogEntry()).ToArray(); - receipt.Recipient = To; - receipt.Sender = From; - receipt.BlockHash = BlockHash; - receipt.BlockNumber = BlockNumber; - receipt.ContractAddress = ContractAddress; - receipt.GasUsed = GasUsed; - receipt.StatusCode = (byte)Status; - receipt.TxHash = TransactionHash; - receipt.GasUsedTotal = CumulativeGasUsed; - receipt.PostTransactionState = Root; - receipt.TxType = Type; + TxReceipt receipt = new() + { + Bloom = LogsBloom, + Error = Error, + Index = (int)TransactionIndex, + Logs = Logs.Select(l => l.ToLogEntry()).ToArray(), + Recipient = To, + Sender = From, + BlockHash = BlockHash, + BlockNumber = BlockNumber, + ContractAddress = ContractAddress, + GasUsed = GasUsed, + StatusCode = (byte)Status, + TxHash = TransactionHash, + GasUsedTotal = CumulativeGasUsed, + PostTransactionState = Root, + TxType = Type + }; return receipt; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs index a338dc5bbb1..9806860fad7 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Eip2930; using Nethermind.Core.Extensions; using Nethermind.Int256; -using Newtonsoft.Json; namespace Nethermind.JsonRpc.Data; @@ -83,9 +84,8 @@ public TransactionForRpc(Hash256? blockHash, long? blockNumber, int? txIndex, Tr } // ReSharper disable once UnusedMember.Global - public TransactionForRpc() - { - } + [JsonConstructor] + public TransactionForRpc() { } public Hash256? SourceHash { get; set; } public UInt256? Mint { get; set; } @@ -94,18 +94,18 @@ public TransactionForRpc() public Hash256? Hash { get; set; } public UInt256? Nonce { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Hash256? BlockHash { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? BlockNumber { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? TransactionIndex { get; set; } public Address? From { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address? To { get; set; } public UInt256? Value { get; set; } @@ -119,10 +119,12 @@ public TransactionForRpc() // Required for compatibility with some CLs like Prysm // Accept during deserialization, ignore during serialization // See: https://github.com/NethermindEth/nethermind/pull/6067 - [JsonProperty(nameof(Data))] - private byte[]? Data { set { Input = value; } } + [JsonPropertyName(nameof(Data))] + [JsonConverter(typeof(DataConverter))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public byte[]? Data { set { Input = value; } private get { return null; } } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public byte[]? Input { get; set; } public UInt256? ChainId { get; set; } @@ -131,11 +133,10 @@ public TransactionForRpc() public IEnumerable? AccessList { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public UInt256? MaxFeePerBlobGas { get; set; } // eip4844 - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public byte[][]? BlobVersionedHashes { get; set; } // eip4844 public UInt256? V { get; set; } @@ -144,7 +145,7 @@ public TransactionForRpc() public UInt256? R { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public UInt256? YParity { get; set; } public Transaction ToTransactionWithDefaults(ulong? chainId = null) => ToTransactionWithDefaults(chainId); @@ -237,4 +238,19 @@ public void EnsureDefaults(long? gasCap) From ??= Address.SystemUser; } + + private class DataConverter : JsonConverter + { + public override bool HandleNull { get; } = false; + + public override byte[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + public override void Write(Utf8JsonWriter writer, byte[]? value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs index 3f118399476..061f0b02f11 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpcWithTraceTypes.cs @@ -1,13 +1,49 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + namespace Nethermind.JsonRpc.Data { + using Nethermind.JsonRpc.Modules.Trace; + + [JsonConverter(typeof(TransactionForRpcWithTraceTypesConverter))] public class TransactionForRpcWithTraceTypes { public TransactionForRpc Transaction { get; set; } public string[] TraceTypes { get; set; } } +} + +namespace Nethermind.JsonRpc.Modules.Trace +{ + using Nethermind.JsonRpc.Data; + public class TransactionForRpcWithTraceTypesConverter : JsonConverter + { + public override TransactionForRpcWithTraceTypes? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + TransactionForRpcWithTraceTypes value = new(); + + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException(); + } + reader.Read(); + + value.Transaction = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + value.TraceTypes = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + return value; + } + + public override void Write(Utf8JsonWriter writer, TransactionForRpcWithTraceTypes value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Error.cs b/src/Nethermind/Nethermind.JsonRpc/Error.cs index dc6b1ffe26f..1db1be2c9db 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Error.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Error.cs @@ -1,19 +1,20 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc { public class Error { - [JsonProperty(PropertyName = "code", Order = 0)] + [JsonPropertyName("code")] public int Code { get; set; } - [JsonProperty(PropertyName = "message", Order = 1)] + [JsonPropertyName("message")] public string? Message { get; set; } - [JsonProperty(PropertyName = "data", Order = 2)] + [JsonPropertyName("data")] public object? Data { get; set; } [JsonIgnore] diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcParam.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcParam.cs index 58e9e96f1a2..11f8c9b4b3b 100644 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcParam.cs +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcParam.cs @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; namespace Nethermind.JsonRpc; public interface IJsonRpcParam { - void ReadJson(JsonSerializer serializer, string jsonValue); + void ReadJson(JsonElement jsonValue, JsonSerializerOptions options); } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcProcessor.cs index 3749ea84488..5096a3435c3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcProcessor.cs @@ -1,13 +1,13 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; -using System.IO; +using System.IO.Pipelines; -namespace Nethermind.JsonRpc +namespace Nethermind.JsonRpc; + +public interface IJsonRpcProcessor { - public interface IJsonRpcProcessor - { - IAsyncEnumerable ProcessAsync(TextReader request, JsonRpcContext context); - } + IAsyncEnumerable ProcessAsync(PipeReader stream, JsonRpcContext context); } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcService.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcService.cs index 54a311fa875..23b924bed60 100644 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcService.cs +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcService.cs @@ -2,14 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Threading.Tasks; -using Newtonsoft.Json; -namespace Nethermind.JsonRpc +using Nethermind.JsonRpc.Modules; + +namespace Nethermind.JsonRpc; + +public interface IJsonRpcService { - public interface IJsonRpcService - { - Task SendRequestAsync(JsonRpcRequest request, JsonRpcContext context); - JsonRpcErrorResponse GetErrorResponse(int errorCode, string errorMessage, object? id = null, string? methodName = null); - JsonConverter[] Converters { get; } - } + Task SendRequestAsync(JsonRpcRequest request, JsonRpcContext context); + JsonRpcErrorResponse GetErrorResponse(int errorCode, string errorMessage, object? id = null, string? methodName = null); } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcLocalStats.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcLocalStats.cs index 2c3eba2ffac..91000449525 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcLocalStats.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcLocalStats.cs @@ -133,7 +133,7 @@ private void BuildReport() Swap(); - if (!_previousStats.Any()) + if (_previousStats.IsEmpty) { return; } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index 40dc11b572b..c9faf4d8833 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -2,209 +2,287 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Abstractions; -using System.Text; +using System.IO.Pipelines; +using System.Linq; +using System.Numerics; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Nethermind.Core.Collections; using Nethermind.Core.Extensions; -using Nethermind.JsonRpc.Utils; using Nethermind.Logging; using Nethermind.Serialization.Json; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -#nullable enable -namespace Nethermind.JsonRpc +namespace Nethermind.JsonRpc; + +public class JsonRpcProcessor : IJsonRpcProcessor { - public class JsonRpcProcessor : IJsonRpcProcessor + private readonly IJsonRpcConfig _jsonRpcConfig; + private readonly ILogger _logger; + private readonly IJsonRpcService _jsonRpcService; + private readonly Recorder _recorder; + + public JsonRpcProcessor(IJsonRpcService jsonRpcService, IJsonRpcConfig jsonRpcConfig, IFileSystem fileSystem, ILogManager logManager) { - private readonly JsonSerializer _traceSerializer; - private readonly IJsonRpcConfig _jsonRpcConfig; - private readonly ILogger _logger; - private readonly JsonSerializer _obsoleteBasicJsonSerializer = new(); - private readonly IJsonRpcService _jsonRpcService; - private readonly IJsonSerializer _jsonSerializer; - private readonly Recorder? _recorder; - - public JsonRpcProcessor(IJsonRpcService jsonRpcService, IJsonSerializer jsonSerializer, IJsonRpcConfig jsonRpcConfig, IFileSystem fileSystem, ILogManager logManager) - { - _logger = logManager.GetClassLogger(); - _jsonRpcService = jsonRpcService; - _jsonRpcConfig = jsonRpcConfig; - _jsonSerializer = jsonSerializer; + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + ArgumentNullException.ThrowIfNull(fileSystem); - if (_jsonRpcConfig.RpcRecorderState != RpcRecorderState.None) - { - if (_logger.IsWarn) _logger.Warn("Enabling JSON RPC diagnostics recorder - this will affect performance and should be only used in a diagnostics mode."); - string recorderBaseFilePath = _jsonRpcConfig.RpcRecorderBaseFilePath.GetApplicationResourcePath(); - _recorder = new Recorder(recorderBaseFilePath, fileSystem, _logger); - } + _jsonRpcService = jsonRpcService ?? throw new ArgumentNullException(nameof(jsonRpcService)); + _jsonRpcConfig = jsonRpcConfig ?? throw new ArgumentNullException(nameof(jsonRpcConfig)); - _traceSerializer = BuildTraceJsonSerializer(); + if (_jsonRpcConfig.RpcRecorderState != RpcRecorderState.None) + { + if (_logger.IsWarn) _logger.Warn("Enabling JSON RPC diagnostics recorder - this will affect performance and should be only used in a diagnostics mode."); + string recorderBaseFilePath = _jsonRpcConfig.RpcRecorderBaseFilePath.GetApplicationResourcePath(); + _recorder = new Recorder(recorderBaseFilePath, fileSystem, _logger); } + } - /// - /// The serializer is created in a way that mimics the behaviour of the Kestrel serialization - /// and can be used for recording and replaying JSON RPC calls. - /// - private JsonSerializer BuildTraceJsonSerializer() + private (JsonRpcRequest? Model, ArrayPoolList? Collection) DeserializeObjectOrArray(JsonDocument doc) + { + JsonValueKind type = doc.RootElement.ValueKind; + if (type == JsonValueKind.Array) { - JsonSerializerSettings jsonSettings = new() - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; + return (null, DeserializeArray(doc.RootElement)); + } + else if (type == JsonValueKind.Object) + { + return (DeserializeObject(doc.RootElement), null); + } + else + { + throw new JsonException("Invalid"); + } + } - foreach (JsonConverter converter in _jsonRpcService.Converters) + private JsonRpcRequest DeserializeObject(JsonElement element) + { + string? jsonRpc = null; + if (element.TryGetProperty("jsonrpc"u8, out JsonElement versionElement)) + { + if (versionElement.ValueEquals("2.0"u8)) { - jsonSettings.Converters.Add(converter); + jsonRpc = "2.0"; } - - return JsonSerializer.Create(jsonSettings); } - private IEnumerable<(JsonRpcRequest? Model, List? Collection)> DeserializeObjectOrArray(TextReader json) + object? id = null; + if (element.TryGetProperty("id"u8, out JsonElement idElement)) { - IEnumerable parsedJson = JTokenUtils.ParseMulticontent(json); - - foreach (JToken token in parsedJson) + if (idElement.ValueKind == JsonValueKind.Number) { - if (token is JArray array) + if (idElement.TryGetInt64(out long idNumber)) { - foreach (JToken tokenElement in array) - { - UpdateParams(tokenElement); - } - - yield return (null, array.ToObject>(_obsoleteBasicJsonSerializer)); + id = idNumber; } - else + else if (decimal.TryParse(idElement.GetRawText(), out var value)) { - UpdateParams(token); - yield return (token.ToObject(_obsoleteBasicJsonSerializer), null); + id = value; } } - } - - private void UpdateParams(JToken token) - { - var paramsToken = token.SelectToken("params"); - if (paramsToken is null) + else { - paramsToken = token.SelectToken("Params"); - if (paramsToken is null) - { - return; - } + id = idElement.GetString(); } + } - if (paramsToken is JValue) - { - return; // null - } + string? method = null; + if (element.TryGetProperty("method"u8, out JsonElement methodElement)) + { + method = methodElement.GetString(); + } - JArray arrayToken = (JArray)paramsToken; - for (int i = 0; i < arrayToken.Count; i++) - { - if (arrayToken[i].Type == JTokenType.Array || arrayToken[i].Type == JTokenType.Object) - { - arrayToken[i].Replace(JToken.Parse(_jsonSerializer.Serialize(arrayToken[i].Value()!.ToString()))); - } - } + if (!element.TryGetProperty("params"u8, out JsonElement paramsElement)) + { + paramsElement = default; } - public async IAsyncEnumerable ProcessAsync(TextReader request, JsonRpcContext context) + return new JsonRpcRequest { - request = await RecordRequest(request); - Stopwatch stopwatch = Stopwatch.StartNew(); - IEnumerable<(JsonRpcRequest? Model, List? Collection)> rpcRequests = DeserializeObjectOrArray(request); + JsonRpc = jsonRpc!, + Id = id!, + Method = method!, + Params = paramsElement + }; + } - using IEnumerator<(JsonRpcRequest? Model, List? Collection)> enumerator = rpcRequests.GetEnumerator(); + private ArrayPoolList DeserializeArray(JsonElement element) => + new(element.GetArrayLength(), element.EnumerateArray().Select(DeserializeObject)); - bool moveNext = true; + public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) + { + reader = await RecordRequest(reader); + Stopwatch stopwatch = Stopwatch.StartNew(); - do + // Initializes a buffer to store the data read from the reader. + ReadOnlySequence buffer = default; + try + { + // Continuously read data from the PipeReader in a loop. + // Can read multiple requests, ends when there is no more requests to read or there is an error in deserialization. + while (true) { + // Asynchronously reads data from the PipeReader. + ReadResult readResult = await reader.ReadAsync(); + buffer = readResult.Buffer; + // Placeholder for a result in case of deserialization failure. JsonRpcResult? deserializationFailureResult = null; - try - { - moveNext = enumerator.MoveNext(); - } - catch (BadHttpRequestException e) - { - Metrics.JsonRpcRequestDeserializationFailures++; - if (_logger.IsDebug) _logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); - yield break; - } - catch (Exception ex) - { - Metrics.JsonRpcRequestDeserializationFailures++; - if (_logger.IsError) _logger.Error($"Error during parsing/validation.", ex); - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ParseError, "Incorrect message"); - TraceResult(response); - stopwatch.Stop(); - deserializationFailureResult = JsonRpcResult.Single( - RecordResponse(response, new RpcReport("# parsing error #", stopwatch.ElapsedMicroseconds(), false))); - } - if (deserializationFailureResult.HasValue) + // Processes the buffer while it's not empty; before going out to outer loop to get more data. + while (!buffer.IsEmpty) { - moveNext = false; - yield return deserializationFailureResult.Value; - } - else if (moveNext) - { - (JsonRpcRequest? Model, List? Collection) rpcRequest = enumerator.Current; - - if (rpcRequest.Model is not null) + JsonDocument? jsonDocument = null; + JsonRpcRequest? model = null; + ArrayPoolList? collection = null; + try { - if (_logger.IsDebug) _logger.Debug($"JSON RPC request {rpcRequest.Model}"); - - JsonRpcResult.Entry result = await HandleSingleRequest(rpcRequest.Model, context); + // Tries to parse the JSON from the buffer. + if (!TryParseJson(ref buffer, out jsonDocument)) + { + // More data needs to be read to complete a document + break; + } - yield return JsonRpcResult.Single(RecordResponse(result)); + // Deserializes the JSON document into a request object or a collection of requests. + (model, collection) = DeserializeObjectOrArray(jsonDocument); } - - if (rpcRequest.Collection is not null) + catch (BadHttpRequestException e) + { + // Increments failure metric and logs the exception, then stops processing. + Metrics.JsonRpcRequestDeserializationFailures++; + if (_logger.IsDebug) _logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); + yield break; + } + catch (Exception ex) { - if (_logger.IsDebug) _logger.Debug($"{rpcRequest.Collection.Count} JSON RPC requests"); + // Handles general exceptions during parsing and validation. + // Sends an error response and stops the stopwatch. + Metrics.JsonRpcRequestDeserializationFailures++; + if (_logger.IsError) _logger.Error($"Error during parsing/validation.", ex); + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ParseError, "Incorrect message"); + TraceResult(response); + stopwatch.Stop(); + deserializationFailureResult = JsonRpcResult.Single( + RecordResponse(response, new RpcReport("# parsing error #", stopwatch.ElapsedMicroseconds(), false))); + } - if (!context.IsAuthenticated && rpcRequest.Collection.Count > _jsonRpcConfig.MaxBatchSize) + // Checks for deserialization failure and yields the result. + if (deserializationFailureResult.HasValue) + { + yield return deserializationFailureResult.Value; + break; + } + else + { + // Handles a single JSON RPC request. + if (model is not null) { - if (_logger.IsWarn) _logger.Warn($"The batch size limit was exceeded. The requested batch size {rpcRequest.Collection.Count}, and the current config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchSize)} = {_jsonRpcConfig.MaxBatchSize}."); - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.LimitExceeded, "Batch size limit exceeded"); + if (_logger.IsDebug) _logger.Debug($"JSON RPC request {model}"); - yield return JsonRpcResult.Single(RecordResponse(response, RpcReport.Error)); - continue; + // Processes the individual request. + JsonRpcResult.Entry result = await HandleSingleRequest(model, context); + result.Response.AddDisposable(() => jsonDocument.Dispose()); + + // Returns the result of the processed request. + yield return JsonRpcResult.Single(RecordResponse(result)); } - stopwatch.Stop(); - yield return JsonRpcResult.Collection(new JsonRpcBatchResult((e, c) => IterateRequest(rpcRequest.Collection, context, e).GetAsyncEnumerator(c))); + // Processes a collection of JSON RPC requests. + if (collection is not null) + { + if (_logger.IsDebug) _logger.Debug($"{collection.Count} JSON RPC requests"); + + // Checks for authentication and batch size limit. + if (!context.IsAuthenticated && collection.Count > _jsonRpcConfig.MaxBatchSize) + { + if (_logger.IsWarn) _logger.Warn($"The batch size limit was exceeded. The requested batch size {collection.Count}, and the current config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchSize)} = {_jsonRpcConfig.MaxBatchSize}."); + JsonRpcErrorResponse? response = _jsonRpcService.GetErrorResponse(ErrorCodes.LimitExceeded, "Batch size limit exceeded"); + response.AddDisposable(() => jsonDocument.Dispose()); + + deserializationFailureResult = JsonRpcResult.Single(RecordResponse(response, RpcReport.Error)); + yield return deserializationFailureResult.Value; + break; + } + + // Stops the stopwatch and yields the batch processing result. + stopwatch.Stop(); + yield return JsonRpcResult.Collection(new JsonRpcBatchResult((e, c) => IterateRequest(collection, context, e).GetAsyncEnumerator(c))); + } + + // Handles invalid requests. + if (model is null && collection is null) + { + Metrics.JsonRpcInvalidRequests++; + JsonRpcErrorResponse errorResponse = _jsonRpcService.GetErrorResponse(ErrorCodes.InvalidRequest, "Invalid request"); + errorResponse.AddDisposable(() => jsonDocument.Dispose()); + + TraceResult(errorResponse); + stopwatch.Stop(); + if (_logger.IsDebug) _logger.Debug($" Failed request handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); + deserializationFailureResult = JsonRpcResult.Single(RecordResponse(errorResponse, new RpcReport("# parsing error #", stopwatch.ElapsedMicroseconds(), false))); + yield return deserializationFailureResult.Value; + break; + } } + } - if (rpcRequest.Model is null && rpcRequest.Collection is null) + // Checks if the deserialization failed + if (deserializationFailureResult.HasValue) + { + break; + } + + // Checks if the read operation is completed. + if (readResult.IsCompleted) + { + if (buffer.Length > 0 && (buffer.IsSingleSegment ? buffer.FirstSpan : buffer.ToArray()).IndexOfAnyExcept(WhiteSpace()) >= 0) { - Metrics.JsonRpcInvalidRequests++; - JsonRpcErrorResponse errorResponse = _jsonRpcService.GetErrorResponse(ErrorCodes.InvalidRequest, "Invalid request"); - TraceResult(errorResponse); + Metrics.JsonRpcRequestDeserializationFailures++; + if (_logger.IsError) _logger.Error($"Error during parsing/validation. Incomplete request"); + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ParseError, "Incorrect message"); + TraceResult(response); stopwatch.Stop(); - if (_logger.IsDebug) _logger.Debug($" Failed request handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); - yield return JsonRpcResult.Single(RecordResponse(errorResponse, new RpcReport("# parsing error #", stopwatch.ElapsedMicroseconds(), false))); + deserializationFailureResult = JsonRpcResult.Single( + RecordResponse(response, new RpcReport("# parsing error #", stopwatch.ElapsedMicroseconds(), false))); + yield return deserializationFailureResult.Value; } + + break; } - } while (moveNext); + + // Advances the reader to the next segment of the buffer. + reader.AdvanceTo(buffer.Start, buffer.End); + buffer = default; + } } + finally + { + // Advances the reader to the end of the buffer if not null. + if (!buffer.FirstSpan.IsNull()) + { + reader.AdvanceTo(buffer.End); + } + } + + // Completes the PipeReader's asynchronous reading operation. + await reader.CompleteAsync(); + } + + private static ReadOnlySpan WhiteSpace() => " \n\r\t"u8; - private async IAsyncEnumerable IterateRequest( - List requests, - JsonRpcContext context, - JsonRpcBatchResultAsyncEnumerator enumerator) + private async IAsyncEnumerable IterateRequest( + ArrayPoolList requests, + JsonRpcContext context, + JsonRpcBatchResultAsyncEnumerator enumerator) + { + try { Stopwatch stopwatch = Stopwatch.StartNew(); - int requestIndex = 0; for (int index = 0; index < requests.Count; index++) { @@ -214,9 +292,9 @@ public async IAsyncEnumerable ProcessAsync(TextReader request, Js ? new JsonRpcResult.Entry( _jsonRpcService.GetErrorResponse( ErrorCodes.LimitExceeded, - $"{nameof(IJsonRpcConfig.MaxBatchResponseBodySize)} of {_jsonRpcConfig.MaxBatchResponseBodySize / 1.KB()}KB exceeded", + jsonRpcRequest.Method, jsonRpcRequest.Id, - jsonRpcRequest.Method), + $"{nameof(IJsonRpcConfig.MaxBatchResponseBodySize)} of {_jsonRpcConfig.MaxBatchResponseBodySize / 1.KB()}KB exceeded"), RpcReport.Error) : await HandleSingleRequest(jsonRpcRequest, context); @@ -227,88 +305,103 @@ public async IAsyncEnumerable ProcessAsync(TextReader request, Js if (_logger.IsDebug) _logger.Debug($" {requests.Count} requests handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); } + finally + { + requests.Dispose(); + } + } + + private async Task HandleSingleRequest(JsonRpcRequest request, JsonRpcContext context) + { + Metrics.JsonRpcRequests++; + Stopwatch stopwatch = Stopwatch.StartNew(); - private async Task HandleSingleRequest(JsonRpcRequest request, JsonRpcContext context) + JsonRpcResponse response = await _jsonRpcService.SendRequestAsync(request, context); + JsonRpcErrorResponse localErrorResponse = response as JsonRpcErrorResponse; + bool isSuccess = localErrorResponse is null; + if (!isSuccess) { - Metrics.JsonRpcRequests++; - Stopwatch stopwatch = Stopwatch.StartNew(); + if (_logger.IsWarn) _logger.Warn($"Error when handling {request} | {JsonSerializer.Serialize(localErrorResponse, EthereumJsonSerializer.JsonOptionsIndented)}"); + Metrics.JsonRpcErrors++; + } + else + { + if (_logger.IsDebug) _logger.Debug($"Responded to {request}"); + Metrics.JsonRpcSuccesses++; + } - JsonRpcResponse response = await _jsonRpcService.SendRequestAsync(request, context); - JsonRpcErrorResponse? localErrorResponse = response as JsonRpcErrorResponse; - bool isSuccess = localErrorResponse is null; - if (!isSuccess) - { - if (localErrorResponse?.Error?.SuppressWarning == false) - { - if (_logger.IsWarn) _logger.Warn($"Error when handling {request} | {_jsonSerializer.Serialize(localErrorResponse)}"); - } + stopwatch.Stop(); - Metrics.JsonRpcErrors++; - } - else - { - if (_logger.IsDebug) _logger.Debug($"Responded to {request}"); - Metrics.JsonRpcSuccesses++; - } + if (_logger.IsDebug) _logger.Debug($" {request} handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); + + JsonRpcResult.Entry result = new(response, new RpcReport(request.Method, stopwatch.ElapsedMicroseconds(), isSuccess)); + TraceResult(result); + return result; + } - stopwatch.Stop(); - if (_logger.IsDebug) _logger.Debug($" {request} handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); + private static bool TryParseJson(ref ReadOnlySequence buffer, out JsonDocument jsonDocument) + { + Utf8JsonReader reader = new(buffer, isFinalBlock: false, default); - if (_logger.IsDebug) _logger.Debug($" {request} handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); - JsonRpcResult.Entry result = new(response, new RpcReport(request.Method, stopwatch.ElapsedMicroseconds(), isSuccess)); - TraceResult(result); - return result; + if (JsonDocument.TryParseValue(ref reader, out jsonDocument)) + { + buffer = buffer.Slice(reader.BytesConsumed); + return true; } - private JsonRpcResult.Entry RecordResponse(JsonRpcResponse response, RpcReport report) => - RecordResponse(new JsonRpcResult.Entry(response, report)); + return false; + } - private JsonRpcResult.Entry RecordResponse(JsonRpcResult.Entry result) - { - if ((_jsonRpcConfig.RpcRecorderState & RpcRecorderState.Response) != 0) - { - _recorder!.RecordResponse(_jsonSerializer.Serialize(result)); - } + private JsonRpcResult.Entry RecordResponse(JsonRpcResponse response, RpcReport report) => + RecordResponse(new JsonRpcResult.Entry(response, report)); - return result; + private JsonRpcResult.Entry RecordResponse(JsonRpcResult.Entry result) + { + if ((_jsonRpcConfig.RpcRecorderState & RpcRecorderState.Response) != 0) + { + _recorder.RecordResponse(JsonSerializer.Serialize(result, EthereumJsonSerializer.JsonOptionsIndented)); } - private async ValueTask RecordRequest(TextReader request) + return result; + } + + private async ValueTask RecordRequest(PipeReader reader) + { + if ((_jsonRpcConfig.RpcRecorderState & RpcRecorderState.Request) != 0) { - if ((_jsonRpcConfig.RpcRecorderState & RpcRecorderState.Request) != 0) - { - string requestString = await request.ReadToEndAsync(); - _recorder!.RecordRequest(requestString); - return new StringReader(requestString); - } + Stream memoryStream = new MemoryStream(); + await reader.CopyToAsync(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + + StreamReader streamReader = new(memoryStream); - return request; + string requestString = await streamReader.ReadToEndAsync(); + _recorder.RecordRequest(requestString); + + memoryStream.Seek(0, SeekOrigin.Begin); + return PipeReader.Create(memoryStream); } - private void TraceResult(JsonRpcResult.Entry response) + return reader; + } + + private void TraceResult(JsonRpcResult.Entry response) + { + if (_logger.IsTrace) { - if (_logger.IsTrace) - { - StringBuilder builder = new(); - using StringWriter stringWriter = new(builder); - using JsonTextWriter jsonWriter = new(stringWriter); - _traceSerializer.Serialize(jsonWriter, response); + string json = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptionsIndented); - _logger.Trace($"Sending JSON RPC response: {builder}"); - } + _logger.Trace($"Sending JSON RPC response: {json}"); } + } - private void TraceResult(JsonRpcErrorResponse response) + private void TraceResult(JsonRpcErrorResponse response) + { + if (_logger.IsTrace) { - if (_logger.IsTrace) - { - StringBuilder builder = new(); - using StringWriter stringWriter = new(builder); - using JsonTextWriter jsonWriter = new(stringWriter); - _traceSerializer.Serialize(jsonWriter, response); + string json = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptionsIndented); - _logger.Trace($"Sending JSON RPC response: {builder}"); - } + _logger.Trace($"Sending JSON RPC response: {json}"); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcRequest.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcRequest.cs index 9ed5fcbbe9b..a9c014da034 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcRequest.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcRequest.cs @@ -1,8 +1,10 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; namespace Nethermind.JsonRpc { @@ -11,16 +13,14 @@ public class JsonRpcRequest public string JsonRpc { get; set; } public string Method { get; set; } - [JsonProperty(Required = Required.Default)] - public string[]? Params { get; set; } + public JsonElement Params { get; set; } [JsonConverter(typeof(IdConverter))] public object Id { get; set; } public override string ToString() { - string paramsString = Params is null ? string.Empty : $"{string.Join(",", Params)}"; - return $"ID {Id}, {Method}({paramsString})"; + return $"Id:{Id}, {Method}({Params})"; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponse.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponse.cs index 29a10aee90e..600181e1eb3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponse.cs @@ -2,11 +2,19 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; + using Nethermind.Serialization.Json; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.JsonRpc.Modules.Subscribe; +using Nethermind.Int256; namespace Nethermind.JsonRpc { + [JsonDerivedType(typeof(JsonRpcResponse))] + [JsonDerivedType(typeof(JsonRpcSuccessResponse))] + [JsonDerivedType(typeof(JsonRpcErrorResponse))] + [JsonDerivedType(typeof(JsonRpcSubscriptionResponse))] public class JsonRpcResponse : IDisposable { private Action? _disposableAction; @@ -16,11 +24,26 @@ public JsonRpcResponse(Action? disposableAction = null) _disposableAction = disposableAction; } - [JsonProperty(PropertyName = "jsonrpc", Order = 0)] + public void AddDisposable(Action disposableAction) + { + if (_disposableAction is null) + { + _disposableAction = disposableAction; + } + else + { + _disposableAction += disposableAction; + } + } + + [JsonPropertyName("jsonrpc")] + [JsonPropertyOrder(0)] public readonly string JsonRpc = "2.0"; [JsonConverter(typeof(IdConverter))] - [JsonProperty(PropertyName = "id", Order = 2, NullValueHandling = NullValueHandling.Include)] + [JsonPropertyName("id")] + [JsonPropertyOrder(2)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public object? Id { get; set; } [JsonIgnore] @@ -35,9 +58,14 @@ public virtual void Dispose() public class JsonRpcSuccessResponse : JsonRpcResponse { - [JsonProperty(PropertyName = "result", NullValueHandling = NullValueHandling.Include, Order = 1)] + [JsonPropertyName("result")] + [JsonPropertyOrder(1)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public object? Result { get; set; } + [JsonConstructor] + public JsonRpcSuccessResponse() : base(null) { } + public JsonRpcSuccessResponse(Action? disposableAction = null) : base(disposableAction) { } @@ -54,9 +82,14 @@ public override void Dispose() public class JsonRpcErrorResponse : JsonRpcResponse { - [JsonProperty(PropertyName = "error", NullValueHandling = NullValueHandling.Include, Order = 1)] + [JsonPropertyName("error")] + [JsonPropertyOrder(1)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Error? Error { get; set; } + [JsonConstructor] + public JsonRpcErrorResponse() : base(null) { } + public JsonRpcErrorResponse(Action? disposableAction = null) : base(disposableAction) { } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs index 761e49e2e54..2c34729f701 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs @@ -7,26 +7,26 @@ using System.Linq; using System.Reflection; using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; + using Nethermind.Core; using Nethermind.Core.Attributes; +using Nethermind.Core.Collections; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Exceptions; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.State; -using Newtonsoft.Json; -using JsonSerializer = Newtonsoft.Json.JsonSerializer; namespace Nethermind.JsonRpc; -[Todo(Improve.Refactor, "Use JsonConverters and JSON serialization everywhere")] public class JsonRpcService : IJsonRpcService { private readonly ILogger _logger; private readonly IRpcModuleProvider _rpcModuleProvider; - private readonly JsonSerializer _serializer; private readonly HashSet _methodsLoggingFiltering; private readonly int _maxLoggedRequestParametersCharacters; @@ -34,30 +34,8 @@ public JsonRpcService(IRpcModuleProvider rpcModuleProvider, ILogManager logManag { _logger = logManager.GetClassLogger(); _rpcModuleProvider = rpcModuleProvider; - _serializer = rpcModuleProvider.Serializer; _methodsLoggingFiltering = (jsonRpcConfig.MethodsLoggingFiltering ?? Array.Empty()).ToHashSet(); _maxLoggedRequestParametersCharacters = jsonRpcConfig.MaxLoggedRequestParametersCharacters ?? int.MaxValue; - - List converterList = new(); - foreach (JsonConverter converter in rpcModuleProvider.Converters) - { - if (_logger.IsDebug) _logger.Debug($"Registering {converter.GetType().Name} inside {nameof(JsonRpcService)}"); - _serializer.Converters.Add(converter); - converterList.Add(converter); - } - - foreach (JsonConverter converter in EthereumJsonSerializer.CommonConverters) - { - if (_logger.IsDebug) _logger.Debug($"Registering {converter.GetType().Name} (default)"); - _serializer.Converters.Add(converter); - converterList.Add(converter); - } - - BlockParameterConverter blockParameterConverter = new(); - _serializer.Converters.Add(blockParameterConverter); - converterList.Add(blockParameterConverter); - - Converters = converterList.ToArray(); } public async Task SendRequestAsync(JsonRpcRequest rpcRequest, JsonRpcContext context) @@ -108,21 +86,28 @@ private async Task ExecuteRequestAsync(JsonRpcRequest rpcReques { string methodName = rpcRequest.Method.Trim(); - (MethodInfo MethodInfo, bool ReadOnly) result = _rpcModuleProvider.Resolve(methodName); + (MethodInfo MethodInfo, ParameterInfo[] expectedParameters, bool ReadOnly) result = _rpcModuleProvider.Resolve(methodName); return result.MethodInfo is not null ? await ExecuteAsync(rpcRequest, methodName, result, context) : GetErrorResponse(methodName, ErrorCodes.MethodNotFound, "Method not found", $"{rpcRequest.Method}", rpcRequest.Id); } private async Task ExecuteAsync(JsonRpcRequest request, string methodName, - (MethodInfo Info, bool ReadOnly) method, JsonRpcContext context) + (MethodInfo Info, ParameterInfo[] expectedParameters, bool ReadOnly) method, JsonRpcContext context) { - ParameterInfo[] expectedParameters = method.Info.GetParameters(); - string?[] providedParameters = request.Params ?? Array.Empty(); + JsonElement providedParameters = request.Params; - LogRequest(methodName, providedParameters, expectedParameters); + LogRequest(methodName, providedParameters, method.expectedParameters); + + int missingParamsCount = method.expectedParameters.Length - providedParameters.GetArrayLength(); + foreach (JsonElement item in providedParameters.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Null || (item.ValueKind == JsonValueKind.String && item.ValueEquals(ReadOnlySpan.Empty))) + { + missingParamsCount++; + } + } - int missingParamsCount = expectedParameters.Length - providedParameters.Length + (providedParameters.Count(string.IsNullOrWhiteSpace)); int explicitNullableParamsCount = 0; if (missingParamsCount != 0) @@ -133,18 +118,18 @@ private async Task ExecuteAsync(JsonRpcRequest request, string hasIncorrectParameters = false; for (int i = 0; i < missingParamsCount; i++) { - int parameterIndex = expectedParameters.Length - missingParamsCount + i; - bool nullable = IsNullableParameter(expectedParameters[parameterIndex]); + int parameterIndex = method.expectedParameters.Length - missingParamsCount + i; + bool nullable = IsNullableParameter(method.expectedParameters[parameterIndex]); // if the null is the default parameter it could be passed in an explicit way as "" or null // or we can treat null as a missing parameter. Two tests for this cases: // Eth_call_is_working_with_implicit_null_as_the_last_argument and Eth_call_is_working_with_explicit_null_as_the_last_argument - bool isExplicit = providedParameters.Length >= parameterIndex + 1; + bool isExplicit = providedParameters.GetArrayLength() >= parameterIndex + 1; if (nullable && isExplicit) { explicitNullableParamsCount += 1; } - if (!expectedParameters[expectedParameters.Length - missingParamsCount + i].IsOptional && !nullable) + if (!method.expectedParameters[method.expectedParameters.Length - missingParamsCount + i].IsOptional && !nullable) { hasIncorrectParameters = true; break; @@ -154,7 +139,7 @@ private async Task ExecuteAsync(JsonRpcRequest request, string if (hasIncorrectParameters) { - return GetErrorResponse(methodName, ErrorCodes.InvalidParams, "Invalid params", $"Incorrect parameters count, expected: {expectedParameters.Length}, actual: {expectedParameters.Length - missingParamsCount}", request.Id); + return GetErrorResponse(methodName, ErrorCodes.InvalidParams, "Invalid params", $"Incorrect parameters count, expected: {method.expectedParameters.Length}, actual: {method.expectedParameters.Length - missingParamsCount}", request.Id); } } @@ -162,9 +147,9 @@ private async Task ExecuteAsync(JsonRpcRequest request, string //prepare parameters object[]? parameters = null; - if (expectedParameters.Length > 0) + if (method.expectedParameters.Length > 0) { - parameters = DeserializeParameters(expectedParameters, providedParameters, missingParamsCount); + parameters = DeserializeParameters(method.expectedParameters, providedParameters, missingParamsCount); if (parameters is null) { if (_logger.IsWarn) _logger.Warn($"Incorrect JSON RPC parameters when calling {methodName} with params [{string.Join(", ", providedParameters)}]"); @@ -234,7 +219,7 @@ private async Task ExecuteAsync(JsonRpcRequest request, string : GetSuccessResponse(methodName, resultWrapper.Data, request.Id, returnAction); } - private void LogRequest(string methodName, string?[] providedParameters, ParameterInfo[] expectedParameters) + private void LogRequest(string methodName, JsonElement providedParameters, ParameterInfo[] expectedParameters) { if (_logger.IsDebug && !_methodsLoggingFiltering.Contains(methodName)) { @@ -247,29 +232,32 @@ private void LogRequest(string methodName, string?[] providedParameters, Paramet int paramsCount = 0; const string separator = ", "; - for (int i = 0; i < providedParameters.Length; i++) + if (providedParameters.ValueKind != JsonValueKind.Undefined && providedParameters.ValueKind != JsonValueKind.Null) { - string? parameter = expectedParameters.ElementAtOrDefault(i)?.Name == "passphrase" - ? "{passphrase}" - : providedParameters[i]; - - if (paramsLength > _maxLoggedRequestParametersCharacters) + foreach (JsonElement param in providedParameters.EnumerateArray()) { - int toRemove = paramsLength - _maxLoggedRequestParametersCharacters; - builder.Remove(builder.Length - toRemove, toRemove); - builder.Append("..."); - break; - } + string? parameter = expectedParameters.ElementAtOrDefault(paramsCount)?.Name == "passphrase" + ? "{passphrase}" + : param.GetRawText(); - if (paramsCount != 0) - { - builder.Append(separator); - paramsLength += separator.Length; - } + if (paramsLength > _maxLoggedRequestParametersCharacters) + { + int toRemove = paramsLength - _maxLoggedRequestParametersCharacters; + builder.Remove(builder.Length - toRemove, toRemove); + builder.Append("..."); + break; + } - builder.Append(parameter); - paramsLength += (parameter?.Length ?? 0); - paramsCount++; + if (paramsCount != 0) + { + builder.Append(separator); + paramsLength += separator.Length; + } + + builder.Append(parameter); + paramsLength += (parameter?.Length ?? 0); + paramsCount++; + } } builder.Append(']'); string log = builder.ToString(); @@ -277,78 +265,99 @@ private void LogRequest(string methodName, string?[] providedParameters, Paramet } } - private object[]? DeserializeParameters(ParameterInfo[] expectedParameters, string?[] providedParameters, int missingParamsCount) + private object? DeserializeParameter(JsonElement providedParameter, ParameterInfo expectedParameter) { - try + Type paramType = expectedParameter.ParameterType; + if (paramType.IsByRef) { - List executionParameters = new List(); - for (int i = 0; i < providedParameters.Length; i++) - { - string providedParameter = providedParameters[i]; - ParameterInfo expectedParameter = expectedParameters[i]; - Type paramType = expectedParameter.ParameterType; - if (paramType.IsByRef) - { - paramType = paramType.GetElementType(); - } + paramType = paramType.GetElementType(); + } - if (string.IsNullOrWhiteSpace(providedParameter)) - { - if (providedParameter is null && IsNullableParameter(expectedParameter)) - { - executionParameters.Add(null); - } - else - { - executionParameters.Add(Type.Missing); - } - continue; - } + if (providedParameter.ValueKind == JsonValueKind.Null || (providedParameter.ValueKind == JsonValueKind.String && providedParameter.ValueEquals(ReadOnlySpan.Empty))) + { + if (providedParameter.ValueKind == JsonValueKind.Null && IsNullableParameter(expectedParameter)) + { + return null; + } + else + { + return Type.Missing; + } + } - object? executionParam; - if (typeof(IJsonRpcParam).IsAssignableFrom(paramType)) + object? executionParam; + if (paramType.IsAssignableTo(typeof(IJsonRpcParam))) + { + IJsonRpcParam jsonRpcParam = (IJsonRpcParam)Activator.CreateInstance(paramType); + jsonRpcParam!.ReadJson(providedParameter, EthereumJsonSerializer.JsonOptions); + executionParam = jsonRpcParam; + } + else if (paramType == typeof(string)) + { + executionParam = providedParameter.GetString(); + } + else + { + if (providedParameter.ValueKind == JsonValueKind.String) + { + JsonConverter converter = EthereumJsonSerializer.JsonOptions.GetConverter(paramType); + if (converter.GetType().FullName.StartsWith("System.")) { - IJsonRpcParam jsonRpcParam = (IJsonRpcParam)Activator.CreateInstance(paramType); - jsonRpcParam!.ReadJson(_serializer, providedParameter); - executionParam = jsonRpcParam; + executionParam = JsonSerializer.Deserialize(providedParameter.GetString(), paramType, EthereumJsonSerializer.JsonOptions); } - else if (paramType == typeof(string)) + else { - executionParam = providedParameter; + executionParam = providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } - else if (paramType == typeof(string[])) + } + else + { + executionParam = providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + } + } + + return executionParam; + } + + private object[]? DeserializeParameters(ParameterInfo[] expectedParameters, JsonElement providedParameters, int missingParamsCount) + { + const int parallelThreshold = 4; + try + { + int arrayLength = providedParameters.GetArrayLength(); + int totalLength = arrayLength + missingParamsCount; + + if (totalLength == 0) return Array.Empty(); + + object[] executionParameters = new object[totalLength]; + + if (arrayLength <= parallelThreshold) + { + for (int i = 0; i < arrayLength; i++) { - executionParam = _serializer.Deserialize(new JsonTextReader(new StringReader(providedParameter))); + JsonElement providedParameter = providedParameters[i]; + ParameterInfo expectedParameter = expectedParameters[i]; + + executionParameters[i] = DeserializeParameter(providedParameter, expectedParameter); } - else + } + else if (arrayLength > parallelThreshold) + { + Parallel.For(0, arrayLength, (int i) => { - if (providedParameter.StartsWith('[') || providedParameter.StartsWith('{')) - { - executionParam = _serializer.Deserialize(new JsonTextReader(new StringReader(providedParameter)), paramType); - if (executionParam is null && !IsNullableParameter(expectedParameter)) - { - executionParameters.Add(Type.Missing); - } - } - else - { - var stringReader = providedParameter.StartsWith('\"') && providedParameter.EndsWith('\"') - ? new StringReader(providedParameter) - : new StringReader($"\"{providedParameter}\""); - var jsonTextReader = new JsonTextReader(stringReader); - executionParam = _serializer.Deserialize(jsonTextReader, paramType); - } - } + JsonElement providedParameter = providedParameters[i]; + ParameterInfo expectedParameter = expectedParameters[i]; - executionParameters.Add(executionParam); + executionParameters[i] = DeserializeParameter(providedParameter, expectedParameter); + }); } - for (int i = 0; i < missingParamsCount; i++) + for (int i = arrayLength; i < totalLength; i++) { - executionParameters.Add(Type.Missing); + executionParameters[i] = Type.Missing; } - return executionParameters.ToArray(); + return executionParameters; } catch (Exception e) { @@ -393,8 +402,6 @@ private JsonRpcResponse GetSuccessResponse(string methodName, object result, obj public JsonRpcErrorResponse GetErrorResponse(int errorCode, string errorMessage, object? id = null, string? methodName = null) => GetErrorResponse(methodName ?? string.Empty, errorCode, errorMessage, null, id); - public JsonConverter[] Converters { get; } - private JsonRpcErrorResponse GetErrorResponse( string methodName, int errorCode, diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcUrl.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcUrl.cs index 41b4cadc139..4221fb7b4e2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcUrl.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcUrl.cs @@ -25,8 +25,7 @@ public JsonRpcUrl(string scheme, string host, int port, RpcEndpoint rpcEndpoint, public static JsonRpcUrl Parse(string packedUrlValue) { - if (packedUrlValue is null) - throw new ArgumentNullException(nameof(packedUrlValue)); + ArgumentNullException.ThrowIfNull(packedUrlValue); string[] parts = packedUrlValue.Split('|'); if (parts.Length != 3 && parts.Length != 4) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs index a3d0aa3e2f7..22a2e715903 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/AdminRpcModule.cs @@ -63,7 +63,7 @@ private void BuildNodeInfo() private void UpdateEthProtocolInfo() { _nodeInfo.Protocols["eth"].Difficulty = _blockTree.Head?.TotalDifficulty ?? 0; - _nodeInfo.Protocols["eth"].ChainId = _blockTree.ChainId; + _nodeInfo.Protocols["eth"].NewtorkId = _blockTree.ChainId; _nodeInfo.Protocols["eth"].HeadHash = _blockTree.HeadHash; _nodeInfo.Protocols["eth"].GenesisHash = _blockTree.GenesisHash; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/EthProtocolInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/EthProtocolInfo.cs index 025ff35ecf1..4e620f5aceb 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/EthProtocolInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/EthProtocolInfo.cs @@ -3,19 +3,20 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Admin { public class EthProtocolInfo { - [JsonProperty("difficulty", Order = 0)] + [JsonPropertyName("difficulty")] public UInt256 Difficulty { get; set; } - [JsonProperty("genesis", Order = 1)] + [JsonPropertyName("genesis")] public Hash256 GenesisHash { get; set; } - [JsonProperty("head", Order = 2)] + [JsonPropertyName("head")] public Hash256 HeadHash { get; set; } - [JsonProperty("network", Order = 3)] - public ulong ChainId { get; set; } + [JsonPropertyName("network")] + public ulong NewtorkId { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs index d7e8df3f724..71e819d3f51 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/IAdminRpcModule.cs @@ -3,67 +3,68 @@ using System.Threading.Tasks; using Nethermind.Blockchain.FullPruning; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.JsonRpc.Modules.Admin +namespace Nethermind.JsonRpc.Modules.Admin; + +[RpcModule(ModuleType.Admin)] +public interface IAdminRpcModule : IRpcModule { - [RpcModule(ModuleType.Admin)] - public interface IAdminRpcModule : IRpcModule - { - [JsonRpcMethod(Description = "Adds given node.", - EdgeCaseHint = "", - ResponseDescription = "Added node", - ExampleResponse = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"", - IsImplemented = true)] - Task> admin_addPeer( - [JsonRpcParameter(Description = "Given node", ExampleValue = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"")] - string enode, - [JsonRpcParameter(Description = "Adding to static nodes if `true` (optional)", ExampleValue = "true")] - bool addToStaticNodes = false); + [JsonRpcMethod(Description = "Adds given node.", + EdgeCaseHint = "", + ResponseDescription = "Added node", + ExampleResponse = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"", + IsImplemented = true)] + Task> admin_addPeer( + [JsonRpcParameter(Description = "Given node", ExampleValue = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"")] + string enode, + [JsonRpcParameter(Description = "Adding to static nodes if `true` (optional)", ExampleValue = "true")] + bool addToStaticNodes = false); - [JsonRpcMethod(Description = "Removes given node.", - EdgeCaseHint = "", - ResponseDescription = "Removed node", - ExampleResponse = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"", - IsImplemented = true)] - Task> admin_removePeer( - [JsonRpcParameter(Description = "Given node", ExampleValue = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"")] - string enode, - [JsonRpcParameter(Description = "Removing from static nodes if `true` (optional)", ExampleValue = "true")] - bool removeFromStaticNodes = false); + [JsonRpcMethod(Description = "Removes given node.", + EdgeCaseHint = "", + ResponseDescription = "Removed node", + ExampleResponse = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"", + IsImplemented = true)] + Task> admin_removePeer( + [JsonRpcParameter(Description = "Given node", ExampleValue = "\"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\"")] + string enode, + [JsonRpcParameter(Description = "Removing from static nodes if `true` (optional)", ExampleValue = "true")] + bool removeFromStaticNodes = false); - [JsonRpcMethod(Description = "Displays a list of connected peers including information about them (`clientId`, `host`, `port`, `address`, `isBootnode`, `isStatic`, `enode`).", - EdgeCaseHint = "", - ResponseDescription = "List of connected peers including information", - ExampleResponse = "[\n {\n \"clientId\": \"Nethermind/v1.10.33-1-5c4c185e8-20210310/X64-Linux/5.0.2\",\n \"host\": \"94.237.54.114\",\n \"port\": 30313,\n \"address\": \"94.237.54.114:30313\",\n \"isBootnode\": false,\n \"isTrusted\": false,\n \"isStatic\": false,\n \"enode\": \"enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313\",\n \"clientType\": \"Nethermind\",\n \"ethDetails\": \"eth65\",\n \"lastSignal\": \"03/11/2021 12:33:58\"\n },\n \n (...)\n \n]", - IsImplemented = true)] - ResultWrapper admin_peers( - [JsonRpcParameter(Description = "If true, including `clientType`, `ethDetails` and `lastSignal` (optional)", ExampleValue = "true")] - bool includeDetails = false); + [JsonRpcMethod(Description = "Displays a list of connected peers including information about them (`clientId`, `host`, `port`, `address`, `isBootnode`, `isStatic`, `enode`).", + EdgeCaseHint = "", + ResponseDescription = "List of connected peers including information", + ExampleResponse = "[\n {\n \"clientId\": \"Nethermind/v1.10.33-1-5c4c185e8-20210310/X64-Linux/5.0.2\",\n \"host\": \"94.237.54.114\",\n \"port\": 30313,\n \"address\": \"94.237.54.114:30313\",\n \"isBootnode\": false,\n \"isTrusted\": false,\n \"isStatic\": false,\n \"enode\": \"enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313\",\n \"clientType\": \"Nethermind\",\n \"ethDetails\": \"eth65\",\n \"lastSignal\": \"03/11/2021 12:33:58\"\n },\n \n (...)\n \n]", + IsImplemented = true)] + ResultWrapper admin_peers( + [JsonRpcParameter(Description = "If true, including `clientType`, `ethDetails` and `lastSignal` (optional)", ExampleValue = "true")] + bool includeDetails = false); - [JsonRpcMethod(Description = "Displays relevant information about this node.", - EdgeCaseHint = "", - ResponseDescription = "Information about this node", - ExampleResponse = "{\n \"enode\": \"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\",\n \"id\": \"b70bb308924de8247d73844f80561e488ae731105a6ef46004e4579edd4f378a\",\n \"ip\": \"85.221.141.144\",\n \"listenAddr\": \"85.221.141.144:30303\",\n \"name\": \"Nethermind/v1.10.37-0-068e5c399-20210315/X64-Windows/5.0.3\",\n \"ports\": {\n \"discovery\": 30303,\n \"listener\": 30303\n },\n \"protocols\": {\n \"eth\": {\n \"difficulty\": \"0x6372ca\",\n \"genesis\": \"0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a\",\n \"head\": \"0xf266b2639ef7e1db6ee769f7b161ef7eb2d74beb0ab8ffcd270036da04b41cd4\",\n \"network\": \"0x5\"\n }\n }\n}", - IsImplemented = true)] - ResultWrapper admin_nodeInfo(); + [JsonRpcMethod(Description = "Displays relevant information about this node.", + EdgeCaseHint = "", + ResponseDescription = "Information about this node", + ExampleResponse = "{\n \"enode\": \"enode://deed356ddcaa1eb33a859b818a134765fff2a3dd5cd5b3d6cbe08c9424dca53b947bdc1c64e6f1257e29bb2960ac0a4fb56e307f360b7f8d4ddf48024cdb9d68@85.221.141.144:30303\",\n \"id\": \"b70bb308924de8247d73844f80561e488ae731105a6ef46004e4579edd4f378a\",\n \"ip\": \"85.221.141.144\",\n \"listenAddr\": \"85.221.141.144:30303\",\n \"name\": \"Nethermind/v1.10.37-0-068e5c399-20210315/X64-Windows/5.0.3\",\n \"ports\": {\n \"discovery\": 30303,\n \"listener\": 30303\n },\n \"protocols\": {\n \"eth\": {\n \"difficulty\": \"0x6372ca\",\n \"genesis\": \"0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a\",\n \"head\": \"0xf266b2639ef7e1db6ee769f7b161ef7eb2d74beb0ab8ffcd270036da04b41cd4\",\n \"network\": \"0x5\"\n }\n }\n}", + IsImplemented = true)] + ResultWrapper admin_nodeInfo(); - [JsonRpcMethod(Description = "Base data directory path", - IsImplemented = false)] - ResultWrapper admin_dataDir(); + [JsonRpcMethod(Description = "Base data directory path", + IsImplemented = false)] + ResultWrapper admin_dataDir(); - [JsonRpcMethod(Description = "[DEPRECATED]", - IsImplemented = false)] - ResultWrapper admin_setSolc(); + [JsonRpcMethod(Description = "[DEPRECATED]", + IsImplemented = false)] + ResultWrapper admin_setSolc(); - [JsonRpcMethod(Description = "Runs full pruning if enabled.", - EdgeCaseHint = "", - ExampleResponse = "\"Starting\"", - IsImplemented = true)] - ResultWrapper admin_prune(); - } + [JsonRpcMethod(Description = "Runs full pruning if enabled.", + EdgeCaseHint = "", + ExampleResponse = "\"Starting\"", + IsImplemented = true)] + ResultWrapper admin_prune(); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs index 31f8d5296c9..d1de804f661 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/NodeInfo.cs @@ -2,61 +2,62 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Admin +using Nethermind.Core; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Nethermind.JsonRpc.Modules.Admin; + +/// +/// { +/// enode: "enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@[::]:30303", +/// id: "44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d", +/// ip: "::", +/// listenAddr: "[::]:30303", +/// name: "Geth/v1.5.0-unstable/linux/go1.6", +/// ports: { +/// discovery: 30303, +/// listener: 30303 +/// }, +/// protocols: { +/// eth: { +/// difficulty: 17334254859343145000, +/// genesis: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", +/// head: "0xb83f73fbe6220c111136aefd27b160bf4a34085c65ba89f24246b3162257c36a", +/// network: 1 +/// } +/// } +/// } +/// +public class NodeInfo { - /// - /// { - /// enode: "enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@[::]:30303", - /// id: "44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d", - /// ip: "::", - /// listenAddr: "[::]:30303", - /// name: "Geth/v1.5.0-unstable/linux/go1.6", - /// ports: { - /// discovery: 30303, - /// listener: 30303 - /// }, - /// protocols: { - /// eth: { - /// difficulty: 17334254859343145000, - /// genesis: "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", - /// head: "0xb83f73fbe6220c111136aefd27b160bf4a34085c65ba89f24246b3162257c36a", - /// network: 1 - /// } - /// } - /// } - /// - public class NodeInfo + public NodeInfo() { - public NodeInfo() + Protocols = new Dictionary { - Protocols = new Dictionary - { - { "eth", new EthProtocolInfo() } - }; - Ports = new PortsInfo(); - } + { "eth", new EthProtocolInfo() } + }; + Ports = new PortsInfo(); + } - [JsonProperty("enode", Order = 0)] - public string Enode { get; set; } + [JsonPropertyName("enode")] + public string Enode { get; set; } - [JsonProperty("id", Order = 1)] - public string Id { get; set; } + [JsonPropertyName("id")] + public string Id { get; set; } - [JsonProperty("ip", Order = 2)] - public string? Ip { get; set; } + [JsonPropertyName("ip")] + public string? Ip { get; set; } - [JsonProperty("listenAddr", Order = 3)] - public string ListenAddress { get; set; } + [JsonPropertyName("listenAddr")] + public string ListenAddress { get; set; } - [JsonProperty("name", Order = 4)] - public string Name { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } - [JsonProperty("ports", Order = 5)] - public PortsInfo Ports { get; set; } + [JsonPropertyName("ports")] + public PortsInfo Ports { get; set; } - [JsonProperty("protocols", Order = 6)] - public Dictionary Protocols { get; set; } - } + [JsonPropertyName("protocols")] + public Dictionary Protocols { get; set; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PortsInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PortsInfo.cs index d01028582bf..87160e48f3a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PortsInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PortsInfo.cs @@ -1,15 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Admin { public class PortsInfo { - [JsonProperty("discovery", Order = 0)] + [JsonPropertyName("discovery")] public int Discovery { get; set; } - [JsonProperty("listener", Order = 1)] + [JsonPropertyName("listener")] public int Listener { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs index 92f25eeea44..7b36aa1930b 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs @@ -19,7 +19,8 @@ using Nethermind.State; using Nethermind.Synchronization.ParallelSync; using Nethermind.Trie.Pruning; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.DebugModule; @@ -115,8 +116,4 @@ public override IDebugRpcModule Create() return new DebugRpcModule(_logManager, debugBridge, _jsonRpcConfig); } - - public static JsonConverter[] Converters = { new GethLikeTxTraceConverter(), new JavaScriptObjectConverter(), new GethLikeJavaScriptTraceConverter() }; - - public override IReadOnlyCollection GetConverters() => Converters; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/JavaScriptObjectConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/JavaScriptObjectConverter.cs deleted file mode 100644 index 4cb644f35a3..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/JavaScriptObjectConverter.cs +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Numerics; -using Microsoft.ClearScript; -using Microsoft.ClearScript.JavaScript; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.DebugModule; - -public class JavaScriptObjectConverter : JsonConverter -{ - [ThreadStatic] - private static bool _disabled; - - public override bool CanRead => false; - - public override bool CanConvert(Type objectType) => - _disabled ? (_disabled = false) : typeof(IJavaScriptObject).IsAssignableFrom(objectType); - - public override void WriteJson(JsonWriter writer, object? o, JsonSerializer serializer) - { - if (o is IDictionary dictionary) - { - if (dictionary.TryGetValue("value", out object? value)) - { - // value is marshaled to BigInteger by ClearScript - if (value is BigInteger bigInteger) - { - writer.WriteValue(bigInteger.ToString()); - return; - } - - if (value == Undefined.Value) - { - dictionary.Remove("value"); - } - } - - // remove undefined errors - if (dictionary.TryGetValue("error", out object? error) && error == Undefined.Value) - { - dictionary.Remove("error"); - } - } - - // fallback to standard serialization - _disabled = true; - try - { - serializer.Serialize(writer, o); - } - finally - { - _disabled = false; - } - } - - public override object? ReadJson( - JsonReader reader, - Type objectType, - object? o, - JsonSerializer jsonSerializer) => throw new NotSupportedException(); -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs index 8fe9b2b2f91..9fe1dc13fee 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs @@ -11,7 +11,9 @@ using Nethermind.JsonRpc.Data; using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Runtime.CompilerServices; namespace Nethermind.JsonRpc.Modules.Eth; @@ -25,6 +27,7 @@ protected BlockForRpc() } + [SkipLocalsInit] public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider specProvider) { _isAuRaBlock = block.Header.AuRaSignature is not null; @@ -89,22 +92,22 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s public long GasLimit { get; set; } public long GasUsed { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Hash256 Hash { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Bloom LogsBloom { get; set; } public Address Miner { get; set; } public Hash256 MixHash { get; set; } public bool ShouldSerializeMixHash() => !_isAuRaBlock && MixHash is not null; - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public byte[] Nonce { get; set; } public bool ShouldSerializeNonce() => !_isAuRaBlock; - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? Number { get; set; } public Hash256 ParentHash { get; set; } public Hash256 ReceiptsRoot { get; set; } @@ -113,7 +116,7 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s public bool ShouldSerializeSignature() => _isAuRaBlock; public long Size { get; set; } public Hash256 StateRoot { get; set; } - [JsonConverter(typeof(NullableLongConverter), NumberConversion.Raw)] + [JsonConverter(typeof(NullableRawLongConverter))] public long? Step { get; set; } public bool ShouldSerializeStep() => _isAuRaBlock; public UInt256 TotalDifficulty { get; set; } @@ -124,18 +127,18 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s public Hash256 TransactionsRoot { get; set; } public IEnumerable Uncles { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IEnumerable? Withdrawals { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Hash256? WithdrawalsRoot { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ulong? BlobGasUsed { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ulong? ExcessBlobGas { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Hash256? ParentBeaconBlockRoot { get; set; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs index a864856c117..32444ae6c4e 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs @@ -15,7 +15,8 @@ using Nethermind.State; using Nethermind.TxPool; using Nethermind.Wallet; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Eth { @@ -79,13 +80,5 @@ public override IEthRpcModule Create() _ethSyncingInfo, new FeeHistoryOracle(_blockTree, _receiptStorage, _specProvider)); } - - public static List Converters = new() - { - new SyncingResultConverter(), - new ProofConverter() - }; - - public override IReadOnlyCollection GetConverters() => Converters; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 00cfbd9ef6d..aef4c841ce5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -580,7 +580,7 @@ public ResultWrapper> eth_getFilterLogs(UInt256 filterId) if (id < 0 || !filterFound) { cancellationTokenSource.Dispose(); - return ResultWrapper>.Fail($"Filter with id: '{filterId}' does not exist."); + return ResultWrapper>.Fail($"Filter with id: {filterId} does not exist."); } else { @@ -633,7 +633,8 @@ public ResultWrapper> eth_getLogs(Filter filter) if (fromBlockNumber > toBlockNumber && toBlockNumber != 0) { cancellationTokenSource.Dispose(); - return ResultWrapper>.Fail($"'From' block '{fromBlockNumber}' is later than 'to' block '{toBlockNumber}'.", ErrorCodes.InvalidParams); + + return ResultWrapper>.Fail($"From block {fromBlockNumber} is later than to block {toBlockNumber}.", ErrorCodes.InvalidParams); } try diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs index 69440a1991a..34e2c07a05d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs @@ -46,7 +46,7 @@ public ResultWrapper GetFeeHistory(long blockCount, BlockPara baseFeePerGas.Push(BaseFeeCalculator.Calculate(block!.Header, _specProvider.GetSpecFor1559(block!.Number + 1))); Stack gasUsedRatio = new((int)blockCount); - Stack? rewards = rewardPercentiles is null || rewardPercentiles.Any() is false ? null : new Stack((int)blockCount); + Stack? rewards = rewardPercentiles is null || rewardPercentiles.Length == 0 ? null : new Stack((int)blockCount); while (block is not null && blockCount > 0) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs index 56f0699d690..0b3a0284bdb 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json; + using Nethermind.Blockchain.Find; using Nethermind.JsonRpc.Data; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace Nethermind.JsonRpc.Modules.Eth; @@ -19,46 +19,74 @@ public class Filter : IJsonRpcParam public IEnumerable? Topics { get; set; } - public void ReadJson(JsonSerializer serializer, string json) + public void ReadJson(JsonElement filter, JsonSerializerOptions options) { - var filter = serializer.Deserialize(json.ToJsonTextReader()); - var blockHash = filter["blockHash"]?.Value(); - - if (blockHash is null) + JsonDocument doc = null; + string blockHash = null; + try { - FromBlock = BlockParameterConverter.GetBlockParameter(filter["fromBlock"]?.Value()); - ToBlock = BlockParameterConverter.GetBlockParameter(filter["toBlock"]?.Value()); - } - else - FromBlock = - ToBlock = BlockParameterConverter.GetBlockParameter(blockHash); + if (filter.ValueKind == JsonValueKind.String) + { + doc = JsonDocument.Parse(filter.GetString()); + filter = doc.RootElement; + } + + if (filter.TryGetProperty("blockHash"u8, out JsonElement blockHashElement)) + { + blockHash = blockHashElement.GetString(); + } - Address = GetAddress(filter["address"]); + if (blockHash is null) + { + filter.TryGetProperty("fromBlock"u8, out JsonElement fromBlockElement); + FromBlock = BlockParameterConverter.GetBlockParameter(fromBlockElement.ToString()); + filter.TryGetProperty("toBlock"u8, out JsonElement toBlockElement); + ToBlock = BlockParameterConverter.GetBlockParameter(toBlockElement.ToString()); + } + else + { + FromBlock = ToBlock = BlockParameterConverter.GetBlockParameter(blockHash); + } - var topics = filter["topics"] as JArray; + filter.TryGetProperty("address"u8, out JsonElement addressElement); + Address = GetAddress(addressElement, options); - Topics = topics is null ? null : GetTopics(filter["topics"] as JArray); + if (filter.TryGetProperty("topics"u8, out JsonElement topicsElement) && topicsElement.ValueKind == JsonValueKind.Array) + { + Topics = GetTopics(topicsElement, options); + } + else + { + Topics = null; + } + } + finally + { + doc?.Dispose(); + } } - private static object? GetAddress(JToken? token) => GetSingleOrMany(token); + private static object? GetAddress(JsonElement? token, JsonSerializerOptions options) => GetSingleOrMany(token, options); - private static IEnumerable GetTopics(JArray? array) + private static IEnumerable GetTopics(JsonElement? array, JsonSerializerOptions options) { if (array is null) { yield break; } - foreach (JToken token in array) + foreach (var token in array.GetValueOrDefault().EnumerateArray()) { - yield return GetSingleOrMany(token); + yield return GetSingleOrMany(token, options); } } - private static object? GetSingleOrMany(JToken? token) => token switch + private static object? GetSingleOrMany(JsonElement? token, JsonSerializerOptions options) => token switch { null => null, - JArray _ => token.ToObject>(), - _ => token.Value(), + { ValueKind: JsonValueKind.Undefined } _ => null, + { ValueKind: JsonValueKind.Null } _ => null, + { ValueKind: JsonValueKind.Array } _ => token.GetValueOrDefault().Deserialize(options), + _ => token.GetValueOrDefault().GetString(), }; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SyncingResultConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SyncingResultConverter.cs deleted file mode 100644 index 013f5e90749..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SyncingResultConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Facade.Eth; -using Nethermind.JsonRpc.Modules.Trace; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Eth -{ - public class SyncingResultConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, SyncingResult value, JsonSerializer serializer) - { - if (!value.IsSyncing) - { - writer.WriteValue(false); - return; - } - - writer.WriteStartObject(); - writer.WriteProperty("startingBlock", value.StartingBlock, serializer); - writer.WriteProperty("currentBlock", value.CurrentBlock, serializer); - writer.WriteProperty("highestBlock", value.HighestBlock, serializer); - writer.WriteEndObject(); - } - - public override SyncingResult ReadJson(JsonReader reader, Type objectType, SyncingResult existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/TransactionsOption.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/TransactionsOption.cs index e2224c66568..8dceee0a0c9 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/TransactionsOption.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/TransactionsOption.cs @@ -2,8 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; namespace Nethermind.JsonRpc.Modules.Eth; @@ -11,19 +10,49 @@ public class TransactionsOption : IJsonRpcParam { public bool IncludeTransactions { get; set; } - public void ReadJson(JsonSerializer serializer, string jsonValue) + public void ReadJson(JsonElement jsonValue, JsonSerializerOptions options) { - bool isTrue = string.Equals(jsonValue, bool.TrueString, StringComparison.InvariantCultureIgnoreCase); - bool isFalse = string.Equals(jsonValue, bool.FalseString, StringComparison.InvariantCultureIgnoreCase); + if (jsonValue.ValueKind == JsonValueKind.True) + { + IncludeTransactions = true; + return; + } + if (jsonValue.ValueKind == JsonValueKind.False) + { + IncludeTransactions = false; + return; + } + JsonElement value; + if (jsonValue.ValueKind == JsonValueKind.Object) + { + if (jsonValue.TryGetProperty("includeTransactions"u8, out value)) + { + if (value.ValueKind == JsonValueKind.True) + { + IncludeTransactions = true; + return; + } + if (value.ValueKind == JsonValueKind.False) + { + IncludeTransactions = false; + return; + } + } + return; + } + + string? text = jsonValue.GetString(); + bool isTrue = string.Equals(text, bool.TrueString, StringComparison.InvariantCultureIgnoreCase); + bool isFalse = string.Equals(text, bool.FalseString, StringComparison.InvariantCultureIgnoreCase); IncludeTransactions = isTrue || isFalse ? isTrue - : GetIncludeTransactions(serializer.Deserialize(jsonValue.ToJsonTextReader())["includeTransactions"]); + : GetIncludeTransactions(jsonValue.TryGetProperty("includeTransactions"u8, out value) ? value : null, options); } - private static bool GetIncludeTransactions(JToken? token) => token switch + private static bool GetIncludeTransactions(JsonElement? token, JsonSerializerOptions options) => token switch { null => false, - _ => token.ToObject(), + _ => token.GetValueOrDefault().Deserialize(options), }; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleFactory.cs index 136bb262015..225df256465 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleFactory.cs @@ -2,14 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules { public interface IRpcModuleFactory where T : IRpcModule { T Create(); - - IReadOnlyCollection GetConverters(); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleProvider.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleProvider.cs index 6fca4c8b045..084ec4e5fbb 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleProvider.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/IRpcModuleProvider.cs @@ -4,7 +4,10 @@ using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; -using Newtonsoft.Json; + +using Nethermind.Serialization.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules { @@ -14,16 +17,14 @@ public interface IRpcModuleProvider { void Register(IRpcModulePool pool) where T : IRpcModule; - IReadOnlyCollection Converters { get; } - IReadOnlyCollection Enabled { get; } IReadOnlyCollection All { get; } - JsonSerializer Serializer { get; } + IJsonSerializer Serializer { get; } ModuleResolution Check(string methodName, JsonRpcContext context); - (MethodInfo MethodInfo, bool ReadOnly) Resolve(string methodName); + (MethodInfo MethodInfo, ParameterInfo[], bool ReadOnly) Resolve(string methodName); Task Rent(string methodName, bool canBeShared); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleFactoryBase.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleFactoryBase.cs index c4e06f6583d..ca3f50402b2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleFactoryBase.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleFactoryBase.cs @@ -4,7 +4,8 @@ using System; using System.Collections.Generic; using System.Reflection; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules { @@ -26,17 +27,9 @@ public ModuleFactoryBase() ModuleType = attribute.ModuleType; } - // ReSharper disable once StaticMemberInGenericType - private static IReadOnlyCollection _noConverters = new List(); - public abstract T Create(); public string ModuleType { get; } - - public virtual IReadOnlyCollection GetConverters() - { - return _noConverters; - } } public class SingletonFactory : ModuleFactoryBase where T : IRpcModule diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/NullModuleProvider.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/NullModuleProvider.cs index c0c2b2e05f4..8ebedcb9f9f 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/NullModuleProvider.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/NullModuleProvider.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Reflection; +using System.Text.Json.Serialization; using System.Threading.Tasks; -using Newtonsoft.Json; + +using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Modules { @@ -22,9 +24,7 @@ public void Register(IRpcModulePool pool) where T : IRpcModule { } - public JsonSerializer Serializer { get; } = new(); - - public IReadOnlyCollection Converters => Array.Empty(); + public IJsonSerializer Serializer { get; } = new EthereumJsonSerializer(); public IReadOnlyCollection Enabled => Array.Empty(); @@ -32,7 +32,7 @@ public void Register(IRpcModulePool pool) where T : IRpcModule public ModuleResolution Check(string methodName, JsonRpcContext context) => ModuleResolution.Unknown; - public (MethodInfo, bool) Resolve(string methodName) => (null, false); + public (MethodInfo, ParameterInfo[], bool) Resolve(string methodName) => (null, Array.Empty(), false); public Task Rent(string methodName, bool canBeShared) => Null; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/EthProtocolInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/EthProtocolInfo.cs index 29cb22d2b17..697dc49a8b0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/EthProtocolInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/EthProtocolInfo.cs @@ -3,19 +3,20 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Parity { public class EthProtocolInfo { - [JsonProperty("version", Order = 0)] + [JsonPropertyName("version")] public byte Version { get; set; } - [JsonProperty("difficulty", Order = 1)] + [JsonPropertyName("difficulty")] public UInt256 Difficulty { get; set; } - [JsonProperty("head", Order = 2)] + [JsonPropertyName("head")] public Hash256 HeadHash { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityNetPeers.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityNetPeers.cs index 774aefbdee2..a2e97f533cb 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityNetPeers.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityNetPeers.cs @@ -1,22 +1,23 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Parity { public class ParityNetPeers { - [JsonProperty("active", Order = 0)] + [JsonPropertyName("active")] public int Active { get; set; } - [JsonProperty("connected", Order = 1)] + [JsonPropertyName("connected")] public int Connected { get; set; } - [JsonProperty("max", Order = 2)] + [JsonPropertyName("max")] public int Max { get; set; } - [JsonProperty("peers", Order = 3)] + [JsonPropertyName("peers")] public PeerInfo[] Peers { get; set; } public ParityNetPeers() diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs index dc76e9a8045..96c62ae2b7d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs @@ -6,7 +6,8 @@ using Nethermind.Core.Extensions; using Nethermind.Int256; using Nethermind.Evm; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Parity { @@ -14,25 +15,25 @@ public class ParityTransaction { public Hash256 Hash { get; set; } public UInt256? Nonce { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Hash256 BlockHash { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public UInt256? BlockNumber { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public UInt256? TransactionIndex { get; set; } public Address From { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address To { get; set; } public UInt256? Value { get; set; } public UInt256? GasPrice { get; set; } public long? Gas { get; set; } public byte[] Input { get; set; } public byte[] Raw { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address Creates { get; set; } public PublicKey PublicKey { get; set; } public ulong? ChainId { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public object Condition { get; set; } public byte[] R { get; set; } public byte[] S { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerInfo.cs index 9095c0ca77a..cd3a6c4e18e 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerInfo.cs @@ -8,25 +8,26 @@ using Nethermind.Network.P2P; using Nethermind.Network.P2P.ProtocolHandlers; using Nethermind.Stats.Model; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Parity { public class PeerInfo { - [JsonProperty("id", Order = 0)] + [JsonPropertyName("id")] public string Id { get; set; } - [JsonProperty("name", Order = 1)] + [JsonPropertyName("name")] public string Name { get; set; } - [JsonProperty("caps", Order = 2)] + [JsonPropertyName("caps")] public List Caps { get; set; } - [JsonProperty("network", Order = 3)] + [JsonPropertyName("network")] public PeerNetworkInfo Network { get; set; } - [JsonProperty("protocols", Order = 4)] + [JsonPropertyName("protocols")] public Dictionary Protocols { get; set; } public PeerInfo(Peer peer) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerNetworkInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerNetworkInfo.cs index 36983d84f9a..01ef56996c4 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerNetworkInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/PeerNetworkInfo.cs @@ -1,16 +1,17 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Parity { public class PeerNetworkInfo { - [JsonProperty("localAddress", Order = 0)] + [JsonPropertyName("localAddress")] public string LocalAddress { get; set; } - [JsonProperty("remoteAddress", Order = 1)] + [JsonPropertyName("remoteAddress")] public string RemoteAddress { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofModuleFactory.cs index 590f23df523..bca0e9a34b0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofModuleFactory.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; + using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Processing; @@ -11,11 +12,9 @@ using Nethermind.Consensus.Validators; using Nethermind.Core.Specs; using Nethermind.Db; -using Nethermind.JsonRpc.Data; using Nethermind.Logging; using Nethermind.State; using Nethermind.Trie.Pruning; -using Newtonsoft.Json; namespace Nethermind.JsonRpc.Modules.Proof { @@ -59,12 +58,5 @@ public override IProofRpcModule Create() return new ProofRpcModule(tracer, _blockTree, _receiptFinder, _specProvider, _logManager); } - - private static readonly List _converters = new() - { - new ProofConverter() - }; - - public override IReadOnlyCollection GetConverters() => _converters; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/RpcMethodFilter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/RpcMethodFilter.cs index 69ee6d67ae9..fb9b79d8797 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/RpcMethodFilter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/RpcMethodFilter.cs @@ -39,12 +39,13 @@ public RpcMethodFilter(string filePath, IFileSystem fileSystem, ILogger logger) public bool AcceptMethod(string methodName) { - if (!_methodsCache.ContainsKey(methodName)) + if (!_methodsCache.TryGetValue(methodName, out var value)) { - _methodsCache[methodName] = CheckMethod(methodName); + value = CheckMethod(methodName); + _methodsCache[methodName] = value; } - return _methodsCache[methodName]; + return value; } private bool CheckMethod(string methodName) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/RpcModuleProvider.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/RpcModuleProvider.cs index bc2e304d873..72921c8ae10 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/RpcModuleProvider.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/RpcModuleProvider.cs @@ -2,16 +2,19 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Frozen; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Nethermind.Logging; -using Newtonsoft.Json; +using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Modules { + using Pool = (Func> RentModule, Action ReturnModule, IRpcModulePool ModulePool); + public class RpcModuleProvider : IRpcModuleProvider { private readonly ILogger _logger; @@ -19,15 +22,18 @@ public class RpcModuleProvider : IRpcModuleProvider private readonly HashSet _modules = new(StringComparer.InvariantCultureIgnoreCase); private readonly HashSet _enabledModules = new(StringComparer.InvariantCultureIgnoreCase); - private readonly Dictionary _methods = new(StringComparer.InvariantCulture); - private readonly Dictionary> RentModule, Action ReturnModule, IRpcModulePool ModulePool)> _pools = new(); + private FrozenDictionary _methods = FrozenDictionary.Empty; + private FrozenDictionary _pools = FrozenDictionary.Empty; private readonly IRpcMethodFilter _filter = NullRpcMethodFilter.Instance; + private readonly object _updateRegistrationsLock = new(); + public RpcModuleProvider(IFileSystem fileSystem, IJsonRpcConfig jsonRpcConfig, ILogManager logManager) { _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + Serializer = new EthereumJsonSerializer(); _jsonRpcConfig = jsonRpcConfig ?? throw new ArgumentNullException(nameof(jsonRpcConfig)); if (fileSystem.File.Exists(_jsonRpcConfig.CallsFilterFilePath)) { @@ -36,9 +42,7 @@ public RpcModuleProvider(IFileSystem fileSystem, IJsonRpcConfig jsonRpcConfig, I } } - public JsonSerializer Serializer { get; } = new(); - - public IReadOnlyCollection Converters { get; } = new List(); + public IJsonSerializer Serializer { get; } public IReadOnlyCollection Enabled => _enabledModules; @@ -54,26 +58,42 @@ public void Register(IRpcModulePool pool) where T : IRpcModule } string moduleType = attribute.ModuleType; + lock (_updateRegistrationsLock) + { + // FrozenDictionary can't be directly updated (which makes it fast for reading) so we combine the two sets of + // data as an Enumerable create an new FrozenDictionary from it and then update the reference + _pools = GetPools(moduleType, pool).ToFrozenDictionary(StringComparer.Ordinal); + _methods = _methods.Concat(GetMethods(moduleType)).ToFrozenDictionary(StringComparer.Ordinal); + + _modules.Add(moduleType); + + if (_jsonRpcConfig.EnabledModules.Contains(moduleType, StringComparer.InvariantCultureIgnoreCase)) + { + _enabledModules.Add(moduleType); + } + } + } - _pools[moduleType] = (async canBeShared => await pool.GetModule(canBeShared), m => pool.ReturnModule((T)m), pool); - _modules.Add(moduleType); + private IEnumerable> GetPools(string moduleType, IRpcModulePool pool) where T : IRpcModule + { + foreach (KeyValuePair item in _pools) + { + yield return item; + } - IReadOnlyCollection poolConverters = pool.Factory.GetConverters(); - ((List)Converters).AddRange(poolConverters); + yield return new(moduleType, (async canBeShared => await pool.GetModule(canBeShared), m => pool.ReturnModule((T)m), pool)); + } + private IEnumerable> GetMethods(string moduleType) where T : IRpcModule + { foreach ((string name, (MethodInfo info, bool readOnly, RpcEndpoint availability)) in GetMethodDict(typeof(T))) { ResolvedMethodInfo resolvedMethodInfo = new(moduleType, info, readOnly, availability); if (_filter.AcceptMethod(resolvedMethodInfo.ToString())) { - _methods[name] = resolvedMethodInfo; + yield return new(name, resolvedMethodInfo); } } - - if (_jsonRpcConfig.EnabledModules.Contains(moduleType, StringComparer.InvariantCultureIgnoreCase)) - { - _enabledModules.Add(moduleType); - } } public ModuleResolution Check(string methodName, JsonRpcContext context) @@ -90,11 +110,11 @@ public ModuleResolution Check(string methodName, JsonRpcContext context) return _enabledModules.Contains(result.ModuleType) ? ModuleResolution.Enabled : ModuleResolution.Disabled; } - public (MethodInfo, bool) Resolve(string methodName) + public (MethodInfo, ParameterInfo[], bool) Resolve(string methodName) { - if (!_methods.TryGetValue(methodName, out ResolvedMethodInfo result)) return (null, false); + if (!_methods.TryGetValue(methodName, out ResolvedMethodInfo result)) return (null, Array.Empty(), false); - return (result.MethodInfo, result.ReadOnly); + return (result.MethodInfo, result.ExpectedParameters, result.ReadOnly); } public Task Rent(string methodName, bool canBeShared) @@ -136,12 +156,14 @@ public ResolvedMethodInfo( { ModuleType = moduleType; MethodInfo = methodInfo; + ExpectedParameters = methodInfo.GetParameters(); ReadOnly = readOnly; Availability = availability; } public string ModuleType { get; } public MethodInfo MethodInfo { get; } + public ParameterInfo[] ExpectedParameters { get; } public bool ReadOnly { get; } public RpcEndpoint Availability { get; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResponse.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResponse.cs index eef1194050c..5c791e392a0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResponse.cs @@ -2,20 +2,27 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Serialization.Json; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Subscribe { public class JsonRpcSubscriptionResponse : JsonRpcResponse { - [JsonProperty(PropertyName = "params", Order = 2)] - public JsonRpcSubscriptionResult Params { get; set; } - - [JsonProperty(PropertyName = "method", Order = 1)] + [JsonPropertyName("method")] + [JsonPropertyOrder(1)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public new string MethodName => "eth_subscription"; + [JsonPropertyName("params")] + [JsonPropertyOrder(2)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public JsonRpcSubscriptionResult Params { get; set; } + + [JsonPropertyName("id")] + [JsonPropertyOrder(3)] [JsonConverter(typeof(IdConverter))] - [JsonProperty(PropertyName = "id", Order = 3, NullValueHandling = NullValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public new object? Id { get { return base.Id; } set { base.Id = value; } } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResult.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResult.cs index 9db4e059590..87b90ee556d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResult.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/JsonRpcSubscriptionResult.cs @@ -1,16 +1,19 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Nethermind.Facade.Eth; namespace Nethermind.JsonRpc.Modules.Subscribe { public class JsonRpcSubscriptionResult { - [JsonProperty(PropertyName = "result", Order = 1)] - public object Result { get; set; } - - [JsonProperty(PropertyName = "subscription", Order = 0)] + [JsonPropertyName("subscription")] public string Subscription { get; set; } + + [JsonPropertyName("result")] + public object Result { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscribeRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscribeRpcModule.cs index 87c723e8c01..eea2daff894 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscribeRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscribeRpcModule.cs @@ -3,7 +3,8 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Subscribe { @@ -31,7 +32,7 @@ public ResultWrapper eth_subscribe(string subscriptionName, string? args { return ResultWrapper.Fail($"Invalid params", ErrorCodes.InvalidParams, e.Message); } - catch (JsonReaderException) + catch (JsonException) { return ResultWrapper.Fail($"Invalid params", ErrorCodes.InvalidParams); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs index 71c96e95d03..bf4ca1419a1 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactory.cs @@ -11,8 +11,10 @@ using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; +using Nethermind.Serialization.Json; using Nethermind.TxPool; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Subscribe; @@ -27,7 +29,7 @@ namespace Nethermind.JsonRpc.Modules.Subscribe; /// public class SubscriptionFactory : ISubscriptionFactory { - private readonly JsonSerializer _jsonSerializer; + private readonly IJsonSerializer _jsonSerializer; private readonly ConcurrentDictionary _subscriptionConstructors; public SubscriptionFactory(ILogManager? logManager, @@ -37,7 +39,7 @@ public SubscriptionFactory(ILogManager? logManager, IFilterStore? filterStore, IEthSyncingInfo ethSyncingInfo, ISpecProvider specProvider, - JsonSerializer jsonSerializer) + IJsonSerializer jsonSerializer) { _jsonSerializer = jsonSerializer; logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); @@ -83,7 +85,8 @@ public Subscription CreateSubscription(IJsonRpcDuplexClient jsonRpcDuplexClient, param = (IJsonRpcParam)Activator.CreateInstance(paramType); if (thereAreArgs) { - param!.ReadJson(_jsonSerializer, args); + using var doc = JsonDocument.Parse(args); + param!.ReadJson(doc.RootElement, EthereumJsonSerializer.JsonOptions); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs index b8a810483c5..80bf4bd389b 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json.Serialization; + using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Facade.Eth; @@ -38,6 +40,7 @@ public SyncingSubscription( private class SubscriptionSyncingResult { + [JsonIgnore] public bool? IsSyncing { get; set; } public long? StartingBlock { get; set; } public long? CurrentBlock { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/JsonWriterExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/JsonWriterExtensions.cs deleted file mode 100644 index 8949c47325a..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/JsonWriterExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public static class JsonWriterExtensions - { - public static void WriteProperty(this JsonWriter jsonWriter, string propertyName, T propertyValue) - { - jsonWriter.WritePropertyName(propertyName); - jsonWriter.WriteValue(propertyValue); - } - - public static void WriteProperty(this JsonWriter jsonWriter, string propertyName, T propertyValue, JsonSerializer serializer) - { - jsonWriter.WritePropertyName(propertyName); - serializer.Serialize(jsonWriter, propertyValue); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityAccountStateChangeConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityAccountStateChangeConverter.cs deleted file mode 100644 index 6800b134c0e..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityAccountStateChangeConverter.cs +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Linq; -using Nethermind.Int256; -using Nethermind.Evm.Tracing.ParityStyle; -using Nethermind.Serialization.Json; -using Newtonsoft.Json; -using JsonSerializer = Newtonsoft.Json.JsonSerializer; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public class ParityAccountStateChangeConverter : JsonConverter - { - private ByteArrayConverter _bytesConverter = new(); - private NullableUInt256Converter _intConverter = new(); - private Bytes32Converter _32BytesConverter = new(); - - private void WriteChange(JsonWriter writer, ParityStateChange change, JsonSerializer serializer) - { - if (change is null) - { - writer.WriteValue("="); - } - else - { - if (change.Before is null) - { - writer.WriteStartObject(); - writer.WritePropertyName("+"); - _bytesConverter.WriteJson(writer, change.After, serializer); - writer.WriteEndObject(); - } - else - { - writer.WriteStartObject(); - writer.WritePropertyName("*"); - writer.WriteStartObject(); - writer.WritePropertyName("from"); - _bytesConverter.WriteJson(writer, change.Before, serializer); - writer.WritePropertyName("to"); - _bytesConverter.WriteJson(writer, change.After, serializer); - writer.WriteEndObject(); - writer.WriteEndObject(); - } - } - } - - private void WriteChange(JsonWriter writer, ParityStateChange change, JsonSerializer serializer) - { - if (change is null) - { - writer.WriteValue("="); - } - else - { - if (change.Before is null) - { - writer.WriteStartObject(); - writer.WritePropertyName("+"); - _intConverter.WriteJson(writer, change.After, serializer); - writer.WriteEndObject(); - } - else - { - writer.WriteStartObject(); - writer.WritePropertyName("*"); - writer.WriteStartObject(); - writer.WritePropertyName("from"); - _intConverter.WriteJson(writer, change.Before, serializer); - writer.WritePropertyName("to"); - _intConverter.WriteJson(writer, change.After, serializer); - writer.WriteEndObject(); - writer.WriteEndObject(); - } - } - } - - private void WriteStorageChange(JsonWriter writer, ParityStateChange change, bool isNew, JsonSerializer serializer) - { - if (change is null) - { - writer.WriteValue("="); - } - else - { - if (isNew) - { - writer.WriteStartObject(); - writer.WritePropertyName("+"); - _32BytesConverter.WriteJson(writer, change.After, serializer); - writer.WriteEndObject(); - } - else - { - writer.WriteStartObject(); - writer.WritePropertyName("*"); - writer.WriteStartObject(); - writer.WritePropertyName("from"); - _32BytesConverter.WriteJson(writer, change.Before, serializer); - writer.WritePropertyName("to"); - _32BytesConverter.WriteJson(writer, change.After, serializer); - writer.WriteEndObject(); - writer.WriteEndObject(); - } - } - } - - public override void WriteJson(JsonWriter writer, ParityAccountStateChange value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WritePropertyName("balance"); - if (value.Balance is null) - { - writer.WriteValue("="); - } - else - { - WriteChange(writer, value.Balance, serializer); - } - - writer.WritePropertyName("code"); - if (value.Code is null) - { - writer.WriteValue("="); - } - else - { - WriteChange(writer, value.Code, serializer); - } - - writer.WritePropertyName("nonce"); - if (value.Nonce is null) - { - writer.WriteValue("="); - } - else - { - WriteChange(writer, value.Nonce, serializer); - } - - writer.WritePropertyName("storage"); - - writer.WriteStartObject(); - if (value.Storage is not null) - { - foreach (KeyValuePair> pair in value.Storage.OrderBy(s => s.Key)) - { - string trimmedKey = pair.Key.ToString("x64"); - trimmedKey = trimmedKey.Substring(trimmedKey.Length - 64, 64); - - writer.WritePropertyName(string.Concat("0x", trimmedKey)); - WriteStorageChange(writer, pair.Value, value.Balance?.Before is null && value.Balance?.After is not null, serializer); - } - } - - writer.WriteEndObject(); - - writer.WriteEndObject(); - } - - public override ParityAccountStateChange ReadJson(JsonReader reader, Type objectType, ParityAccountStateChange existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceActionConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceActionConverter.cs deleted file mode 100644 index 223a0bd24ac..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceActionConverter.cs +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Tracing.ParityStyle; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - /* - * { - * "callType": "call", - * "from": "0x430adc807210dab17ce7538aecd4040979a45137", - * "gas": "0x1a1f8", - * "input": "0x", - * "to": "0x9bcb0733c56b1d8f0c7c4310949e00485cae4e9d", - * "value": "0x2707377c7552d8000" - * }, - */ - public class ParityTraceActionConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, ParityTraceAction value, JsonSerializer serializer) - { - if (value.Type == "reward") - { - WriteRewardJson(writer, value, serializer); - return; - } - - if (value.Type == "suicide") - { - WriteSelfDestructJson(writer, value, serializer); - return; - } - - writer.WriteStartObject(); - if (value.CallType != "create") - { - writer.WriteProperty("callType", value.CallType); - } - else - { - writer.WriteProperty("creationMethod", value.CreationMethod); - } - - writer.WriteProperty("from", value.From, serializer); - writer.WriteProperty("gas", value.Gas, serializer); - - if (value.CallType == "create") - { - writer.WriteProperty("init", value.Input, serializer); - } - else - { - writer.WriteProperty("input", value.Input, serializer); - writer.WriteProperty("to", value.To, serializer); - } - - writer.WriteProperty("value", value.Value, serializer); - writer.WriteEndObject(); - } - - private void WriteSelfDestructJson(JsonWriter writer, ParityTraceAction value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WriteProperty("address", value.From, serializer); - writer.WriteProperty("balance", value.Value, serializer); - writer.WriteProperty("refundAddress", value.To, serializer); - writer.WriteEndObject(); - } - - private void WriteRewardJson(JsonWriter writer, ParityTraceAction value, JsonSerializer serializer) - { - writer.WriteStartObject(); - writer.WriteProperty("author", value.Author, serializer); - writer.WriteProperty("rewardType", value.RewardType, serializer); - writer.WriteProperty("value", value.Value, serializer); - writer.WriteEndObject(); - } - - public override ParityTraceAction ReadJson(JsonReader reader, Type objectType, ParityTraceAction existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceAddressConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceAddressConverter.cs deleted file mode 100644 index 56e0587fc44..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceAddressConverter.cs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using JsonSerializer = Newtonsoft.Json.JsonSerializer; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public class ParityTraceAddressConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, int[] value, JsonSerializer serializer) - { - if (value is null) - { - writer.WriteNull(); - } - else - { - writer.WriteStartArray(); - foreach (int i in value) - { - writer.WriteValue(i); - } - - writer.WriteEndArray(); - } - } - - public override int[] ReadJson(JsonReader reader, Type objectType, int[] existingValue, bool hasExistingValue, JsonSerializer serializer) - { - List result = new(); - int? pathPart; - - do - { - pathPart = reader.ReadAsInt32(); - if (pathPart.HasValue) - { - result.Add(pathPart.Value); - } - } while (pathPart is not null); - - return result.ToArray(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceResultConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceResultConverter.cs deleted file mode 100644 index 63b0c129c70..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTraceResultConverter.cs +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Tracing.ParityStyle; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public class ParityTraceResultConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, ParityTraceResult value, JsonSerializer serializer) - { - writer.WriteStartObject(); - - if (value.Address is not null) - { - writer.WriteProperty("address", value.Address, serializer); - writer.WriteProperty("code", value.Code, serializer); - } - - writer.WriteProperty("gasUsed", string.Concat("0x", value.GasUsed.ToString("x"))); - - if (value.Address is null) - { - writer.WriteProperty("output", value.Output, serializer); - } - - writer.WriteEndObject(); - } - - public override ParityTraceResult ReadJson(JsonReader reader, Type objectType, ParityTraceResult existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplay.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplay.cs index 6e546ea1bfe..65267360b6c 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplay.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplay.cs @@ -1,13 +1,19 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing.ParityStyle; namespace Nethermind.JsonRpc.Modules.Trace { + [JsonConverter(typeof(ParityTxTraceFromReplayJsonConverter))] public class ParityTxTraceFromReplay { public ParityTxTraceFromReplay() @@ -41,8 +47,137 @@ public ParityTxTraceFromReplay(IReadOnlyCollection txTraces, public ParityVmTrace? VmTrace { get; set; } + [JsonConverter(typeof(ParityTraceActionFromReplayJsonConverter))] public ParityTraceAction? Action { get; set; } public Dictionary? StateChanges { get; set; } } + + public class ParityTraceActionFromReplayJsonConverter : JsonConverter + { + public override ParityTraceAction Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) => throw new NotImplementedException(); + + public override void Write( + Utf8JsonWriter writer, + ParityTraceAction value, + JsonSerializerOptions options) + { + if (!value.IncludeInTrace) + { + return; + } + + writer.WriteStartObject(); + + writer.WritePropertyName("action"u8); + JsonSerializer.Serialize(writer, value, options); + + if (value.Error is null) + { + writer.WritePropertyName("result"u8); + JsonSerializer.Serialize(writer, value.Result, options); + } + else + { + writer.WritePropertyName("error"u8); + JsonSerializer.Serialize(writer, value.Error, options); + } + + writer.WriteNumber("subtraces"u8, value.Subtraces.Count(s => s.IncludeInTrace)); + + writer.WritePropertyName("traceAddress"u8); + if (value.TraceAddress is null) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, value.TraceAddress, options); + } + + writer.WriteString("type"u8, value.Type); + writer.WriteEndObject(); + foreach (ParityTraceAction subtrace in value.Subtraces) + { + writer.WriteStartObject(); + + writer.WritePropertyName("action"u8); + JsonSerializer.Serialize(writer, subtrace, options); + + writer.WritePropertyName("result"u8); + JsonSerializer.Serialize(writer, subtrace.Result, options); + + writer.WritePropertyName("subtraces"u8); + JsonSerializer.Serialize(writer, subtrace.Subtraces.Count, options); + + writer.WritePropertyName("traceAddress"u8); + JsonSerializer.Serialize(writer, subtrace.TraceAddress, options); + + writer.WritePropertyName("type"u8); + JsonSerializer.Serialize(writer, subtrace.Type, options); + + writer.WriteEndObject(); + } + } + } + + public class ParityTxTraceFromReplayJsonConverter : JsonConverter + { + ParityTraceActionFromReplayJsonConverter _actionConverter = new(); + public override ParityTxTraceFromReplay Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) => throw new NotImplementedException(); + + public override void Write( + Utf8JsonWriter writer, + ParityTxTraceFromReplay value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WritePropertyName("output"u8); + JsonSerializer.Serialize(writer, value.Output, options); + + writer.WritePropertyName("stateDiff"u8); + if (value.StateChanges is not null) + { + writer.WriteStartObject(); + foreach ((Address address, ParityAccountStateChange stateChange) in value.StateChanges.OrderBy(sc => sc.Key, AddressComparer.Instance)) + { + writer.WritePropertyName(address.ToString()); + JsonSerializer.Serialize(writer, stateChange, options); + } + + writer.WriteEndObject(); + } + else + { + writer.WriteNullValue(); + } + + writer.WritePropertyName("trace"u8); + + writer.WriteStartArray(); + if (value.Action is not null) + { + _actionConverter.Write(writer, value.Action, options); + } + writer.WriteEndArray(); + + if (value.TransactionHash is not null) + { + writer.WritePropertyName("transactionHash"u8); + JsonSerializer.Serialize(writer, value.TransactionHash, options); + } + + writer.WritePropertyName("vmTrace"u8); + JsonSerializer.Serialize(writer, value.VmTrace, options); + + writer.WriteEndObject(); + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplayConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplayConverter.cs deleted file mode 100644 index 0bda0fab6a9..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromReplayConverter.cs +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Linq; -using Nethermind.Core; -using Nethermind.Evm.Tracing.ParityStyle; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public class ParityTxTraceFromReplayConverter : JsonConverter - { - private ParityTraceAddressConverter _traceAddressConverter = new(); - - public override void WriteJson(JsonWriter writer, ParityTxTraceFromReplay value, JsonSerializer serializer) - { - writer.WriteStartObject(); - - writer.WriteProperty("output", value.Output, serializer); - writer.WritePropertyName("stateDiff"); - - if (value.StateChanges is not null) - { - writer.WriteStartObject(); - foreach ((Address address, ParityAccountStateChange stateChange) in value.StateChanges.OrderBy(sc => sc.Key, AddressComparer.Instance)) writer.WriteProperty(address.ToString(), stateChange, serializer); - - writer.WriteEndObject(); - } - else - { - writer.WriteNull(); - } - - writer.WritePropertyName("trace"); - - writer.WriteStartArray(); - if (value.Action is not null) - { - WriteJson(writer, value.Action, serializer); - } - writer.WriteEndArray(); - - if (value.TransactionHash is not null) - { - writer.WriteProperty("transactionHash", value.TransactionHash, serializer); - } - - writer.WriteProperty("vmTrace", value.VmTrace, serializer); - - writer.WriteEndObject(); - } - - // { - // "output": "0x", - // "stateDiff": null, - // "trace": [{ - // "action": { ... }, - // "result": { - // "gasUsed": "0x0", - // "output": "0x" - // }, - // "subtraces": 0, - // "traceAddress": [], - // "type": "call" - // }], - // "vmTrace": null - // } - private void WriteJson(JsonWriter writer, ParityTraceAction traceAction, JsonSerializer serializer) - { - if (!traceAction.IncludeInTrace) - { - return; - } - - writer.WriteStartObject(); - writer.WriteProperty("action", traceAction, serializer); - if (traceAction.Error is null) - { - writer.WriteProperty("result", traceAction.Result, serializer); - } - else - { - writer.WriteProperty("error", traceAction.Error, serializer); - } - - writer.WriteProperty("subtraces", traceAction.Subtraces.Count(s => s.IncludeInTrace)); - writer.WritePropertyName("traceAddress"); - _traceAddressConverter.WriteJson(writer, traceAction.TraceAddress, serializer); - - writer.WriteProperty("type", traceAction.Type); - writer.WriteEndObject(); - foreach (ParityTraceAction subtrace in traceAction.Subtraces) WriteJson(writer, subtrace, serializer); - } - - public override ParityTxTraceFromReplay ReadJson(JsonReader reader, Type objectType, ParityTxTraceFromReplay existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromStore.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromStore.cs index 14002950733..febabd3337d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromStore.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityTxTraceFromStore.cs @@ -2,10 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Text.Json.Serialization; + using Nethermind.Core.Crypto; using Nethermind.Evm.Tracing.ParityStyle; using Nethermind.Serialization.Json; -using Newtonsoft.Json; namespace Nethermind.JsonRpc.Modules.Trace { @@ -65,7 +66,7 @@ private ParityTxTraceFromStore() public Hash256 BlockHash { get; set; } - [JsonConverter(typeof(LongConverter), NumberConversion.Raw)] + [JsonConverter(typeof(LongRawJsonConverter))] public long BlockNumber { get; set; } public ParityTraceResult Result { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmOperationTraceConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmOperationTraceConverter.cs deleted file mode 100644 index 4d403d24ce5..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmOperationTraceConverter.cs +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core.Extensions; -using Nethermind.Evm.Tracing.ParityStyle; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public class ParityVmOperationTraceConverter : JsonConverter - { - //{ - // "cost": 0.0, - // "ex": { - // "mem": null, - // "push": [], - // "store": null, - // "used": 16961.0 - // }, - // "pc": 526.0, - // "sub": null - // } - public override void WriteJson(JsonWriter writer, ParityVmOperationTrace value, JsonSerializer serializer) - { - writer.WriteStartObject(); - - writer.WriteProperty("cost", value.Cost); - writer.WritePropertyName("ex"); - writer.WriteStartObject(); - writer.WritePropertyName("mem"); - if (value.Memory is not null) - { - writer.WriteStartObject(); - writer.WritePropertyName("data"); - writer.WriteValue(value.Memory.Data.ToHexString(true, false)); - writer.WritePropertyName("off"); - writer.WriteValue(value.Memory.Offset); - writer.WriteEndObject(); - } - else - { - writer.WriteNull(); - } - - writer.WritePropertyName("push"); - if (value.Push is not null) - { - writer.WriteStartArray(); - for (int i = 0; i < value.Push.Length; i++) - { - writer.WriteValue(value.Push[i].ToHexString(true, true)); - } - - writer.WriteEndArray(); - } - else - { - writer.WriteNull(); - } - - writer.WritePropertyName("store"); - if (value.Store is not null) - { - writer.WriteStartObject(); - writer.WritePropertyName("key"); - writer.WriteValue(value.Store.Key.ToHexString(true, true)); - writer.WritePropertyName("val"); - writer.WriteValue(value.Store.Value.ToHexString(true, true)); - writer.WriteEndObject(); - } - else - { - writer.WriteNull(); - } - - writer.WriteProperty("used", value.Used); - writer.WriteEndObject(); - - writer.WriteProperty("pc", value.Pc, serializer); - writer.WriteProperty("sub", value.Sub, serializer); - writer.WriteEndObject(); - } - - public override ParityVmOperationTrace ReadJson(JsonReader reader, Type objectType, ParityVmOperationTrace existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmTraceConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmTraceConverter.cs deleted file mode 100644 index a0a2f3296c6..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/ParityVmTraceConverter.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Tracing.ParityStyle; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public class ParityVmTraceConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, ParityVmTrace value, JsonSerializer serializer) - { - writer.WriteStartObject(); - - writer.WriteProperty("code", value.Code ?? Array.Empty(), serializer); - writer.WriteProperty("ops", value.Operations, serializer); - - writer.WriteEndObject(); - } - - public override ParityVmTrace ReadJson(JsonReader reader, Type objectType, ParityVmTrace existingValue, bool hasExistingValue, JsonSerializer serializer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceFilterForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceFilterForRpc.cs index 6b2d397c2cc..1393254c6f0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceFilterForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceFilterForRpc.cs @@ -3,7 +3,10 @@ using Nethermind.Blockchain.Find; using Nethermind.Core; -using Newtonsoft.Json; +using Nethermind.Evm.Tracing; + +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.JsonRpc.Modules.Trace { @@ -19,7 +22,7 @@ public class TraceFilterForRpc public int After { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public int? Count { get; set; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs index 814bcca829c..3235754eba1 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceModuleFactory.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; + using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus; @@ -15,7 +16,6 @@ using Nethermind.Logging; using Nethermind.State; using Nethermind.Trie.Pruning; -using Newtonsoft.Json; namespace Nethermind.JsonRpc.Modules.Trace { @@ -84,18 +84,5 @@ public override ITraceRpcModule Create() return new TraceRpcModule(_receiptStorage, tracer, _blockTree, _jsonRpcConfig, _specProvider, _logManager, txProcessingEnv.StateReader); } - - public static JsonConverter[] Converters = - { - new ParityTxTraceFromReplayConverter(), - new ParityAccountStateChangeConverter(), - new ParityTraceActionConverter(), - new ParityTraceResultConverter(), - new ParityVmOperationTraceConverter(), - new ParityVmTraceConverter(), - new TransactionForRpcWithTraceTypesConverter() - }; - - public override IReadOnlyCollection GetConverters() => Converters; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TransactionForRpcWithTraceTypesConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TransactionForRpcWithTraceTypesConverter.cs deleted file mode 100644 index 6adfb0b7e4c..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TransactionForRpcWithTraceTypesConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.JsonRpc.Data; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Nethermind.JsonRpc.Modules.Trace -{ - public class TransactionForRpcWithTraceTypesConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, TransactionForRpcWithTraceTypes value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - - public override TransactionForRpcWithTraceTypes ReadJson(JsonReader reader, Type objectType, - TransactionForRpcWithTraceTypes existingValue, bool hasExistingValue, JsonSerializer serializer) - { - existingValue ??= new(); - JArray jArray = JArray.Load(reader); - existingValue.Transaction = serializer.Deserialize(jArray[0].CreateReader()) ?? throw new InvalidOperationException(); - existingValue.TraceTypes = serializer.Deserialize(jArray[1].CreateReader()) ?? throw new InvalidOperationException(); - - return existingValue; - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj b/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj index 0d8632011df..d3e2a06964d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj +++ b/src/Nethermind/Nethermind.JsonRpc/Nethermind.JsonRpc.csproj @@ -1,10 +1,11 @@ annotations + true + True - diff --git a/src/Nethermind/Nethermind.JsonRpc/StringExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/StringExtensions.cs deleted file mode 100644 index bbcaf351ed5..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/StringExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.IO; -using Newtonsoft.Json; - -namespace Nethermind.JsonRpc; - -public static class StringExtensions -{ - public static JsonTextReader ToJsonTextReader(this string json) => new(new StringReader(json)); -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Utils/JTokenUtils.cs b/src/Nethermind/Nethermind.JsonRpc/Utils/JTokenUtils.cs deleted file mode 100644 index 78d0d44b8ec..00000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Utils/JTokenUtils.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.IO; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Nethermind.JsonRpc.Utils -{ - public static class JTokenUtils - { - public static IEnumerable ParseMulticontent(TextReader jsonReader) - { - using JsonReader reader = new JsonTextReader(jsonReader) { SupportMultipleContent = true }; - while (reader.Read()) - { - yield return JToken.Load(reader); - } - } - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs b/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs index c6c5d5b1827..64010fbefce 100644 --- a/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs +++ b/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs @@ -2,195 +2,180 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Pipelines; using System.Text; using System.Threading; using System.Threading.Tasks; + using Nethermind.Core.Extensions; using Nethermind.JsonRpc.Modules; using Nethermind.Serialization.Json; using Nethermind.Sockets; -namespace Nethermind.JsonRpc.WebSockets +namespace Nethermind.JsonRpc.WebSockets; + +public class JsonRpcSocketsClient : SocketClient, IJsonRpcDuplexClient { - public class JsonRpcSocketsClient : SocketClient, IJsonRpcDuplexClient + public event EventHandler? Closed; + + private readonly IJsonRpcProcessor _jsonRpcProcessor; + private readonly IJsonRpcService _jsonRpcService; + private readonly IJsonRpcLocalStats _jsonRpcLocalStats; + private readonly long? _maxBatchResponseBodySize; + private readonly JsonRpcContext _jsonRpcContext; + + private readonly SemaphoreSlim _sendSemaphore = new(1, 1); + + public JsonRpcSocketsClient( + string clientName, + ISocketHandler handler, + RpcEndpoint endpointType, + IJsonRpcProcessor jsonRpcProcessor, + IJsonRpcService jsonRpcService, + IJsonRpcLocalStats jsonRpcLocalStats, + IJsonSerializer jsonSerializer, + JsonRpcUrl? url = null, + long? maxBatchResponseBodySize = null) + : base(clientName, handler, jsonSerializer) { - public event EventHandler? Closed; - - private readonly IJsonRpcProcessor _jsonRpcProcessor; - private readonly IJsonRpcService _jsonRpcService; - private readonly IJsonRpcLocalStats _jsonRpcLocalStats; - private readonly long? _maxBatchResponseBodySize; - private readonly JsonRpcContext _jsonRpcContext; - - private readonly SemaphoreSlim _sendSemaphore = new(1, 1); - - public JsonRpcSocketsClient( - string clientName, - ISocketHandler handler, - RpcEndpoint endpointType, - IJsonRpcProcessor jsonRpcProcessor, - IJsonRpcService jsonRpcService, - IJsonRpcLocalStats jsonRpcLocalStats, - IJsonSerializer jsonSerializer, - JsonRpcUrl? url = null, - long? maxBatchResponseBodySize = null) - : base(clientName, handler, jsonSerializer) - { - _jsonRpcProcessor = jsonRpcProcessor; - _jsonRpcService = jsonRpcService; - _jsonRpcLocalStats = jsonRpcLocalStats; - _maxBatchResponseBodySize = maxBatchResponseBodySize; - _jsonRpcContext = new JsonRpcContext(endpointType, this, url); - } + _jsonRpcProcessor = jsonRpcProcessor; + _jsonRpcService = jsonRpcService; + _jsonRpcLocalStats = jsonRpcLocalStats; + _maxBatchResponseBodySize = maxBatchResponseBodySize; + _jsonRpcContext = new JsonRpcContext(endpointType, this, url); + } - public override void Dispose() - { - base.Dispose(); - _sendSemaphore.Dispose(); - Closed?.Invoke(this, EventArgs.Empty); - } + public override void Dispose() + { + base.Dispose(); + _sendSemaphore.Dispose(); + Closed?.Invoke(this, EventArgs.Empty); + } - private static readonly byte[] _jsonOpeningBracket = { Convert.ToByte('[') }; - private static readonly byte[] _jsonComma = { Convert.ToByte(',') }; - private static readonly byte[] _jsonClosingBracket = { Convert.ToByte(']') }; + private static readonly byte[] _jsonOpeningBracket = { Convert.ToByte('[') }; + private static readonly byte[] _jsonComma = { Convert.ToByte(',') }; + private static readonly byte[] _jsonClosingBracket = { Convert.ToByte(']') }; - public override async Task ProcessAsync(ArraySegment data) - { - Stopwatch stopwatch = Stopwatch.StartNew(); - IncrementBytesReceivedMetric(data.Count); - using TextReader request = new StreamReader(new MemoryStream(data.Array!, data.Offset, data.Count), Encoding.UTF8); - int allResponsesSize = 0; + public override async Task ProcessAsync(ArraySegment data) + { + Stopwatch stopwatch = Stopwatch.StartNew(); + IncrementBytesReceivedMetric(data.Count); + PipeReader request = PipeReader.Create(new MemoryStream(data.Array!, data.Offset, data.Count)); + int allResponsesSize = 0; - await foreach (JsonRpcResult result in _jsonRpcProcessor.ProcessAsync(request, _jsonRpcContext)) - { - stopwatch.Restart(); + await foreach (JsonRpcResult result in _jsonRpcProcessor.ProcessAsync(request, _jsonRpcContext)) + { + stopwatch.Restart(); - int singleResponseSize = await SendJsonRpcResult(result); - allResponsesSize += singleResponseSize; + int singleResponseSize = await SendJsonRpcResult(result); + allResponsesSize += singleResponseSize; - if (result.IsCollection) - { - long handlingTimeMicroseconds = stopwatch.ElapsedMicroseconds(); - _ = _jsonRpcLocalStats.ReportCall(new RpcReport("# collection serialization #", handlingTimeMicroseconds, true), handlingTimeMicroseconds, singleResponseSize); - } - else - { - long handlingTimeMicroseconds = stopwatch.ElapsedMicroseconds(); - _ = _jsonRpcLocalStats.ReportCall(result.Report!.Value, handlingTimeMicroseconds, singleResponseSize); - } - stopwatch.Restart(); + if (result.IsCollection) + { + long handlingTimeMicroseconds = stopwatch.ElapsedMicroseconds(); + _ = _jsonRpcLocalStats.ReportCall(new RpcReport("# collection serialization #", handlingTimeMicroseconds, true), handlingTimeMicroseconds, singleResponseSize); } - - IncrementBytesSentMetric(allResponsesSize); + else + { + long handlingTimeMicroseconds = stopwatch.ElapsedMicroseconds(); + _ = _jsonRpcLocalStats.ReportCall(result.Report!.Value, handlingTimeMicroseconds, singleResponseSize); + } + stopwatch.Restart(); } - private void IncrementBytesReceivedMetric(int size) + IncrementBytesSentMetric(allResponsesSize); + } + + private void IncrementBytesReceivedMetric(int size) + { + if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.Ws) { - if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.Ws) - { - Interlocked.Add(ref Metrics.JsonRpcBytesReceivedWebSockets, size); - } + Interlocked.Add(ref Metrics.JsonRpcBytesReceivedWebSockets, size); + } - if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.IPC) - { - Interlocked.Add(ref Metrics.JsonRpcBytesReceivedIpc, size); - } + if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.IPC) + { + Interlocked.Add(ref Metrics.JsonRpcBytesReceivedIpc, size); } + } - private void IncrementBytesSentMetric(int size) + private void IncrementBytesSentMetric(int size) + { + if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.Ws) { - if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.Ws) - { - Interlocked.Add(ref Metrics.JsonRpcBytesSentWebSockets, size); - } + Interlocked.Add(ref Metrics.JsonRpcBytesSentWebSockets, size); + } - if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.IPC) - { - Interlocked.Add(ref Metrics.JsonRpcBytesSentIpc, size); - } + if (_jsonRpcContext.RpcEndpoint == RpcEndpoint.IPC) + { + Interlocked.Add(ref Metrics.JsonRpcBytesSentIpc, size); } + } - public virtual async Task SendJsonRpcResult(JsonRpcResult result) + public virtual async Task SendJsonRpcResult(JsonRpcResult result) + { + await _sendSemaphore.WaitAsync(); + try { - await _sendSemaphore.WaitAsync(); - try + if (result.IsCollection) { - await using Stream stream = _handler.SendUsingStream(); - await using Stream buffered = new BufferedStream(stream); - await using CounterStream resultData = new CounterStream(buffered); - - if (result.IsCollection) + int singleResponseSize = 1; + bool isFirst = true; + await _handler.SendRawAsync(_jsonOpeningBracket, false); + JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses!.GetAsyncEnumerator(CancellationToken.None); + try { - bool isFirst = true; - await resultData.WriteAsync(_jsonOpeningBracket); - JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses!.GetAsyncEnumerator(CancellationToken.None); - try + while (await enumerator.MoveNextAsync()) { - while (await enumerator.MoveNextAsync()) + JsonRpcResult.Entry entry = enumerator.Current; + using (entry) { - JsonRpcResult.Entry entry = enumerator.Current; - using (entry) + if (!isFirst) + { + await _handler.SendRawAsync(_jsonComma, false); + singleResponseSize += 1; + } + isFirst = false; + singleResponseSize += await SendJsonRpcResultEntry(entry, false); + _ = _jsonRpcLocalStats.ReportCall(entry.Report); + + // We reached the limit and don't want to responded to more request in the batch + if (!_jsonRpcContext.IsAuthenticated && singleResponseSize > _maxBatchResponseBodySize) { - if (!isFirst) - { - await resultData.WriteAsync(_jsonComma); - } - isFirst = false; - SendJsonRpcResultEntry(resultData, entry); - _ = _jsonRpcLocalStats.ReportCall(entry.Report); - - // We reached the limit and don't want to responded to more request in the batch - if (!_jsonRpcContext.IsAuthenticated && resultData.WrittenBytes > _maxBatchResponseBodySize) - { - enumerator.IsStopped = true; - } + enumerator.IsStopped = true; } } } - finally - { - await enumerator.DisposeAsync(); - } - - await resultData.WriteAsync(_jsonClosingBracket); } - else + finally { - SendJsonRpcResultEntry(resultData, result.SingleResponse!.Value); + await enumerator.DisposeAsync(); } - // ? What if we write more than int.MaxValue. - // Result could be negative - return (int)resultData.WrittenBytes; + await _handler.SendRawAsync(_jsonClosingBracket, true); + singleResponseSize += 1; + + return singleResponseSize; } - finally + else { - _sendSemaphore.Release(); + return await SendJsonRpcResultEntry(result.SingleResponse!.Value); } } - - private void SendJsonRpcResultEntry(Stream dest, JsonRpcResult.Entry result) + finally { - using JsonRpcResult.Entry entry = result; + _sendSemaphore.Release(); + } + } - try - { - _jsonSerializer.SerializeWaitForEnumeration(dest, result.Response); - } - catch (Exception e) when (e is OperationCanceledException || e.InnerException is OperationCanceledException) - { - _jsonSerializer.Serialize( - dest, - _jsonRpcService.GetErrorResponse( - ErrorCodes.Timeout, - "Request was canceled due to enabled timeout.", - result.Response.Id, - result.Response.MethodName - ) - ); - } + private async Task SendJsonRpcResultEntry(JsonRpcResult.Entry result, bool endOfMessage = true) + { + using (result) + { + return (int)await _jsonSerializer.SerializeAsync(_handler.SendUsingStream(), result.Response, indented: false, leaveOpen: !endOfMessage); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcWebSocketsModule.cs b/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcWebSocketsModule.cs index 2c56b1696f6..78fd6d933f2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcWebSocketsModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcWebSocketsModule.cs @@ -57,7 +57,7 @@ public ISocketsClient CreateClient(WebSocket webSocket, string clientName, HttpC throw new InvalidOperationException($"WebSocket-enabled url not defined for port {port}"); } - if (jsonRpcUrl.IsAuthenticated && !_rpcAuthentication.Authenticate(context.Request.Headers["Authorization"])) + if (jsonRpcUrl.IsAuthenticated && !_rpcAuthentication.Authenticate(context.Request.Headers.Authorization)) { throw new InvalidOperationException($"WebSocket connection on port {port} should be authenticated"); } diff --git a/src/Nethermind/Nethermind.KeyStore/CipherParams.cs b/src/Nethermind/Nethermind.KeyStore/CipherParams.cs index 15bdce259ad..0a9c7a2878c 100644 --- a/src/Nethermind/Nethermind.KeyStore/CipherParams.cs +++ b/src/Nethermind/Nethermind.KeyStore/CipherParams.cs @@ -1,13 +1,14 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.KeyStore { public class CipherParams { - [JsonProperty(PropertyName = "iv")] + [JsonPropertyName("iv")] public string IV { get; set; } } } diff --git a/src/Nethermind/Nethermind.KeyStore/Crypto.cs b/src/Nethermind/Nethermind.KeyStore/Crypto.cs index 8a8fb39a157..c1fff95787c 100644 --- a/src/Nethermind/Nethermind.KeyStore/Crypto.cs +++ b/src/Nethermind/Nethermind.KeyStore/Crypto.cs @@ -1,28 +1,28 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Nethermind.KeyStore { public class Crypto { - [JsonProperty(PropertyName = "ciphertext", Order = 0)] + [JsonPropertyName("ciphertext")] public string CipherText { get; set; } - [JsonProperty(PropertyName = "cipherparams", Order = 1)] + [JsonPropertyName("cipherparams")] public CipherParams CipherParams { get; set; } - [JsonProperty(PropertyName = "cipher", Order = 2)] + [JsonPropertyName("cipher")] public string Cipher { get; set; } - [JsonProperty(PropertyName = "kdf", Order = 3)] + [JsonPropertyName("kdf")] public string KDF { get; set; } - [JsonProperty(PropertyName = "kdfparams", Order = 4)] + [JsonPropertyName("kdfparams")] public KdfParams KDFParams { get; set; } - [JsonProperty(PropertyName = "mac", Order = 5)] + [JsonPropertyName("mac")] public string MAC { get; set; } } } diff --git a/src/Nethermind/Nethermind.KeyStore/KdfParams.cs b/src/Nethermind/Nethermind.KeyStore/KdfParams.cs index bbafdfd7bd9..d413766ed66 100644 --- a/src/Nethermind/Nethermind.KeyStore/KdfParams.cs +++ b/src/Nethermind/Nethermind.KeyStore/KdfParams.cs @@ -1,31 +1,32 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.KeyStore { public class KdfParams { - [JsonProperty(PropertyName = "dklen", Order = 0)] + [JsonPropertyName("dklen")] public int DkLen { get; set; } - [JsonProperty(PropertyName = "salt", Order = 1)] + [JsonPropertyName("salt")] public string Salt { get; set; } - [JsonProperty(PropertyName = "n", Order = 2)] + [JsonPropertyName("n")] public int? N { get; set; } - [JsonProperty(PropertyName = "p", Order = 4)] - public int? P { get; set; } - - [JsonProperty(PropertyName = "r", Order = 3)] + [JsonPropertyName("r")] public int? R { get; set; } - [JsonProperty(PropertyName = "c")] + [JsonPropertyName("p")] + public int? P { get; set; } + + [JsonPropertyName("c")] public int? C { get; set; } - [JsonProperty(PropertyName = "prf")] + [JsonPropertyName("prf")] public string Prf { get; set; } } } diff --git a/src/Nethermind/Nethermind.KeyStore/KeyStoreItem.cs b/src/Nethermind/Nethermind.KeyStore/KeyStoreItem.cs index c22fecb94c7..ac133cb57cd 100644 --- a/src/Nethermind/Nethermind.KeyStore/KeyStoreItem.cs +++ b/src/Nethermind/Nethermind.KeyStore/KeyStoreItem.cs @@ -1,22 +1,23 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.KeyStore { public class KeyStoreItem { - [JsonProperty(PropertyName = "version", Order = 0)] + [JsonPropertyName("version")] public int Version { get; set; } - [JsonProperty(PropertyName = "id", Order = 1)] + [JsonPropertyName("id")] public string Id { get; set; } - [JsonProperty(PropertyName = "address", Order = 2)] + [JsonPropertyName("address")] public string Address { get; set; } - [JsonProperty(PropertyName = "crypto", Order = 3)] + [JsonPropertyName("crypto")] public Crypto Crypto { get; set; } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 2a58c78ffb6..6bf71014e01 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -35,7 +36,7 @@ using Nethermind.Specs.Forks; using Nethermind.State; using Nethermind.Trie; -using Newtonsoft.Json; + using NSubstitute; using NUnit.Framework; @@ -72,8 +73,8 @@ public virtual async Task processing_block_should_serialize_valid_responses(stri }; string?[] parameters = { - JsonConvert.SerializeObject(forkChoiceUpdatedParams), - JsonConvert.SerializeObject(preparePayloadParams) + JsonSerializer.Serialize(forkChoiceUpdatedParams), + JsonSerializer.Serialize(preparePayloadParams) }; // prepare a payload string result = await RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters!); @@ -111,7 +112,7 @@ public virtual async Task processing_block_should_serialize_valid_responses(stri safeBlockHash = expectedBlockHash.ToString(true), finalizedBlockHash = startingHead.ToString(true), }; - parameters = new[] { JsonConvert.SerializeObject(forkChoiceUpdatedParams), null }; + parameters = new[] { JsonSerializer.Serialize(forkChoiceUpdatedParams), null }; // update the fork choice result = await RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters!); result.Should().Be("{\"jsonrpc\":\"2.0\",\"result\":{\"payloadStatus\":{\"status\":\"VALID\",\"latestValidHash\":\"" + @@ -130,7 +131,7 @@ public async Task can_parse_forkchoiceUpdated_with_implicit_null_payloadAttribut safeBlockHash = Keccak.Zero.ToString(), finalizedBlockHash = Keccak.Zero.ToString(), }; - string[] parameters = new[] { JsonConvert.SerializeObject(forkChoiceUpdatedParams) }; + string[] parameters = new[] { JsonSerializer.Serialize(forkChoiceUpdatedParams) }; string? result = await RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters); result.Should().Be("{\"jsonrpc\":\"2.0\",\"result\":{\"payloadStatus\":{\"status\":\"SYNCING\",\"latestValidHash\":null,\"validationError\":null},\"payloadId\":null},\"id\":67}"); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 44eb80d7b03..e636cca9525 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -28,7 +30,6 @@ using Nethermind.Serialization.Rlp; using Nethermind.Specs; using Nethermind.Specs.Forks; -using Newtonsoft.Json.Linq; using NSubstitute; using NUnit.Framework; @@ -221,22 +222,22 @@ public async Task NewPayloadV3_should_decline_empty_fields() string executionPayloadString = serializer.Serialize(executionPayload); string blobsString = serializer.Serialize(Array.Empty()); - string parentBeaconBlockRootString = serializer.Serialize(TestItem.KeccakA.BytesToArray()); + string parentBeaconBlockRootString = TestItem.KeccakA.ToString(); { - JObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); + JsonObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); JsonRpcRequest request = RpcTest.GetJsonRequest(nameof(IEngineRpcModule.engine_newPayloadV3), serializer.Serialize(executionPayloadAsJObject), blobsString, parentBeaconBlockRootString); JsonRpcResponse response = await jsonRpcService.SendRequestAsync(request, context); Assert.That(response is JsonRpcSuccessResponse); } - string[] props = serializer.Deserialize(serializer.Serialize(new ExecutionPayload())) - .Properties().Select(prop => prop.Name).ToArray(); + string[] props = serializer.Deserialize(serializer.Serialize(new ExecutionPayload())) + .Select(prop => prop.Key).ToArray(); foreach (string prop in props) { - JObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); + JsonObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); executionPayloadAsJObject[prop] = null; JsonRpcRequest request = RpcTest.GetJsonRequest(nameof(IEngineRpcModule.engine_newPayloadV3), @@ -248,7 +249,7 @@ public async Task NewPayloadV3_should_decline_empty_fields() foreach (string prop in props) { - JObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); + JsonObject executionPayloadAsJObject = serializer.Deserialize(executionPayloadString); executionPayloadAsJObject.Remove(prop); JsonRpcRequest request = RpcTest.GetJsonRequest(nameof(IEngineRpcModule.engine_newPayloadV3), diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs index 48e43ab455a..667eee43b37 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs @@ -205,7 +205,7 @@ private void CleanupOldPayloads(object? sender, EventArgs e) { using (blockContext) { - bool currentBestBlockIsEmpty = blockContext.CurrentBestBlock?.Transactions.Any() != true; + bool currentBestBlockIsEmpty = blockContext.CurrentBestBlock?.Transactions.Length == 0; if (currentBestBlockIsEmpty && !blockContext.ImprovementTask.IsCompleted) { await Task.WhenAny(blockContext.ImprovementTask, Task.Delay(GetPayloadWaitForFullBlockMillisecondsDelay)); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index 5f2404ee796..20ee36d36f7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -3,15 +3,18 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Int256; using Nethermind.Merge.Plugin.Handlers; +using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; using Nethermind.State.Proofs; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Merge.Plugin.Data; @@ -96,12 +99,14 @@ public byte[][] Transactions /// Gets or sets as defined in /// EIP-4844. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ulong? BlobGasUsed { get; set; } /// /// Gets or sets as defined in /// EIP-4844. /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ulong? ExcessBlobGas { get; set; } /// @@ -158,7 +163,6 @@ public virtual bool TryGetBlock(out Block? block, UInt256? totalDifficulty = nul } } - private Transaction[]? _transactions = null; /// diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs index 910d7d968a8..2b20e438649 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using Nethermind.Core; using Nethermind.Serialization.Rlp; -using Newtonsoft.Json; + +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Merge.Plugin.Data; @@ -26,8 +28,8 @@ public ExecutionPayloadBodyV1Result(IList transactions, IList> Transactions { get; set; } + public IList Transactions { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public IList? Withdrawals { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV3.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV3.cs index d365264b554..dd78f8ea35a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV3.cs @@ -4,14 +4,12 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Int256; -using Newtonsoft.Json; namespace Nethermind.Merge.Plugin.Data; /// /// Represents an object mapping the ExecutionPayloadV3 structure of the beacon chain spec. /// -[JsonObject(ItemRequired = Required.Always)] public class ExecutionPayloadV3 : ExecutionPayload { public ExecutionPayloadV3() { } // Needed for tests diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs index 1ddeef910cc..dbcdee9f654 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Threading.Tasks; +using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core.Crypto; using Nethermind.JsonRpc; -using Newtonsoft.Json; namespace Nethermind.Merge.Plugin.Data { @@ -51,7 +52,7 @@ public static ResultWrapper Invalid(Hash256? latestVa /// /// Identifier of the payload build process or null if there is none. /// - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public string? PayloadId { get; set; } public static implicit operator Task(ForkchoiceUpdatedV1Result result) => Task.FromResult(result); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs index d7bfca58d2a..6884572133b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Crypto; -using Newtonsoft.Json; + +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Merge.Plugin.Data { @@ -32,13 +34,13 @@ public class PayloadStatusV1 /// /// Hash of the most recent valid block in the branch defined by payload and its ancestors. /// - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Hash256? LatestValidHash { get; set; } /// /// Message providing additional details on the validation error if the payload is classified as . /// - [JsonProperty(NullValueHandling = NullValueHandling.Include)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public string? ValidationError { get; set; } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs index a22f79beb5e..501959a372f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs @@ -50,7 +50,7 @@ private void CheckCapabilities(IEnumerable methods, IReadOnlyDictionary< missing.Add(capability.Key); } - if (missing.Any()) + if (missing.Count > 0) { if (_logger.IsWarn) _logger.Warn($"Consensus client missing capabilities: {string.Join(", ", missing)}"); } diff --git a/src/Nethermind/Nethermind.Mev.Test/Nethermind.Mev.Test.csproj b/src/Nethermind/Nethermind.Mev.Test/Nethermind.Mev.Test.csproj index 8dd4ad1d31d..4b0058ee5c6 100644 --- a/src/Nethermind/Nethermind.Mev.Test/Nethermind.Mev.Test.csproj +++ b/src/Nethermind/Nethermind.Mev.Test/Nethermind.Mev.Test.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs b/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs index 5da0f28f417..b7c218cd438 100644 --- a/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs +++ b/src/Nethermind/Nethermind.Network/StaticNodes/StaticNodesManager.cs @@ -7,12 +7,14 @@ using System.IO; using System.Linq; using System.Net.Sockets; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; using Nethermind.Config; using Nethermind.Core.Crypto; using Nethermind.Logging; +using Nethermind.Serialization.Json; using Nethermind.Stats.Model; -using Newtonsoft.Json; namespace Nethermind.Network.StaticNodes { @@ -71,7 +73,7 @@ private static string[] GetNodes(string data) string[] nodes; try { - nodes = JsonConvert.DeserializeObject(data) ?? Array.Empty(); + nodes = JsonSerializer.Deserialize(data) ?? Array.Empty(); } catch (JsonException) { @@ -130,7 +132,7 @@ public bool IsStatic(string enode) private Task SaveFileAsync() => File.WriteAllTextAsync(_staticNodesPath, - JsonConvert.SerializeObject(_nodes.Select(n => n.Value.ToString()), Formatting.Indented)); + JsonSerializer.Serialize(_nodes.Select(n => n.Value.ToString()), EthereumJsonSerializer.JsonOptionsIndented)); public List LoadInitialList() { diff --git a/src/Nethermind/Nethermind.Overseer.Test/AuRaTest.cs b/src/Nethermind/Nethermind.Overseer.Test/AuRaTest.cs index 2687a001c84..d33d1e2edf8 100644 --- a/src/Nethermind/Nethermind.Overseer.Test/AuRaTest.cs +++ b/src/Nethermind/Nethermind.Overseer.Test/AuRaTest.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using FluentAssertions; using Nethermind.Overseer.Test.Framework; + using NUnit.Framework; namespace Nethermind.Overseer.Test diff --git a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestContextBase.cs b/src/Nethermind/Nethermind.Overseer.Test/Framework/TestContextBase.cs index db43b0ec92c..4b53488b65e 100644 --- a/src/Nethermind/Nethermind.Overseer.Test/Framework/TestContextBase.cs +++ b/src/Nethermind/Nethermind.Overseer.Test/Framework/TestContextBase.cs @@ -2,10 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; using System.Threading.Tasks; using Nethermind.Overseer.Test.Framework.Steps; using Nethermind.Overseer.Test.JsonRpc; -using Newtonsoft.Json; + using NUnit.Framework; namespace Nethermind.Overseer.Test.Framework @@ -73,7 +74,7 @@ private async Task> ExecuteJsonRpcAsync( var result = await funcTask; TestContext.WriteLine($"Received a response for JSON RPC call '{methodName}'." + - $"{Environment.NewLine}{JsonConvert.SerializeObject(result)}"); + $"{Environment.NewLine}{JsonSerializer.Serialize(result)}"); return await funcTask; } diff --git a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs index c10a725622c..651a582d7ad 100644 --- a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs @@ -25,6 +25,8 @@ using Nethermind.Runner.Ethereum.Api; using Nethermind.TxPool; using NUnit.Framework; +using LogLevel = NLog.LogLevel; +using Nethermind.Serialization.Json; namespace Nethermind.Runner.Test { diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Steps/StartRpc.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Steps/StartRpc.cs index b2b8bf24eee..7542ee36dfd 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Steps/StartRpc.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Steps/StartRpc.cs @@ -40,7 +40,7 @@ public async Task Execute(CancellationToken cancellationToken) IRpcModuleProvider rpcModuleProvider = _api.RpcModuleProvider!; JsonRpcService jsonRpcService = new(rpcModuleProvider, _api.LogManager, jsonRpcConfig); - IJsonSerializer jsonSerializer = CreateJsonSerializer(jsonRpcService); + IJsonSerializer jsonSerializer = new EthereumJsonSerializer(); IRpcAuthentication auth = jsonRpcConfig.UnsecureDevNoRpcAuthentication || !jsonRpcUrlCollection.Values.Any(u => u.IsAuthenticated) ? NoAuthentication.Instance : JwtAuthentication.FromFile(jsonRpcConfig.JwtSecretFile, _api.Timestamper, logger); @@ -48,7 +48,6 @@ public async Task Execute(CancellationToken cancellationToken) JsonRpcProcessor jsonRpcProcessor = new( jsonRpcService, - jsonSerializer, jsonRpcConfig, _api.FileSystem, _api.LogManager); @@ -105,12 +104,5 @@ await jsonRpcRunner.Start(cancellationToken).ContinueWith(x => if (logger.IsInfo) logger.Info("Json RPC is disabled"); } } - - private IJsonSerializer CreateJsonSerializer(JsonRpcService jsonRpcService) - { - IJsonSerializer serializer = new EthereumJsonSerializer(); - serializer.RegisterConverters(jsonRpcService.Converters); - return serializer; - } } } diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/JsonRpcIpcRunner.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/JsonRpcIpcRunner.cs index 840279d49b1..f372bf3c2b3 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/JsonRpcIpcRunner.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/JsonRpcIpcRunner.cs @@ -15,6 +15,9 @@ using Nethermind.Serialization.Json; using Nethermind.Sockets; +using System.Text.Json; +using System.Text.Json.Serialization; + namespace Nethermind.Runner.JsonRpc { public class JsonRpcIpcRunner : IDisposable diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs index 70fdec8c406..d08c96b0253 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs @@ -2,12 +2,17 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Diagnostics; using System.IO; +using System.IO.Pipelines; using System.Security.Authentication; using System.Text; using System.Threading; +using System.Threading.Tasks; + using HealthChecks.UI.Client; + using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; @@ -16,6 +21,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; + using Nethermind.Api; using Nethermind.Config; using Nethermind.Core.Authentication; @@ -71,14 +77,10 @@ public void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpcProcessor jsonRpcProcessor, IJsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, IJsonSerializer jsonSerializer) { - long SerializeTimeoutException(IJsonRpcService service, Stream resultStream, JsonRpcResult result) + ValueTask SerializeTimeoutException(IJsonRpcService service, Stream resultStream) { - JsonRpcResponse response = result.SingleResponse?.Response; - return jsonSerializer.Serialize(resultStream, service.GetErrorResponse( - ErrorCodes.Timeout, - "Request was canceled due to enabled timeout.", - response?.Id, - response?.MethodName)); + JsonRpcErrorResponse? error = service.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); + return jsonSerializer.SerializeAsync(resultStream, error); } if (env.IsDevelopment()) @@ -149,18 +151,18 @@ long SerializeTimeoutException(IJsonRpcService service, Stream resultStream, Jso jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) && jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) { - if (jsonRpcUrl.IsAuthenticated && !rpcAuthentication!.Authenticate(ctx.Request.Headers["Authorization"])) + if (jsonRpcUrl.IsAuthenticated && !rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) { JsonRpcErrorResponse? response = jsonRpcService.GetErrorResponse(ErrorCodes.InvalidRequest, "Authentication error"); ctx.Response.ContentType = "application/json"; ctx.Response.StatusCode = StatusCodes.Status403Forbidden; - jsonSerializer.Serialize(ctx.Response.Body, response); + await jsonSerializer.SerializeAsync(ctx.Response.Body, response); await ctx.Response.CompleteAsync(); return; } Stopwatch stopwatch = Stopwatch.StartNew(); - using CountingTextReader request = new(new StreamReader(ctx.Request.Body, Encoding.UTF8)); + CountingPipeReader request = new(ctx.Request.BodyReader); try { JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); @@ -194,7 +196,7 @@ long SerializeTimeoutException(IJsonRpcService service, Stream resultStream, Jso } first = false; - responseSize += jsonSerializer.SerializeWaitForEnumeration(resultStream, entry.Response); + responseSize += await jsonSerializer.SerializeAsync(resultStream, entry.Response); _ = jsonRpcLocalStats.ReportCall(entry.Report); // We reached the limit and don't want to responded to more request in the batch @@ -218,7 +220,7 @@ long SerializeTimeoutException(IJsonRpcService service, Stream resultStream, Jso { using (result.Response) { - jsonSerializer.SerializeWaitForEnumeration(resultStream, result.Response); + await jsonSerializer.SerializeAsync(resultStream, result.Response); } } @@ -231,11 +233,11 @@ long SerializeTimeoutException(IJsonRpcService service, Stream resultStream, Jso } catch (Exception e) when (e.InnerException is OperationCanceledException) { - responseSize = SerializeTimeoutException(jsonRpcService, resultStream, result); + responseSize = await SerializeTimeoutException(jsonRpcService, resultStream); } catch (OperationCanceledException) { - responseSize = SerializeTimeoutException(jsonRpcService, resultStream, result); + responseSize = await SerializeTimeoutException(jsonRpcService, resultStream); } finally { @@ -289,5 +291,59 @@ private static bool IsResourceUnavailableError(JsonRpcResponse? response) return response is JsonRpcErrorResponse { Error.Code: ErrorCodes.ModuleTimeout } or JsonRpcErrorResponse { Error.Code: ErrorCodes.LimitExceeded }; } + + private sealed class CountingPipeReader : PipeReader + { + private readonly PipeReader _wrappedReader; + private ReadOnlySequence _currentSequence; + + public long Length { get; private set; } + + public CountingPipeReader(PipeReader stream) + { + _wrappedReader = stream; + } + + public override void AdvanceTo(SequencePosition consumed) + { + Length += _currentSequence.GetOffset(consumed); + _wrappedReader.AdvanceTo(consumed); + } + + public override void AdvanceTo(SequencePosition consumed, SequencePosition examined) + { + Length += _currentSequence.GetOffset(consumed); + _wrappedReader.AdvanceTo(consumed, examined); + } + + public override void CancelPendingRead() + { + _wrappedReader.CancelPendingRead(); + } + + public override void Complete(Exception? exception = null) + { + Length += _currentSequence.Length; + _wrappedReader.Complete(exception); + } + + public override async ValueTask ReadAsync(CancellationToken cancellationToken = default) + { + ReadResult result = await _wrappedReader.ReadAsync(cancellationToken); + _currentSequence = result.Buffer; + return result; + } + + public override bool TryRead(out ReadResult result) + { + bool didRead = _wrappedReader.TryRead(out result); + if (didRead) + { + _currentSequence = result.Buffer; + } + + return didRead; + } + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/AddressConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/AddressConverter.cs deleted file mode 100644 index b892b197079..00000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/AddressConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; - -namespace Nethermind.Serialization.Json -{ - public class AddressConverter : JsonConverter
- { - public override void WriteJson(JsonWriter writer, Address value, JsonSerializer serializer) - { - writer.WriteValue(Bytes.ByteArrayToHexViaLookup32Safe(value.Bytes, true)); - } - - public override Address ReadJson(JsonReader reader, Type objectType, Address existingValue, bool hasExistingValue, JsonSerializer serializer) - { - string s = (string)reader.Value; - return string.IsNullOrEmpty(s) ? null : new Address(s); - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs index 32d7df6974c..bc00e5e9d94 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs @@ -4,84 +4,26 @@ using System; using System.Globalization; using System.Numerics; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Serialization.Json { public class BigIntegerConverter : JsonConverter { - private readonly NumberConversion _conversion; - - public BigIntegerConverter() - : this(NumberConversion.Hex) + public override BigInteger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - } - - public BigIntegerConverter(NumberConversion conversion) - { - _conversion = conversion; - } - - public override void WriteJson(JsonWriter writer, BigInteger value, JsonSerializer serializer) - { - if (value.IsZero) - { - writer.WriteValue("0x0"); - return; - } - - switch (_conversion) + return reader.TokenType switch { - case NumberConversion.Hex: - writer.WriteValue(string.Concat("0x", value.ToByteArray(false, true).ToHexString())); - break; - case NumberConversion.Decimal: - writer.WriteValue(value.ToString()); - break; - case NumberConversion.Raw: - writer.WriteValue(value); - break; - default: - throw new NotSupportedException(); - } + JsonTokenType.Number => new BigInteger(reader.GetInt64()), + JsonTokenType.String => BigInteger.Parse(reader.GetString()), + _ => throw new InvalidOperationException() + }; } - public override BigInteger ReadJson(JsonReader reader, Type objectType, BigInteger existingValue, bool hasExistingValue, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, BigInteger value, JsonSerializerOptions options) { - if (reader.Value is long || reader.Value is int) - { - return (long)reader.Value; - } - - string s = reader.Value?.ToString(); - if (s == "0x0") - { - return BigInteger.Zero; - } - - bool isHex = false; - Span withZero = null; - if (s.StartsWith("0x0")) - { - withZero = s.AsSpan(2).ToArray(); - isHex = true; - } - else if (s.StartsWith("0x")) - { - withZero = new Span(new char[s.Length - 1]); - withZero[0] = '0'; - s.AsSpan(2).CopyTo(withZero.Slice(1)); - isHex = true; - } - - if (isHex) - { - // withZero.Reverse(); - return BigInteger.Parse(withZero, NumberStyles.AllowHexSpecifier); - } - - return BigInteger.Parse(s, NumberStyles.Integer); + writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/BloomConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/BloomConverter.cs deleted file mode 100644 index 0f41632e60f..00000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/BloomConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; - -namespace Nethermind.Serialization.Json -{ - public class BloomConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, Bloom value, JsonSerializer serializer) - { - writer.WriteValue(Bytes.ByteArrayToHexViaLookup32Safe(value.Bytes, true)); - } - - public override Bloom ReadJson(JsonReader reader, Type objectType, Bloom existingValue, bool hasExistingValue, JsonSerializer serializer) - { - string s = (string)reader.Value; - return s is null ? null : new Bloom(Bytes.FromHexString(s)); - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs new file mode 100644 index 00000000000..6ec8c6cf769 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Globalization; +using Nethermind.Core.Extensions; + +namespace Nethermind.Serialization.Json +{ + using System.Buffers; + using System.Buffers.Binary; + using System.Buffers.Text; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class BooleanConverter : JsonConverter + { + public override bool Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.False) + { + return false; + } + else if (reader.TokenType == JsonTokenType.True) + { + return true; + } + else if (reader.TokenType == JsonTokenType.String) + { + if (!reader.HasValueSequence) + { + if (Utf8Parser.TryParse(reader.ValueSpan, out bool value, out _)) + { + return value; + } + } + else + { + if (Utf8Parser.TryParse(reader.ValueSequence.ToArray(), out bool value, out _)) + { + return value; + } + } + } + + throw new InvalidOperationException(); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + bool value, + JsonSerializerOptions options) + { + writer.WriteBooleanValue(value); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/BufferSegment.cs b/src/Nethermind/Nethermind.Serialization.Json/BufferSegment.cs new file mode 100644 index 00000000000..938677c32e7 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/BufferSegment.cs @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Nethermind.Serialization.Json; + +#nullable enable + +internal sealed class BufferSegment : ReadOnlySequenceSegment +{ + private IMemoryOwner? _memoryOwner; + private byte[]? _array; + private BufferSegment? _next; + private int _end; + + /// + /// The End represents the offset into AvailableMemory where the range of "active" bytes ends. At the point when the block is leased + /// the End is guaranteed to be equal to Start. The value of Start may be assigned anywhere between 0 and + /// Buffer.Length, and must be equal to or less than End. + /// + public int End + { + get => _end; + set + { + Debug.Assert(value <= AvailableMemory.Length); + + _end = value; + Memory = AvailableMemory.Slice(0, value); + } + } + + /// + /// Reference to the next block of data when the overall "active" bytes spans multiple blocks. At the point when the block is + /// leased Next is guaranteed to be null. Start, End, and Next are used together in order to create a linked-list of discontiguous + /// working memory. The "active" memory is grown when bytes are copied in, End is increased, and Next is assigned. The "active" + /// memory is shrunk when bytes are consumed, Start is increased, and blocks are returned to the pool. + /// + public BufferSegment? NextSegment + { + get => _next; + set + { + Next = value; + _next = value; + } + } + + public void SetOwnedMemory(IMemoryOwner memoryOwner) + { + _memoryOwner = memoryOwner; + AvailableMemory = memoryOwner.Memory; + } + + public void SetOwnedMemory(byte[] arrayPoolBuffer) + { + _array = arrayPoolBuffer; + AvailableMemory = arrayPoolBuffer; + } + + // Resets memory and internal state, should be called when removing the segment from the linked list + public void Reset() + { + ResetMemory(); + + Next = null; + RunningIndex = 0; + _next = null; + } + + // Resets memory only, should be called when keeping the BufferSegment in the linked list and only swapping out the memory + public void ResetMemory() + { + IMemoryOwner? memoryOwner = _memoryOwner; + if (memoryOwner != null) + { + _memoryOwner = null; + memoryOwner.Dispose(); + } + else + { + Debug.Assert(_array != null); + ArrayPool.Shared.Return(_array); + _array = null; + } + + + Memory = default; + _end = 0; + AvailableMemory = default; + } + + // Exposed for testing + internal object? MemoryOwner => (object?)_memoryOwner ?? _array; + + public Memory AvailableMemory { get; private set; } + + public int Length => End; + + public int WritableBytes + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AvailableMemory.Length - End; + } + + public void SetNext(BufferSegment segment) + { + Debug.Assert(segment != null); + Debug.Assert(Next == null); + + NextSegment = segment; + + segment = this; + + while (segment.Next != null) + { + Debug.Assert(segment.NextSegment != null); + segment.NextSegment.RunningIndex = segment.RunningIndex + segment.Length; + segment = segment.NextSegment; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static long GetLength(BufferSegment startSegment, int startIndex, BufferSegment endSegment, int endIndex) + { + return (endSegment.RunningIndex + (uint)endIndex) - (startSegment.RunningIndex + (uint)startIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static long GetLength(long startPosition, BufferSegment endSegment, int endIndex) + { + return (endSegment.RunningIndex + (uint)endIndex) - startPosition; + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/BufferSegmentStack.cs b/src/Nethermind/Nethermind.Serialization.Json/BufferSegmentStack.cs new file mode 100644 index 00000000000..09ca1148bbb --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/BufferSegmentStack.cs @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Nethermind.Serialization.Json; + +#nullable enable + +internal struct BufferSegmentStack +{ + private SegmentAsValueType[] _array; + private int _size; + + public BufferSegmentStack(int size) + { + _array = new SegmentAsValueType[size]; + _size = 0; + } + + public int Count => _size; + + public bool TryPop([NotNullWhen(true)] out BufferSegment? result) + { + int size = _size - 1; + SegmentAsValueType[] array = _array; + + if ((uint)size >= (uint)array.Length) + { + result = default; + return false; + } + + _size = size; + result = array[size]; + array[size] = default; + return true; + } + + // Pushes an item to the top of the stack. + public void Push(BufferSegment item) + { + int size = _size; + SegmentAsValueType[] array = _array; + + if ((uint)size < (uint)array.Length) + { + array[size] = item; + _size = size + 1; + } + else + { + PushWithResize(item); + } + } + + // Non-inline from Stack.Push to improve its code quality as uncommon path + [MethodImpl(MethodImplOptions.NoInlining)] + private void PushWithResize(BufferSegment item) + { + Array.Resize(ref _array, 2 * _array.Length); + _array[_size] = item; + _size++; + } + + /// + /// A simple struct we wrap reference types inside when storing in arrays to + /// bypass the CLR's covariant checks when writing to arrays. + /// + /// + /// We use as a wrapper to avoid paying the cost of covariant checks whenever + /// the underlying array that the class uses is written to. + /// We've recognized this as a perf win in ETL traces for these stack frames: + /// clr!JIT_Stelem_Ref + /// clr!ArrayStoreCheck + /// clr!ObjIsInstanceOf + /// + private readonly struct SegmentAsValueType + { + private readonly BufferSegment _value; + private SegmentAsValueType(BufferSegment value) => _value = value; + public static implicit operator SegmentAsValueType(BufferSegment s) => new SegmentAsValueType(s); + public static implicit operator BufferSegment(SegmentAsValueType s) => s._value; + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/ByteArray32Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/ByteArray32Converter.cs index a33ec509f84..a09eb926f6d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ByteArray32Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ByteArray32Converter.cs @@ -2,32 +2,58 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; + using Nethermind.Core.Extensions; -using Newtonsoft.Json; namespace Nethermind.Serialization.Json { + using System.Buffers; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + public class Bytes32Converter : JsonConverter { - public override void WriteJson(JsonWriter writer, byte[] value, JsonSerializer serializer) + [SkipLocalsInit] + public override byte[] Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - writer.WriteValue(string.Concat("0x", value.ToHexString(false).PadLeft(64, '0'))); + ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (hex.StartsWith("0x"u8)) + { + hex = hex[2..]; + } + + if (hex.Length > 64) + { + throw new JsonException(); + } + + if (hex.Length < 64) + { + Span hex32 = stackalloc byte[64]; + hex32.Fill((byte)'0'); + hex.CopyTo(hex32[(64 - hex.Length)..]); + return Bytes.FromUtf8HexString(hex32); + } + + return Bytes.FromUtf8HexString(hex); } - public override byte[] ReadJson( - JsonReader reader, - Type objectType, - byte[] existingValue, - bool hasExistingValue, - JsonSerializer serializer) + public override void Write( + Utf8JsonWriter writer, + byte[] bytes, + JsonSerializerOptions options) { - string s = (string)reader.Value; - if (s is null) + Span data = (bytes is null || bytes.Length < 32) ? stackalloc byte[32] : bytes; + if (bytes is not null && bytes.Length < 32) { - return null; + bytes.AsSpan().CopyTo(data[(32 - bytes.Length)..]); } - return Bytes.FromHexString(s); + ByteArrayConverter.Convert(writer, data, skipLeadingZeros: false); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ByteArrayConverter.cs deleted file mode 100644 index 3e0bd8b21ac..00000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/ByteArrayConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; - -namespace Nethermind.Serialization.Json -{ - public class ByteArrayConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, byte[] value, JsonSerializer serializer) - { - if (value is null) - { - writer.WriteNull(); - } - else - { - writer.WriteValue(Bytes.ByteArrayToHexViaLookup32Safe(value, true)); - } - } - - public override byte[] ReadJson(JsonReader reader, Type objectType, byte[] existingValue, bool hasExistingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - { - return null; - } - - string s = (string)reader.Value; - return Bytes.FromHexString(s); - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/DictionaryAddressKey.cs b/src/Nethermind/Nethermind.Serialization.Json/DictionaryAddressKey.cs new file mode 100644 index 00000000000..4eede341544 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/DictionaryAddressKey.cs @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +#nullable enable + +namespace Nethermind.Serialization.Json +{ + using System.Collections.Generic; + using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization; + + using Nethermind.Core; + + public class DictionaryAddressKeyConverter : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + { + return false; + } + + if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>) + && typeToConvert.GetGenericTypeDefinition() != typeof(IDictionary<,>)) + { + return false; + } + + return typeToConvert.GetGenericArguments()[0] == typeof(Address); + } + + public override JsonConverter CreateConverter( + Type type, + JsonSerializerOptions options) + { + Type keyType = type.GetGenericArguments()[0]; + Type valueType = type.GetGenericArguments()[1]; + + JsonConverter converter = (JsonConverter)Activator.CreateInstance( + typeof(DictionaryAddressKeyConverterInner<,>).MakeGenericType( + new Type[] { keyType, valueType }), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: new object[] { options }, + culture: null)!; + + return converter; + } + + private class DictionaryAddressKeyConverterInner : + JsonConverter> where TKey : notnull + { + private readonly JsonConverter _valueConverter; + + public DictionaryAddressKeyConverterInner(JsonSerializerOptions options) + { + // For performance, use the existing converter. + _valueConverter = (JsonConverter)options + .GetConverter(typeof(TValue)); + } + + public override Dictionary Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException($"JsonTokenType was of type {reader.TokenType}, only objects are supported"); + } + + var dictionary = new Dictionary(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException("JsonTokenType was not PropertyName"); + } + + var propertyName = reader.GetString(); + + if (string.IsNullOrWhiteSpace(propertyName)) + { + throw new JsonException("Failed to get property name"); + } + + reader.Read(); + + dictionary.Add(new Address(propertyName), JsonSerializer.Deserialize(ref reader, options)!); + } + + return (Dictionary)(object)dictionary; + } + + public override void Write( + Utf8JsonWriter writer, + Dictionary dictionary, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach ((TKey key, TValue value) in dictionary) + { + Address address = (Address)(object)key; + string propertyName = address.ToString(); + writer.WritePropertyName + (options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName); + + _valueConverter.Write(writer, value, options); + } + + writer.WriteEndObject(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs new file mode 100644 index 00000000000..d62578904da --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/DoubleConverter.cs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Serialization.Json +{ + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class DoubleConverter : JsonConverter + { + public override double Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + double value, + JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.0#########"), skipInputValidation: true); + } + } + + public class DoubleArrayConverter : JsonConverter + { + public override double[] Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + string s = reader.GetString(); + return JsonSerializer.Deserialize(s); + } + + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException(); + } + List values = null; + reader.Read(); + while (reader.TokenType == JsonTokenType.Number) + { + values ??= new List(); + values.Add(reader.GetDouble()); + } + if (reader.TokenType != JsonTokenType.EndArray) + { + throw new JsonException(); + } + reader.Read(); + return values?.ToArray() ?? Array.Empty(); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + double[] values, + JsonSerializerOptions options) + { + writer.WriteStartArray(); + foreach (double value in values) + { + writer.WriteRawValue(value.ToString("0.0#########"), skipInputValidation: true); + } + writer.WriteEndArray(); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs index 2eaba4cb364..946bd3effb0 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs @@ -1,165 +1,169 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Buffers; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; using System.IO; -using System.Linq; -using System.Text; +using System.IO.Pipelines; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; + using Nethermind.Core.Collections; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; namespace Nethermind.Serialization.Json { public class EthereumJsonSerializer : IJsonSerializer { - private readonly int? _maxDepth; - private JsonSerializer _internalSerializer; - private JsonSerializer _internalReadableSerializer; - - private JsonSerializerSettings _settings; - private JsonSerializerSettings _readableSettings; + private JsonSerializerOptions _jsonOptions; - public EthereumJsonSerializer(int? maxDepth = null, params JsonConverter[] converters) + public EthereumJsonSerializer(int? maxDepth = null) { - _maxDepth = maxDepth; - BasicConverters.AddRange(converters); - ReadableConverters.AddRange(converters); - RebuildSerializers(maxDepth); - } - - public static IReadOnlyList CommonConverters { get; } = new ReadOnlyCollection( - new List + if (maxDepth.HasValue) { - new AddressConverter(), - new KeccakConverter(), - new BloomConverter(), - new ByteArrayConverter(), - new LongConverter(), - new ULongConverter(), - new NullableLongConverter(), - new NullableULongConverter(), - new UInt256Converter(), - new NullableUInt256Converter(), - new BigIntegerConverter(), - new NullableBigIntegerConverter(), - new PublicKeyConverter(), - new TxTypeConverter(), - new MemoryByteConverter(), - }); - - public IList BasicConverters { get; } = CommonConverters.ToList(); - - private IList ReadableConverters { get; } = new List - { - new AddressConverter(), - new KeccakConverter(), - new BloomConverter(), - new ByteArrayConverter(), - new LongConverter(NumberConversion.Decimal), - new ULongConverter(NumberConversion.Decimal), - new NullableLongConverter(NumberConversion.Decimal), - new NullableULongConverter(NumberConversion.Decimal), - new UInt256Converter(NumberConversion.Decimal), - new NullableUInt256Converter(NumberConversion.Decimal), - new BigIntegerConverter(NumberConversion.Decimal), - new NullableBigIntegerConverter(NumberConversion.Decimal), - new PublicKeyConverter(), - new TxTypeConverter(), - new MemoryByteConverter(), - }; + _jsonOptions = CreateOptions(indented: false, maxDepth.Value); + } + else + { + _jsonOptions = JsonOptions; + } + } public T Deserialize(Stream stream) { - using StreamReader reader = new(stream); - return Deserialize(reader); + return JsonSerializer.Deserialize(stream, _jsonOptions); } public T Deserialize(string json) { - using StringReader reader = new(json); - return Deserialize(reader); + return JsonSerializer.Deserialize(json, _jsonOptions); } - private T Deserialize(TextReader reader) + public T Deserialize(ref Utf8JsonReader json) { - using JsonReader jsonReader = new JsonTextReader(reader); - return _internalSerializer.Deserialize(jsonReader); + return JsonSerializer.Deserialize(ref json, _jsonOptions); } - public string Serialize(T value, bool indented = false) { - StringWriter stringWriter = new(new StringBuilder(256), CultureInfo.InvariantCulture); - using JsonTextWriter jsonTextWriter = new(stringWriter); - if (indented) + return JsonSerializer.Serialize(value, indented ? JsonOptionsIndented : _jsonOptions); + } + + private static JsonSerializerOptions CreateOptions(bool indented, int maxDepth = 64) + { + var options = new JsonSerializerOptions { - jsonTextWriter.Formatting = _internalReadableSerializer.Formatting; - _internalReadableSerializer.Serialize(jsonTextWriter, value, typeof(T)); - } - else + WriteIndented = indented, + IncludeFields = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + MaxDepth = maxDepth, + Converters = + { + new LongConverter(), + new UInt256Converter(), + new ULongConverter(), + new IntConverter(), + new ByteArrayConverter(), + new NullableLongConverter(), + new NullableULongConverter(), + new NullableUInt256Converter(), + new NullableIntConverter(), + new TxTypeConverter(), + new DoubleConverter(), + new DoubleArrayConverter(), + new BooleanConverter(), + new DictionaryAddressKeyConverter(), + new MemoryByteConverter(), + new BigIntegerConverter(), + new NullableBigIntegerConverter(), + new JavaScriptObjectConverter(), + } + }; + + foreach (var converter in _additionalConverters) { - jsonTextWriter.Formatting = _internalSerializer.Formatting; - _internalSerializer.Serialize(jsonTextWriter, value, typeof(T)); + options.Converters.Add(converter); } - return stringWriter.ToString(); + return options; } - public long Serialize(Stream stream, T value, bool indented = false) + private static List _additionalConverters = new(); + public static void AddConverter(JsonConverter converter) { - using StreamWriter streamWriter = new(stream, leaveOpen: true); - using CountingTextWriter countingTextWriter = new(streamWriter); - using JsonTextWriter jsonTextWriter = new(countingTextWriter); - if (indented) - { - jsonTextWriter.Formatting = _internalReadableSerializer.Formatting; - _internalReadableSerializer.Serialize(jsonTextWriter, value, typeof(T)); - } - else - { - jsonTextWriter.Formatting = _internalSerializer.Formatting; - _internalSerializer.Serialize(jsonTextWriter, value, typeof(T)); - } + _additionalConverters.Add(converter); + + JsonOptions = CreateOptions(indented: false); + JsonOptionsIndented = CreateOptions(indented: true); + } + + public static JsonSerializerOptions JsonOptions { get; private set; } = CreateOptions(indented: false); - return countingTextWriter.Size; + public static JsonSerializerOptions JsonOptionsIndented { get; private set; } = CreateOptions(indented: true); + + private static StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: true); + private static StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: false); + + private static CountingStreamPipeWriter GetPipeWriter(Stream stream, bool leaveOpen) + { + return new CountingStreamPipeWriter(stream, leaveOpen ? optionsLeaveOpen : options); } - public void RegisterConverter(JsonConverter converter) + public long Serialize(Stream stream, T value, bool indented = false, bool leaveOpen = true) { - BasicConverters.Add(converter); - ReadableConverters.Add(converter); + var countingWriter = GetPipeWriter(stream, leaveOpen); + using var writer = new Utf8JsonWriter(countingWriter, new JsonWriterOptions() { SkipValidation = true, Indented = indented }); + JsonSerializer.Serialize(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + countingWriter.Complete(); - RebuildSerializers(_maxDepth); + long outputCount = countingWriter.OutputCount; + return outputCount; } - private void RebuildSerializers(int? maxDepth = null) + public async ValueTask SerializeAsync(Stream stream, T value, bool indented = false, bool leaveOpen = true) { - _readableSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - NullValueHandling = NullValueHandling.Ignore, - Formatting = Formatting.Indented, - Converters = ReadableConverters, - }; + var countingWriter = GetPipeWriter(stream, leaveOpen); + using var writer = new Utf8JsonWriter(countingWriter, new JsonWriterOptions() { SkipValidation = true, Indented = indented }); + JsonSerializer.Serialize(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + await countingWriter.CompleteAsync(); - _settings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - NullValueHandling = NullValueHandling.Ignore, - Formatting = Formatting.None, - Converters = BasicConverters, - }; + long outputCount = countingWriter.OutputCount; + return outputCount; + } + + public static void SerializeToStream(Stream stream, T value, bool indented = false) + { + JsonSerializer.Serialize(stream, value, indented ? JsonOptionsIndented : JsonOptions); + } + } + + public static class JsonElementExtensions + { + public static bool TryGetSubProperty(this JsonElement element, string innerPath, out JsonElement value) + { + ArgumentNullException.ThrowIfNullOrEmpty(innerPath); - if (maxDepth is not null) + if (innerPath.Contains('.')) { - _readableSettings.MaxDepth = _settings.MaxDepth = maxDepth.Value; + string[] parts = innerPath.Split('.'); + JsonElement currentElement = element; + for (int i = 0; i < parts.Length - 1; i++) + { + if (!currentElement.TryGetProperty(parts[i], out currentElement)) + { + value = default; + return false; + } + } + return currentElement.TryGetProperty(parts[^1], out value); } - _internalSerializer = JsonSerializer.Create(_settings); - _internalReadableSerializer = JsonSerializer.Create(_readableSettings); + return element.TryGetProperty(innerPath.AsSpan(), out value); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs index 6faf2f9a5e0..a4f2ee0cad0 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs @@ -9,5 +9,5 @@ public static class ForcedNumberConversion { public static readonly AsyncLocal ForcedConversion = new(null); - public static NumberConversion GetFinalConversion(this NumberConversion conversion) => ForcedConversion.Value ?? conversion; + public static NumberConversion GetFinalConversion() => ForcedConversion.Value ?? NumberConversion.Hex; } diff --git a/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs index be404d0ab0e..16a5ee3168a 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/IJsonSerializer.cs @@ -1,9 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using System.IO; -using Newtonsoft.Json; +using System.Threading.Tasks; namespace Nethermind.Serialization.Json { @@ -12,15 +11,7 @@ public interface IJsonSerializer T Deserialize(Stream stream); T Deserialize(string json); string Serialize(T value, bool indented = false); - long Serialize(Stream stream, T value, bool indented = false); - void RegisterConverter(JsonConverter converter); - - void RegisterConverters(IEnumerable converters) - { - foreach (JsonConverter converter in converters) - { - RegisterConverter(converter); - } - } + long Serialize(Stream stream, T value, bool indented = false, bool leaveOpen = true); + ValueTask SerializeAsync(Stream stream, T value, bool indented = false, bool leaveOpen = true); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/IdConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/IdConverter.cs index cc815659498..cd77662df34 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/IdConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/IdConverter.cs @@ -3,48 +3,64 @@ using System; using System.Numerics; -using Newtonsoft.Json; namespace Nethermind.Serialization.Json { - public class IdConverter : JsonConverter + using System.Text.Json; + using System.Text.Json.Serialization; + + public class IdConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override object Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) + { + if (reader.TryGetInt64(out long value)) + { + return value; + } + if (reader.TryGetDecimal(out decimal val) && val.Scale == 0) + { + return val; + } + + throw new NotSupportedException(); + } + + return reader.GetString(); + } + + public override void Write( + Utf8JsonWriter writer, + object value, + JsonSerializerOptions options) { + switch (value) { case int typedValue: - writer.WriteValue(typedValue); + writer.WriteNumberValue(typedValue); break; case long typedValue: - writer.WriteValue(typedValue); + writer.WriteNumberValue(typedValue); + break; + case decimal typedValue: + writer.WriteNumberValue(typedValue); break; case BigInteger typedValue: - writer.WriteValue(typedValue); + writer.WriteNumberValue((decimal)typedValue); break; case string typedValue: - writer.WriteValue(typedValue); + writer.WriteStringValue(typedValue); break; default: throw new NotSupportedException(); } } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - switch (reader.TokenType) - { - case JsonToken.Integer: - return reader.Value; - case JsonToken.String: - return reader.Value as string; - case JsonToken.Null: - return null; - default: - throw new NotSupportedException($"{reader.TokenType}"); - } - } - public override bool CanConvert(Type objectType) { return true; diff --git a/src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs new file mode 100644 index 00000000000..08e512f492f --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/IntConverter.cs @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Serialization.Json +{ + using System.Buffers; + using System.Buffers.Text; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class IntConverter : JsonConverter + { + private static int FromString(ReadOnlySpan s) + { + if (s.Length == 0) + { + throw new JsonException("null cannot be assigned to long"); + } + + if (s.SequenceEqual("0x0"u8)) + { + return 0; + } + + int value; + if (s.StartsWith("0x"u8)) + { + s = s.Slice(2); + if (Utf8Parser.TryParse(s, out value, out _, 'x')) + { + return value; + } + } + else if (Utf8Parser.TryParse(s, out value, out _)) + { + return value; + } + + throw new JsonException("hex to int"); + } + + public override int Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt32(); + } + else if (reader.TokenType == JsonTokenType.String) + { + if (!reader.HasValueSequence) + { + return FromString(reader.ValueSpan); + } + else + { + return FromString(reader.ValueSequence.ToArray()); + } + } + + throw new JsonException(); + } + + public override void Write( + Utf8JsonWriter writer, + int value, + JsonSerializerOptions options) + { + writer.WriteNumberValue(value); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/JavaScriptObjectConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/JavaScriptObjectConverter.cs new file mode 100644 index 00000000000..1f0434bd374 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/JavaScriptObjectConverter.cs @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; + +using Microsoft.ClearScript; +using Microsoft.ClearScript.JavaScript; + +#nullable enable + +namespace Nethermind.Serialization.Json; + +public class JavaScriptObjectConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) => typeof(IJavaScriptObject).IsAssignableFrom(objectType); + + public override void Write(Utf8JsonWriter writer, IJavaScriptObject o, JsonSerializerOptions options) + { + if (o is IDictionary dictionary) + { + if (dictionary.TryGetValue("value", out object? value)) + { + // value is marshaled to BigInteger by ClearScript + if (value is BigInteger bigInteger) + { + writer.WriteStringValue(bigInteger.ToString(CultureInfo.InvariantCulture)); + return; + } + + if (value == Undefined.Value) + { + dictionary.Remove("value"); + } + } + + // remove undefined errors + if (dictionary.TryGetValue("error", out object? error) && error == Undefined.Value) + { + dictionary.Remove("error"); + } + + JsonSerializer.Serialize(writer, dictionary, options); + } + else if (o is IList list) + { + JsonSerializer.Serialize(writer, list, options); + } + else if (o is IArrayBufferView buffer) + { + int size = (int)buffer.Size; + if (size == 0) + { + JsonSerializer.Serialize(writer, Array.Empty(), options); + return; + } + + byte[] array = ArrayPool.Shared.Rent(size); + + buffer.ReadBytes(buffer.Offset, buffer.Size, array, 0); + ByteArrayConverter.Convert(writer, array.AsSpan(0, size), skipLeadingZeros: false); + + ArrayPool.Shared.Return(array); + } + else + { + throw new NotSupportedException(o.GetType().ToString()); + } + } + + public override IJavaScriptObject? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/KeccakConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/KeccakConverter.cs deleted file mode 100644 index 493c7b7a4e0..00000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/KeccakConverter.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; - -namespace Nethermind.Serialization.Json -{ - public class KeccakConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, Hash256 value, JsonSerializer serializer) - { - if (value is null) - { - writer.WriteNull(); - } - else - { - writer.WriteValue(value.Bytes.ToHexString(true)); - } - } - - public override Hash256 ReadJson(JsonReader reader, Type objectType, Hash256 existingValue, bool hasExistingValue, JsonSerializer serializer) - { - string s = (string)reader.Value; - return string.IsNullOrWhiteSpace(s) ? null : new Hash256(Bytes.FromHexString(s)); - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs index 4d597b16731..991256c119b 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs @@ -3,50 +3,18 @@ using System; using System.Globalization; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; namespace Nethermind.Serialization.Json { + using System.Buffers; + using System.Buffers.Binary; + using System.Buffers.Text; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + public class LongConverter : JsonConverter { - private readonly NumberConversion _conversion; - - public LongConverter() - : this(NumberConversion.Hex) - { - } - - public LongConverter(NumberConversion conversion) - { - _conversion = conversion; - } - - public override void WriteJson(JsonWriter writer, long value, JsonSerializer serializer) - { - switch (_conversion.GetFinalConversion()) - { - case NumberConversion.Hex: - writer.WriteValue(value == 0L ? "0x0" : value.ToHexString(true)); - break; - case NumberConversion.Decimal: - writer.WriteValue(value == 0 ? "0" : value.ToString()); - break; - case NumberConversion.Raw: - writer.WriteValue(value); - break; - default: - throw new NotSupportedException(); - } - } - - public override long ReadJson(JsonReader reader, Type objectType, long existingValue, bool hasExistingValue, JsonSerializer serializer) - { - return reader.Value is long || reader.Value is int - ? (long)reader.Value - : FromString(reader.Value?.ToString()); - } - public static long FromString(string s) { if (s is null) @@ -74,5 +42,90 @@ public static long FromString(string s) return long.Parse(s, NumberStyles.Integer); } + + public static long FromString(ReadOnlySpan s) + { + if (s.Length == 0) + { + throw new JsonException("null cannot be assigned to long"); + } + + if (s.SequenceEqual("0x0"u8)) + { + return 0L; + } + + long value; + if (s.StartsWith("0x"u8)) + { + s = s.Slice(2); + if (Utf8Parser.TryParse(s, out value, out _, 'x')) + { + return value; + } + } + else if (Utf8Parser.TryParse(s, out value, out _)) + { + return value; + } + + throw new JsonException("hex to long"); + } + + public override long Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt64(); + } + else if (reader.TokenType == JsonTokenType.String) + { + if (!reader.HasValueSequence) + { + return FromString(reader.ValueSpan); + } + else + { + return FromString(reader.ValueSequence.ToArray()); + } + } + + throw new JsonException(); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + long value, + JsonSerializerOptions options) + { + switch (ForcedNumberConversion.GetFinalConversion()) + { + case NumberConversion.Hex: + if (value == 0) + { + writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); + } + else + { + Span bytes = stackalloc byte[8]; + BinaryPrimitives.WriteInt64BigEndian(bytes, value); + ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); + } + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); + + } + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs new file mode 100644 index 00000000000..df81b47e6be --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Nethermind.Serialization.Json; + +public class LongRawJsonConverter : JsonConverter +{ + public override long Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt64(); + } + else if (reader.TokenType == JsonTokenType.String) + { + if (!reader.HasValueSequence) + { + return LongConverter.FromString(reader.ValueSpan); + } + else + { + return LongConverter.FromString(reader.ValueSequence.ToArray()); + } + } + + throw new JsonException(); + } + + public override void Write( + Utf8JsonWriter writer, + long value, + JsonSerializerOptions options) + { + writer.WriteNumberValue(value); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/LowerCaseNamingStrategy.cs b/src/Nethermind/Nethermind.Serialization.Json/LowerCaseNamingStrategy.cs deleted file mode 100644 index ff6535e02c4..00000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/LowerCaseNamingStrategy.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Newtonsoft.Json.Serialization; - -namespace Nethermind.Serialization.Json -{ - public class LowerCaseNamingStrategy : NamingStrategy - { - protected override string ResolvePropertyName(string name) => name.ToLowerInvariant(); - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs index c408db7c3bd..45780816a9b 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs @@ -2,33 +2,40 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core.Extensions; -using Newtonsoft.Json; namespace Nethermind.Serialization.Json; public class MemoryByteConverter : JsonConverter> { - public override void WriteJson(JsonWriter writer, Memory value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, Memory value, JsonSerializerOptions options) { if (value.IsEmpty) { - writer.WriteNull(); + writer.WriteNullValue(); } else { - writer.WriteValue(Bytes.ByteArrayToHexViaLookup32Safe(value, true)); + ByteArrayConverter.Convert(writer, value.Span); } } - public override Memory ReadJson(JsonReader reader, Type objectType, Memory existingValue, bool hasExistingValue, JsonSerializer serializer) + public override Memory Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonToken.Null) + if (reader.TokenType == JsonTokenType.Null) { - return null; + return default; } - string s = (string)reader.Value; - return Bytes.FromHexString(s); + ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (hex.StartsWith("0x"u8)) + { + hex = hex[2..]; + } + + return Bytes.FromUtf8HexString(hex); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj b/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj index 4b05d12ded8..ee58e74ddc3 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj +++ b/src/Nethermind/Nethermind.Serialization.Json/Nethermind.Serialization.Json.csproj @@ -1,12 +1,13 @@ + + true + + - - - - + diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableBigIntegerConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableBigIntegerConverter.cs index 5505706358f..25181e4d21c 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableBigIntegerConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableBigIntegerConverter.cs @@ -3,37 +3,31 @@ using System; using System.Numerics; -using Newtonsoft.Json; +using System.Reflection.PortableExecutable; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Serialization.Json { public class NullableBigIntegerConverter : JsonConverter { - private readonly BigIntegerConverter _bigIntegerConverter; + private static readonly BigIntegerConverter _bigIntegerConverter = new(); - public NullableBigIntegerConverter() - : this(NumberConversion.Hex) + public override BigInteger? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - } + if (reader.TokenType == JsonTokenType.Null) { return null; } - public NullableBigIntegerConverter(NumberConversion conversion) - { - _bigIntegerConverter = new BigIntegerConverter(conversion); - } - - public override void WriteJson(JsonWriter writer, BigInteger? value, JsonSerializer serializer) - { - _bigIntegerConverter.WriteJson(writer, value.Value, serializer); + return _bigIntegerConverter.Read(ref reader, typeToConvert, options); } - public override BigInteger? ReadJson(JsonReader reader, Type objectType, BigInteger? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, BigInteger? value, JsonSerializerOptions options) { - if (reader.TokenType == JsonToken.Null || reader.Value is null) + if (value is null) { - return null; + writer.WriteNullValue(); } - return _bigIntegerConverter.ReadJson(reader, objectType, existingValue ?? 0, hasExistingValue, serializer); + _bigIntegerConverter.Write(writer, value.GetValueOrDefault(), options); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableIntConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableIntConverter.cs new file mode 100644 index 00000000000..363d3e6e698 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableIntConverter.cs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Serialization.Json +{ + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class NullableIntConverter : JsonConverter + { + private readonly IntConverter _converter = new(); + + public override int? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + return _converter.Read(ref reader, typeToConvert, options); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + int? value, + JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + } + + _converter.Write(writer, value.GetValueOrDefault(), options); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableLongConvertercs.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableLongConvertercs.cs index ee294f89432..1695bd5c2e0 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableLongConvertercs.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableLongConvertercs.cs @@ -2,43 +2,74 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Serialization.Json { public class NullableLongConverter : JsonConverter { - private readonly LongConverter _longConverter; + private static readonly LongConverter _converter = new(); - public NullableLongConverter() - : this(NumberConversion.Hex) + public override long? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - } + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } - public NullableLongConverter(NumberConversion conversion) - { - _longConverter = new LongConverter(conversion); + return _converter.Read(ref reader, typeToConvert, options); } - public override void WriteJson(JsonWriter writer, long? value, JsonSerializer serializer) + public override void Write( + Utf8JsonWriter writer, + long? value, + JsonSerializerOptions options) { if (!value.HasValue) { - writer.WriteNull(); - return; + writer.WriteNullValue(); + } + else + { + _converter.Write(writer, value.GetValueOrDefault(), options); } - - _longConverter.WriteJson(writer, value.Value, serializer); } + } - public override long? ReadJson(JsonReader reader, Type objectType, long? existingValue, bool hasExistingValue, JsonSerializer serializer) + public class NullableRawLongConverter : JsonConverter + { + private readonly LongConverter _converter = new(); + + public override long? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - if (reader.TokenType == JsonToken.Null || reader.Value is null) + if (reader.TokenType == JsonTokenType.Null) { return null; } - return _longConverter.ReadJson(reader, objectType, existingValue ?? 0, hasExistingValue, serializer); + return _converter.Read(ref reader, typeToConvert, options); + } + + public override void Write( + Utf8JsonWriter writer, + long? value, + JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + } + else + { + writer.WriteNumberValue(value.GetValueOrDefault()); + } } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableUInt256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableUInt256Converter.cs index 45ddc460772..addf68b8fe8 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableUInt256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableUInt256Converter.cs @@ -3,43 +3,40 @@ using System; using Nethermind.Int256; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Serialization.Json { public class NullableUInt256Converter : JsonConverter { - private UInt256Converter _uInt256Converter; + private static readonly UInt256Converter _converter = new(); - public NullableUInt256Converter() - : this(NumberConversion.Hex) + public override UInt256? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - } - - public NullableUInt256Converter(NumberConversion conversion) - { - _uInt256Converter = new UInt256Converter(conversion); - } - - public override void WriteJson(JsonWriter writer, UInt256? value, JsonSerializer serializer) - { - if (!value.HasValue) + if (reader.TokenType == JsonTokenType.Null) { - writer.WriteNull(); - return; + return null; } - _uInt256Converter.WriteJson(writer, value.Value, serializer); + return _converter.Read(ref reader, typeToConvert, options); } - public override UInt256? ReadJson(JsonReader reader, Type objectType, UInt256? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override void Write( + Utf8JsonWriter writer, + UInt256? value, + JsonSerializerOptions options) { - if (reader.TokenType == JsonToken.Null || reader.Value is null) + if (!value.HasValue) { - return null; + writer.WriteNullValue(); + return; } - return _uInt256Converter.ReadJson(reader, objectType, existingValue ?? 0, hasExistingValue, serializer); + _converter.Write(writer, value.GetValueOrDefault(), options); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs index 90b27e2f074..9d023cdcf66 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs @@ -2,44 +2,41 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using Newtonsoft.Json; namespace Nethermind.Serialization.Json { + using System.Text.Json; + using System.Text.Json.Serialization; + public class NullableULongConverter : JsonConverter { - private readonly ULongConverter _ulongConverter; - - public NullableULongConverter() - : this(NumberConversion.Hex) - { - } - - public NullableULongConverter(NumberConversion conversion) - { - _ulongConverter = new ULongConverter(conversion); - } + private readonly ULongConverter _converter = new(); - public override void WriteJson(JsonWriter writer, ulong? value, JsonSerializer serializer) + public override ulong? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - if (!value.HasValue) + if (reader.TokenType == JsonTokenType.Null) { - writer.WriteNull(); - return; + return null; } - _ulongConverter.WriteJson(writer, value.Value, serializer); + return _converter.Read(ref reader, typeToConvert, options); } - public override ulong? ReadJson(JsonReader reader, Type objectType, ulong? existingValue, bool hasExistingValue, - JsonSerializer serializer) + public override void Write( + Utf8JsonWriter writer, + ulong? value, + JsonSerializerOptions options) { - if (reader.TokenType == JsonToken.Null) + if (!value.HasValue) { - return null; + writer.WriteNullValue(); + return; } - return _ulongConverter.ReadJson(reader, objectType, existingValue ?? 0, hasExistingValue, serializer); + _converter.Write(writer, value.GetValueOrDefault(), options); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/PublicKeyConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/PublicKeyConverter.cs deleted file mode 100644 index 45596a8efe9..00000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/PublicKeyConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core.Crypto; -using Newtonsoft.Json; - -namespace Nethermind.Serialization.Json -{ - public class PublicKeyConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, PublicKey value, JsonSerializer serializer) - { - writer.WriteValue(value.ToString()); - } - - public override PublicKey ReadJson(JsonReader reader, Type objectType, PublicKey existingValue, bool hasExistingValue, JsonSerializer serializer) - { - string s = (string)reader.Value; - return s is null ? null : new PublicKey(s); - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/StorageCellIndexConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/StorageCellIndexConverter.cs index b4e21addf20..a70c82123d3 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/StorageCellIndexConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/StorageCellIndexConverter.cs @@ -4,18 +4,68 @@ using System; using Nethermind.Core.Extensions; using Nethermind.Int256; -using Newtonsoft.Json; + +#nullable enable namespace Nethermind.Serialization.Json { - public class StorageCellIndexConverter : JsonConverter + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class StorageCellIndexConverter : JsonConverter?> { - public override void WriteJson(JsonWriter writer, UInt256 value, JsonSerializer serializer) + private UInt256Converter _converter = new(); + + public override IEnumerable? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - writer.WriteValue(value.ToHexString(false)); + if (reader.TokenType == JsonTokenType.Null) + { + return Array.Empty(); + } + + if (reader.TokenType != JsonTokenType.StartArray) + { + throw new JsonException(); + } + + reader.Read(); + List? value = null; + while (reader.TokenType != JsonTokenType.EndArray) + { + value ??= new(); + value.Add(_converter.Read(ref reader, typeToConvert, options)); + reader.Read(); + } + + return value?.ToArray() ?? Array.Empty(); } - public override UInt256 ReadJson(JsonReader reader, Type objectType, UInt256 existingValue, bool hasExistingValue, JsonSerializer serializer) => - UInt256Converter.ReaderJson(reader); + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + IEnumerable? values, + JsonSerializerOptions options) + { + if (values is null) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStartArray(); + Span bytes = stackalloc byte[32]; + + foreach (var value in values) + { + value.ToBigEndian(bytes); + ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: false); + } + writer.WriteEndArray(); + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/StreamPipeWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/StreamPipeWriter.cs new file mode 100644 index 00000000000..f791f5d071b --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/StreamPipeWriter.cs @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Diagnostics; +using System.IO.Pipelines; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +using Nethermind.Core.Collections; + +namespace Nethermind.Serialization.Json; + +#nullable enable + +internal sealed class CountingStreamPipeWriter : PipeWriter +{ + internal const int InitialSegmentPoolSize = 4; // 16K + internal const int MaxSegmentPoolSize = 256; // 1MB + + private readonly int _minimumBufferSize; + + private BufferSegment? _head; + private BufferSegment? _tail; + private Memory _tailMemory; + private int _tailBytesBuffered; + private int _bytesBuffered; + + private readonly MemoryPool? _pool; + private readonly int _maxPooledBufferSize; + + private CancellationTokenSource? _internalTokenSource; + private bool _isCompleted; + private readonly object _lockObject = new object(); + + private BufferSegmentStack _bufferSegmentPool; + private readonly bool _leaveOpen; + + private CancellationTokenSource InternalTokenSource + { + get + { + lock (_lockObject) + { + return _internalTokenSource ??= new CancellationTokenSource(); + } + } + } + + public CountingStreamPipeWriter(Stream writingStream, StreamPipeWriterOptions options) + { + if (writingStream is null) + { + ThrowHelper.ThrowArgumentNullException_WritingStream(); + } + if (options is null) + { + ThrowHelper.ThrowArgumentNullException_Options(); + } + + InnerStream = writingStream; + _minimumBufferSize = options.MinimumBufferSize; + _pool = options.Pool == MemoryPool.Shared ? null : options.Pool; + _maxPooledBufferSize = _pool?.MaxBufferSize ?? -1; + _bufferSegmentPool = new BufferSegmentStack(InitialSegmentPoolSize); + _leaveOpen = options.LeaveOpen; + } + + /// + /// Gets the inner stream that is being written to. + /// + public Stream InnerStream { get; } + public long OutputCount { get; set; } + + /// + public override void Advance(int bytes) + { + if ((uint)bytes > (uint)_tailMemory.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException_Bytes(); + } + + _tailBytesBuffered += bytes; + _bytesBuffered += bytes; + _tailMemory = _tailMemory.Slice(bytes); + OutputCount += bytes; + + if (_bytesBuffered > _minimumBufferSize) + { + FlushInternal(writeToStream: true); + } + } + + /// + public override Memory GetMemory(int sizeHint = 0) + { + if (_isCompleted) + { + ThrowHelper.ThrowInvalidOperationException_NoWritingAllowed(); + } + + if (sizeHint < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException_SizeHint(); + } + + AllocateMemory(sizeHint); + + return _tailMemory; + } + + /// + public override Span GetSpan(int sizeHint = 0) + { + if (_isCompleted) + { + ThrowHelper.ThrowInvalidOperationException_NoWritingAllowed(); + } + + if (sizeHint < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException_SizeHint(); + } + + AllocateMemory(sizeHint); + + return _tailMemory.Span; + } + + private void AllocateMemory(int sizeHint) + { + if (_head == null) + { + // We need to allocate memory to write since nobody has written before + BufferSegment newSegment = AllocateSegment(sizeHint); + + // Set all the pointers + _head = _tail = newSegment; + _tailBytesBuffered = 0; + } + else + { + Debug.Assert(_tail != null); + int bytesLeftInBuffer = _tailMemory.Length; + + if (bytesLeftInBuffer == 0 || bytesLeftInBuffer < sizeHint) + { + if (_tailBytesBuffered > 0) + { + // Flush buffered data to the segment + _tail.End += _tailBytesBuffered; + _tailBytesBuffered = 0; + } + + BufferSegment newSegment = AllocateSegment(sizeHint); + + _tail.SetNext(newSegment); + _tail = newSegment; + } + } + } + + private BufferSegment AllocateSegment(int sizeHint) + { + Debug.Assert(sizeHint >= 0); + BufferSegment newSegment = CreateSegmentUnsynchronized(); + + int maxSize = _maxPooledBufferSize; + if (sizeHint <= maxSize) + { + // Use the specified pool as it fits. Specified pool is not null as maxSize == -1 if _pool is null. + newSegment.SetOwnedMemory(_pool!.Rent(GetSegmentSize(sizeHint, maxSize))); + } + else + { + // Use the array pool + int sizeToRequest = GetSegmentSize(sizeHint); + newSegment.SetOwnedMemory(ArrayPool.Shared.Rent(sizeToRequest)); + } + + _tailMemory = newSegment.AvailableMemory; + + return newSegment; + } + + private int GetSegmentSize(int sizeHint, int maxBufferSize = int.MaxValue) + { + // First we need to handle case where hint is smaller than minimum segment size + sizeHint = Math.Max(_minimumBufferSize, sizeHint); + // After that adjust it to fit into pools max buffer size + var adjustedToMaximumSize = Math.Min(maxBufferSize, sizeHint); + return adjustedToMaximumSize; + } + + private BufferSegment CreateSegmentUnsynchronized() + { + if (_bufferSegmentPool.TryPop(out BufferSegment? segment)) + { + return segment; + } + + return new BufferSegment(); + } + + private void ReturnSegmentUnsynchronized(BufferSegment segment) + { + segment.Reset(); + if (_bufferSegmentPool.Count < MaxSegmentPoolSize) + { + _bufferSegmentPool.Push(segment); + } + } + + /// + public override void CancelPendingFlush() + { + Cancel(); + } + + /// + public override bool CanGetUnflushedBytes => true; + + /// + public override void Complete(Exception? exception = null) + { + if (_isCompleted) + { + return; + } + + _isCompleted = true; + + try + { + FlushInternal(writeToStream: exception == null); + } + finally + { + _internalTokenSource?.Dispose(); + + if (!_leaveOpen) + { + InnerStream.Dispose(); + } + } + } + + public override async ValueTask CompleteAsync(Exception? exception = null) + { + if (_isCompleted) + { + return; + } + + _isCompleted = true; + + try + { + await FlushAsyncInternal(writeToStream: exception == null, data: Memory.Empty).ConfigureAwait(false); + } + finally + { + _internalTokenSource?.Dispose(); + + if (!_leaveOpen) + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + await InnerStream.DisposeAsync().ConfigureAwait(false); +#else + InnerStream.Dispose(); +#endif + } + } + } + + /// + public override ValueTask FlushAsync(CancellationToken cancellationToken = default) + { + if (_bytesBuffered == 0) + { + return new ValueTask(new FlushResult(isCanceled: false, isCompleted: false)); + } + + return FlushAsyncInternal(writeToStream: true, data: Memory.Empty, cancellationToken); + } + + /// + public override long UnflushedBytes => _bytesBuffered; + + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken = default) + { + return FlushAsyncInternal(writeToStream: true, data: source, cancellationToken); + } + + private void Cancel() + { + InternalTokenSource.Cancel(); + } + + [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] + private async ValueTask FlushAsyncInternal(bool writeToStream, ReadOnlyMemory data, CancellationToken cancellationToken = default) + { + // Write all completed segments and whatever remains in the current segment + // and flush the result. + CancellationTokenRegistration reg = default; + if (cancellationToken.CanBeCanceled) + { + reg = cancellationToken.UnsafeRegister(state => ((CountingStreamPipeWriter)state!).Cancel(), this); + } + + if (_tailBytesBuffered > 0) + { + Debug.Assert(_tail != null); + + // Update any buffered data + _tail.End += _tailBytesBuffered; + _tailBytesBuffered = 0; + } + + using (reg) + { + CancellationToken localToken = InternalTokenSource.Token; + try + { + BufferSegment? segment = _head; + while (segment != null) + { + BufferSegment returnSegment = segment; + segment = segment.NextSegment; + + if (returnSegment.Length > 0 && writeToStream) + { + await InnerStream.WriteAsync(returnSegment.Memory, localToken).ConfigureAwait(false); + } + + ReturnSegmentUnsynchronized(returnSegment); + + // Update the head segment after we return the current segment + _head = segment; + } + + if (writeToStream) + { + // Write data after the buffered data + if (data.Length > 0) + { + await InnerStream.WriteAsync(data, localToken).ConfigureAwait(false); + } + + if (_bytesBuffered > 0 || data.Length > 0) + { + await InnerStream.FlushAsync(localToken).ConfigureAwait(false); + } + } + + // Mark bytes as written *after* flushing + _head = null; + _tail = null; + _tailMemory = default; + _bytesBuffered = 0; + + return new FlushResult(isCanceled: false, isCompleted: false); + } + catch (OperationCanceledException) + { + // Remove the cancellation token such that the next time Flush is called + // A new CTS is created. + lock (_lockObject) + { + _internalTokenSource = null; + } + + if (localToken.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + // Catch cancellation and translate it into setting isCanceled = true + return new FlushResult(isCanceled: true, isCompleted: false); + } + + throw; + } + } + } + + private void FlushInternal(bool writeToStream) + { + // Write all completed segments and whatever remains in the current segment + // and flush the result. + if (_tailBytesBuffered > 0) + { + Debug.Assert(_tail != null); + + // Update any buffered data + _tail.End += _tailBytesBuffered; + _tailBytesBuffered = 0; + } + + BufferSegment? segment = _head; + while (segment != null) + { + BufferSegment returnSegment = segment; + segment = segment.NextSegment; + + if (returnSegment.Length > 0 && writeToStream) + { + InnerStream.Write(returnSegment.Memory.Span); + } + + ReturnSegmentUnsynchronized(returnSegment); + + // Update the head segment after we return the current segment + _head = segment; + } + + if (_bytesBuffered > 0 && writeToStream) + { + InnerStream.Flush(); + } + + // Mark bytes as written *after* flushing + _head = null; + _tail = null; + _tailMemory = default; + _bytesBuffered = 0; + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/TxTypeConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/TxTypeConverter.cs index aeebed1539a..2a5e8da93cf 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/TxTypeConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/TxTypeConverter.cs @@ -2,24 +2,38 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Core; -using Newtonsoft.Json; namespace Nethermind.Serialization.Json { + public class TxTypeConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, TxType txTypeValue, JsonSerializer serializer) + public override TxType Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - byte byteValue = (byte)txTypeValue; - writer.WriteValue(string.Concat("0x", byteValue.ToString("X"))); + return (TxType)Convert.ToByte(reader.GetString(), 16); } - public override TxType ReadJson(JsonReader reader, Type objectType, TxType existingValue, bool hasExistingValue, - JsonSerializer serializer) + public override void Write( + Utf8JsonWriter writer, + TxType txTypeValue, + JsonSerializerOptions options) { - string s = (string)reader.Value; - return (TxType)Convert.ToByte(s, 16); + if (txTypeValue == TxType.Legacy) + { + writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); + return; + } + + byte byteValue = (byte)txTypeValue; + ByteArrayConverter.Convert(writer, MemoryMarshal.CreateReadOnlySpan(ref byteValue, 1), skipLeadingZeros: true); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs index 2b4a05ab3f8..7b52ca4b9d2 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs @@ -2,98 +2,103 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Core.Extensions; using Nethermind.Int256; -using Newtonsoft.Json; -namespace Nethermind.Serialization.Json +namespace Nethermind.Serialization.Json; + +public class UInt256Converter : JsonConverter { - public class UInt256Converter : JsonConverter + public override UInt256 Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - private readonly NumberConversion _conversion; - - public UInt256Converter() - : this(NumberConversion.Hex) + if (reader.TokenType == JsonTokenType.Number) { + return (UInt256)reader.GetUInt64(); } - - public UInt256Converter(NumberConversion conversion) + if (reader.TokenType != JsonTokenType.String) { - _conversion = conversion; + ThrowJsonException(); } - public override void WriteJson(JsonWriter writer, UInt256 value, JsonSerializer serializer) - { - if (value.IsZero) - { - writer.WriteValue("0x0"); - return; - } - - NumberConversion usedConversion = _conversion == NumberConversion.Decimal - ? value < int.MaxValue ? NumberConversion.Decimal : NumberConversion.Hex - : _conversion; + ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + return Read(hex); + } - switch (usedConversion.GetFinalConversion()) - { - case NumberConversion.Hex: - writer.WriteValue(value.ToHexString(true)); - break; - case NumberConversion.Decimal: - writer.WriteRawValue(value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteValue((BigInteger)value); - break; - default: - throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); - } + public static UInt256 Read(ReadOnlySpan hex) + { + if (hex.SequenceEqual("0x0"u8)) + { + return default; } - public override UInt256 ReadJson(JsonReader reader, Type objectType, UInt256 existingValue, bool hasExistingValue, JsonSerializer serializer) => - ReaderJson(reader); - - public static UInt256 ReaderJson(JsonReader reader) + if (hex.StartsWith("0x"u8)) { - if (reader.Value is long || reader.Value is int) - { - return (UInt256)(long)reader.Value; - } - - string s = reader.Value?.ToString(); - if (s is null) - { - throw new JsonException($"{nameof(UInt256)} cannot be deserialized from null"); - } - - if (s == "0x0") - { - return UInt256.Zero; - } - - if (s.StartsWith("0x0")) + hex = hex[2..]; + } + else if (hex[0] != (byte)'0') + { + if (UInt256.TryParse(Encoding.UTF8.GetString(hex), out var result)) { - return UInt256.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); + return result; } + } - if (s.StartsWith("0x")) - { - Span withZero = new(new char[s.Length - 1]); - withZero[0] = '0'; - s.AsSpan(2).CopyTo(withZero.Slice(1)); - return UInt256.Parse(withZero, NumberStyles.AllowHexSpecifier); - } + Span bytes = stackalloc byte[32]; + int length = (hex.Length >> 1) + hex.Length % 2; + Bytes.FromUtf8HexString(hex, bytes[(32 - length)..]); + ReadOnlySpan readOnlyBytes = bytes; + return new UInt256(in readOnlyBytes, isBigEndian: true); + } - try - { - return UInt256.Parse(s, NumberStyles.Integer); - } - catch (Exception) - { - return UInt256.Parse(s, NumberStyles.HexNumber); - } + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + UInt256 value, + JsonSerializerOptions options) + { + if (value.IsZero) + { + writer.WriteRawValue("\"0x0\""); + return; } + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + switch (usedConversion) + { + case NumberConversion.Hex: + { + Span bytes = stackalloc byte[32]; + value.ToBigEndian(bytes); + ByteArrayConverter.Convert(writer, bytes); + } + break; + case NumberConversion.Decimal: + writer.WriteRawValue(value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteStringValue(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); + break; + default: + throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); + } + } + + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowJsonException() + { + throw new JsonException(); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs index 625bc2c0464..1db75c2a7a7 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs @@ -2,76 +2,105 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Globalization; -using Nethermind.Core.Extensions; -using Newtonsoft.Json; -namespace Nethermind.Serialization.Json; - -public class ULongConverter : JsonConverter +namespace Nethermind.Serialization.Json { - private readonly NumberConversion _conversion; + using System.Buffers; + using System.Buffers.Binary; + using System.Buffers.Text; + using System.Globalization; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + using Nethermind.Int256; - public ULongConverter() - : this(NumberConversion.Hex) + public class ULongConverter : JsonConverter { - } - - public ULongConverter(NumberConversion conversion) - { - _conversion = conversion; - } - - public override void WriteJson(JsonWriter writer, ulong value, JsonSerializer serializer) - { - switch (_conversion.GetFinalConversion()) + public static ulong FromString(ReadOnlySpan s) { - case NumberConversion.Hex: - writer.WriteValue(value.ToHexString(true)); - break; - case NumberConversion.Decimal: - writer.WriteValue(value == 0 ? "0" : value.ToString()); - break; - case NumberConversion.Raw: - writer.WriteValue(value); - break; - default: - throw new NotSupportedException(); - } - } + if (s.Length == 0) + { + throw new JsonException("null cannot be assigned to long"); + } - public override ulong ReadJson(JsonReader reader, Type objectType, ulong existingValue, bool hasExistingValue, JsonSerializer serializer) - { - return reader.Value is ulong || reader.Value is int - ? (ulong)reader.Value - : FromString(reader.Value?.ToString()); - } + if (s.SequenceEqual("0x0"u8)) + { + return 0uL; + } - public static ulong FromString(string s) - { - if (s is null) - { - throw new JsonException("null cannot be assigned to long"); - } + ulong value; + if (s.StartsWith("0x"u8)) + { + s = s.Slice(2); + if (Utf8Parser.TryParse(s, out value, out _, 'x')) + { + return value; + } + } + else if (Utf8Parser.TryParse(s, out value, out _)) + { + return value; + } - if (s == "0x0") - { - return 0UL; + throw new JsonException("hex to long"); } - if (s.StartsWith("0x0")) + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + ulong value, + JsonSerializerOptions options) { - return ulong.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + switch (usedConversion) + { + case NumberConversion.Hex: + { + if (value == 0) + { + writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); + } + else + { + Span bytes = stackalloc byte[8]; + BinaryPrimitives.WriteUInt64BigEndian(bytes, value); + ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); + } + break; + } + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); + } } - if (s.StartsWith("0x")) + public override ulong Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - Span withZero = new(new char[s.Length - 1]); - withZero[0] = '0'; - s.AsSpan(2).CopyTo(withZero.Slice(1)); - return ulong.Parse(withZero, NumberStyles.AllowHexSpecifier); - } + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetUInt64(); + } + if (reader.TokenType == JsonTokenType.String) + { + if (!reader.HasValueSequence) + { + return FromString(reader.ValueSpan); + } + else + { + return FromString(reader.ValueSequence.ToArray()); + } + } - return ulong.Parse(s, NumberStyles.Integer); + throw new JsonException(); + } } } diff --git a/src/Nethermind/Nethermind.Sockets.Test/WebSocketExtensionsTests.cs b/src/Nethermind/Nethermind.Sockets.Test/WebSocketExtensionsTests.cs index 3fc100f4d26..bfdf265df78 100644 --- a/src/Nethermind/Nethermind.Sockets.Test/WebSocketExtensionsTests.cs +++ b/src/Nethermind/Nethermind.Sockets.Test/WebSocketExtensionsTests.cs @@ -105,6 +105,13 @@ public async Task Can_receive_whole_message() await webSocketsClient.Received().ProcessAsync(Arg.Is>(ba => ba.Count == 2 * 4096 + 1024)); } + class Disposable : IDisposable + { + public void Dispose() + { + } + } + [Test] public async Task Updates_Metrics_And_Stats_Successfully() { @@ -116,14 +123,14 @@ public async Task Updates_Metrics_And_Stats_Successfully() var processor = Substitute.For(); processor.ProcessAsync(default, default).ReturnsForAnyArgs((x) => new List() { - JsonRpcResult.Single(new JsonRpcResponse(), new RpcReport()), - JsonRpcResult.Collection(new JsonRpcBatchResult((e, c) => + (JsonRpcResult.Single((new JsonRpcResponse()), new RpcReport())), + (JsonRpcResult.Collection(new JsonRpcBatchResult((e, c) => new List() { new(new JsonRpcResponse(), new RpcReport()), new(new JsonRpcResponse(), new RpcReport()), new(new JsonRpcResponse(), new RpcReport()), - }.ToAsyncEnumerable().GetAsyncEnumerator(c))) + }.ToAsyncEnumerable().GetAsyncEnumerator(c)))) }.ToAsyncEnumerable()); var service = Substitute.For(); diff --git a/src/Nethermind/Nethermind.Sockets/SocketClient.cs b/src/Nethermind/Nethermind.Sockets/SocketClient.cs index 76770815dd5..e3ad031ff56 100644 --- a/src/Nethermind/Nethermind.Sockets/SocketClient.cs +++ b/src/Nethermind/Nethermind.Sockets/SocketClient.cs @@ -28,25 +28,18 @@ public SocketClient(string clientName, ISocketHandler handler, IJsonSerializer j public virtual Task ProcessAsync(ArraySegment data) => Task.CompletedTask; - public virtual Task SendAsync(SocketsMessage message) + public virtual async Task SendAsync(SocketsMessage message) { if (message is null) { - return Task.CompletedTask; + return; } if (message.Client == ClientName || string.IsNullOrWhiteSpace(ClientName) || string.IsNullOrWhiteSpace(message.Client)) { - using MemoryStream memoryStream = new(); - _jsonSerializer.Serialize(memoryStream, new { type = message.Type, client = ClientName, data = message.Data }); - if (memoryStream.TryGetBuffer(out ArraySegment data)) - { - return _handler.SendRawAsync(data); - } + await _jsonSerializer.SerializeAsync(_handler.SendUsingStream(), new { type = message.Type, client = ClientName, data = message.Data }, indented: false, leaveOpen: true); } - - return Task.CompletedTask; } public async Task ReceiveAsync() diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index 070c358b3ba..163834c1a40 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -13,7 +13,10 @@ using Nethermind.Int256; using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle.Json; -using Newtonsoft.Json.Linq; + +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Globalization; namespace Nethermind.Specs.ChainSpecStyle; @@ -27,8 +30,6 @@ public class ChainSpecLoader : IChainSpecLoader public ChainSpecLoader(IJsonSerializer serializer) { _serializer = serializer; - _serializer.RegisterConverter(new StepDurationJsonConverter()); - _serializer.RegisterConverter(new BlockRewardJsonConverter()); } public ChainSpec Load(byte[] data) => Load(Encoding.UTF8.GetString(data)); @@ -61,11 +62,11 @@ public ChainSpec Load(string jsonData) private void LoadParameters(ChainSpecJson chainSpecJson, ChainSpec chainSpec) { - long? GetTransitions(string builtInName, Predicate> predicate) + long? GetTransitions(string builtInName, Predicate> predicate) { var allocation = chainSpecJson.Accounts?.Values.FirstOrDefault(v => v.BuiltIn?.Name.Equals(builtInName, StringComparison.InvariantCultureIgnoreCase) == true); if (allocation is null) return null; - KeyValuePair[] pricing = allocation.BuiltIn.Pricing.Where(o => predicate(o)).ToArray(); + KeyValuePair[] pricing = allocation.BuiltIn.Pricing.Where(o => predicate(o)).ToArray(); if (pricing.Length > 0) { string key = pricing[0].Key; @@ -77,13 +78,21 @@ private void LoadParameters(ChainSpecJson chainSpecJson, ChainSpec chainSpec) long? GetTransitionForExpectedPricing(string builtInName, string innerPath, long expectedValue) { - bool GetForExpectedPricing(KeyValuePair o) => o.Value.SelectToken(innerPath)?.Value() == expectedValue; + bool GetForExpectedPricing(KeyValuePair o) + { + return o.Value.TryGetSubProperty(innerPath, out JsonElement value) ? value.GetInt64() == expectedValue : false; + } + return GetTransitions(builtInName, GetForExpectedPricing); } long? GetTransitionIfInnerPathExists(string builtInName, string innerPath) { - bool GetForInnerPathExistence(KeyValuePair o) => o.Value.SelectToken(innerPath) is not null; + bool GetForInnerPathExistence(KeyValuePair o) + { + return o.Value.TryGetSubProperty(innerPath, out _); + } + return GetTransitions(builtInName, GetForInnerPathExistence); } @@ -323,7 +332,11 @@ static AuRaParameters.Validator LoadValidator(ChainSpecJson.AuRaValidatorJson va { foreach (KeyValuePair reward in chainSpecJson.Engine.Ethash.DifficultyBombDelays) { - chainSpec.Ethash.DifficultyBombDelays.Add(LongConverter.FromString(reward.Key), reward.Value); + long key = reward.Key.StartsWith("0x") ? + long.Parse(reward.Key.AsSpan(2), NumberStyles.HexNumber) : + long.Parse(reward.Key); + + chainSpec.Ethash.DifficultyBombDelays.Add(key, reward.Value); } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BlockRewardJsonConverter.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BlockRewardJsonConverter.cs index 775f7590c9c..e87fb6a2bc9 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BlockRewardJsonConverter.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BlockRewardJsonConverter.cs @@ -2,38 +2,63 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using Nethermind.Int256; + +using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Serialization.Json; -using Newtonsoft.Json; +using System.Buffers; namespace Nethermind.Specs.ChainSpecStyle.Json { internal class BlockRewardJsonConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, ChainSpecJson.BlockRewardJson value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, ChainSpecJson.BlockRewardJson value, JsonSerializerOptions options) { - throw new NotImplementedException(); + throw new NotSupportedException(); } - public override ChainSpecJson.BlockRewardJson ReadJson(JsonReader reader, Type objectType, ChainSpecJson.BlockRewardJson existingValue, bool hasExistingValue, JsonSerializer serializer) + public override ChainSpecJson.BlockRewardJson Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - existingValue ??= new ChainSpecJson.BlockRewardJson(); - if (reader.TokenType == JsonToken.String || reader.TokenType == JsonToken.Integer) + var value = new ChainSpecJson.BlockRewardJson(); + if (reader.TokenType == JsonTokenType.String) { - var blockReward = serializer.Deserialize(reader); - existingValue.Add(0, blockReward); + var blockReward = JsonSerializer.Deserialize(ref reader, options); + value.Add(0, blockReward); } - else + else if (reader.TokenType == JsonTokenType.Number) { - var blockRewards = serializer.Deserialize>(reader); - foreach (var blockReward in blockRewards ?? throw new ArgumentException("Cannot deserialize BlockReward.")) + value.Add(0, new UInt256(reader.GetUInt64())); + } + else if (reader.TokenType == JsonTokenType.StartObject) + { + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) { - existingValue.Add(LongConverter.FromString(blockReward.Key), blockReward.Value); + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new ArgumentException("Cannot deserialize BlockReward."); + } + var property = UInt256Converter.Read(reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan); + var key = (long)property; + reader.Read(); + if (reader.TokenType != JsonTokenType.String) + { + throw new ArgumentException("Cannot deserialize BlockReward."); + } + + var blockReward = UInt256Converter.Read(reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan); + value.Add(key, blockReward); + + reader.Read(); } } + else + { + throw new ArgumentException("Cannot deserialize BlockReward."); + } - return existingValue; + return value; } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BuiltInJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BuiltInJson.cs index 9b1b5816d87..aa4ccd3a6a3 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BuiltInJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/BuiltInJson.cs @@ -2,13 +2,15 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using Newtonsoft.Json.Linq; + +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Specs.ChainSpecStyle.Json { internal class BuiltInJson { public string Name { get; set; } - public Dictionary Pricing { get; set; } + public Dictionary Pricing { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs index 31c9bc77071..4bf9b38fe2f 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecJson.cs @@ -3,192 +3,201 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Int256; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -namespace Nethermind.Specs.ChainSpecStyle.Json; - -internal class ChainSpecJson +namespace Nethermind.Specs.ChainSpecStyle.Json { - public string Name { get; set; } - public string DataDir { get; set; } - public EngineJson Engine { get; set; } - public ChainSpecParamsJson Params { get; set; } - public ChainSpecGenesisJson Genesis { get; set; } - public string[] Nodes { get; set; } - public Dictionary Accounts { get; set; } - - internal class EthashEngineJson - { - public long? HomesteadTransition => Params?.HomesteadTransition; - public long? DaoHardforkTransition => Params?.DaoHardforkTransition; - public Address DaoHardforkBeneficiary => Params?.DaoHardforkBeneficiary; - public Address[] DaoHardforkAccounts => Params?.DaoHardforkAccounts; - public long? Eip100bTransition => Params?.Eip100bTransition; - public long? FixedDifficulty => Params?.FixedDifficulty; - public long? DifficultyBoundDivisor => Params?.DifficultyBoundDivisor; - public long? DurationLimit => Params?.DurationLimit; - public UInt256? MinimumDifficulty => Params?.MinimumDifficulty; - public IDictionary BlockReward => Params?.BlockReward; - public IDictionary DifficultyBombDelays => Params?.DifficultyBombDelays; - public EthashEngineParamsJson Params { get; set; } - } - - internal class EthashEngineParamsJson + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] + [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + internal class ChainSpecJson { - public UInt256? MinimumDifficulty { get; set; } - public long? DifficultyBoundDivisor { get; set; } - public long? DurationLimit { get; set; } - public long HomesteadTransition { get; set; } - public long? DaoHardforkTransition { get; set; } - public Address DaoHardforkBeneficiary { get; set; } - public Address[] DaoHardforkAccounts { get; set; } - public long Eip100bTransition { get; set; } - public long? FixedDifficulty { get; set; } - public BlockRewardJson BlockReward { get; set; } - public Dictionary DifficultyBombDelays { get; set; } - } + public string Name { get; set; } + public string DataDir { get; set; } + public EngineJson Engine { get; set; } + public ChainSpecParamsJson Params { get; set; } + [JsonPropertyName("genesis")] + public ChainSpecGenesisJson Genesis { get; set; } + public string[] Nodes { get; set; } + [JsonPropertyName("accounts")] + public Dictionary Accounts { get; set; } + + internal class EthashEngineJson + { + public long? HomesteadTransition => Params?.HomesteadTransition; + public long? DaoHardforkTransition => Params?.DaoHardforkTransition; + public Address DaoHardforkBeneficiary => Params?.DaoHardforkBeneficiary; + public Address[] DaoHardforkAccounts => Params?.DaoHardforkAccounts; + public long? Eip100bTransition => Params?.Eip100bTransition; + public long? FixedDifficulty => Params?.FixedDifficulty; + public long? DifficultyBoundDivisor => Params?.DifficultyBoundDivisor; + public long? DurationLimit => Params?.DurationLimit; + public UInt256? MinimumDifficulty => Params?.MinimumDifficulty; + public IDictionary BlockReward => Params?.BlockReward; + public IDictionary DifficultyBombDelays => Params?.DifficultyBombDelays; + public EthashEngineParamsJson Params { get; set; } + } - internal class CliqueEngineJson - { - public ulong Period => Params.Period; - public ulong Epoch => Params.Epoch; - public UInt256? BlockReward => Params.BlockReward; - public CliqueEngineParamsJson Params { get; set; } - } + internal class EthashEngineParamsJson + { + public UInt256? MinimumDifficulty { get; set; } + public long? DifficultyBoundDivisor { get; set; } + public long? DurationLimit { get; set; } + public long HomesteadTransition { get; set; } + public long? DaoHardforkTransition { get; set; } + public Address DaoHardforkBeneficiary { get; set; } + public Address[] DaoHardforkAccounts { get; set; } + public long Eip100bTransition { get; set; } + public long? FixedDifficulty { get; set; } + public BlockRewardJson BlockReward { get; set; } + public Dictionary DifficultyBombDelays { get; set; } + } - internal class CliqueEngineParamsJson - { - public ulong Period { get; set; } - public ulong Epoch { get; set; } - public UInt256? BlockReward { get; set; } - } + internal class CliqueEngineJson + { + public ulong Period => Params.Period; + public ulong Epoch => Params.Epoch; + public UInt256? BlockReward => Params.BlockReward; + public CliqueEngineParamsJson Params { get; set; } + } - internal class AuraEngineParamsJson - { - public StepDurationJson StepDuration { get; set; } - public BlockRewardJson BlockReward { get; set; } - public long MaximumUncleCountTransition { get; set; } - public long? MaximumUncleCount { get; set; } - public Address BlockRewardContractAddress { get; set; } - public long? BlockRewardContractTransition { get; set; } - public IDictionary BlockRewardContractTransitions { get; set; } = new Dictionary(); - public long ValidateScoreTransition { get; set; } - public long ValidateStepTransition { get; set; } - public AuRaValidatorJson Validators { get; set; } - public IDictionary RandomnessContractAddress { get; set; } = new Dictionary(); - public IDictionary BlockGasLimitContractTransitions { get; set; } = new Dictionary(); - public long? TwoThirdsMajorityTransition { get; set; } - public long? PosdaoTransition { get; set; } - public IDictionary> RewriteBytecode { get; set; } = new Dictionary>(); - public Address WithdrawalContractAddress { get; set; } - - public class StepDurationJson : SortedDictionary { } - } + internal class CliqueEngineParamsJson + { + public ulong Period { get; set; } + public ulong Epoch { get; set; } + public UInt256? BlockReward { get; set; } + } - public class BlockRewardJson : SortedDictionary { } + internal class AuraEngineParamsJson + { + public StepDurationJson StepDuration { get; set; } + public BlockRewardJson BlockReward { get; set; } + public long MaximumUncleCountTransition { get; set; } + public long? MaximumUncleCount { get; set; } + public Address BlockRewardContractAddress { get; set; } + public long? BlockRewardContractTransition { get; set; } + public IDictionary BlockRewardContractTransitions { get; set; } = new Dictionary(); + public long ValidateScoreTransition { get; set; } + public long ValidateStepTransition { get; set; } + public AuRaValidatorJson Validators { get; set; } + public IDictionary RandomnessContractAddress { get; set; } = new Dictionary(); + public IDictionary BlockGasLimitContractTransitions { get; set; } = new Dictionary(); + public long? TwoThirdsMajorityTransition { get; set; } + public long? PosdaoTransition { get; set; } + public IDictionary> RewriteBytecode { get; set; } = new Dictionary>(); + public Address WithdrawalContractAddress { get; set; } + + [JsonConverter(typeof(StepDurationJsonConverter))] + public class StepDurationJson : SortedDictionary { } + } - internal class AuRaValidatorJson - { - public Address[] List { get; set; } - public Address Contract { get; set; } - public Address SafeContract { get; set; } - public Dictionary Multi { get; set; } + [JsonConverter(typeof(BlockRewardJsonConverter))] + public class BlockRewardJson : SortedDictionary { } - public AuRaParameters.ValidatorType GetValidatorType() + internal class AuRaValidatorJson { - if (List is not null) - { - return AuRaParameters.ValidatorType.List; - } - else if (Contract is not null) - { - return AuRaParameters.ValidatorType.ReportingContract; - } - else if (SafeContract is not null) - { - return AuRaParameters.ValidatorType.Contract; - } - else if (Multi is not null) - { - return AuRaParameters.ValidatorType.Multi; - } - else + public Address[] List { get; set; } + public Address Contract { get; set; } + public Address SafeContract { get; set; } + public Dictionary Multi { get; set; } + + public AuRaParameters.ValidatorType GetValidatorType() { - throw new NotSupportedException("AuRa validator type not supported."); + if (List is not null) + { + return AuRaParameters.ValidatorType.List; + } + else if (Contract is not null) + { + return AuRaParameters.ValidatorType.ReportingContract; + } + else if (SafeContract is not null) + { + return AuRaParameters.ValidatorType.Contract; + } + else if (Multi is not null) + { + return AuRaParameters.ValidatorType.Multi; + } + else + { + throw new NotSupportedException("AuRa validator type not supported."); + } } } - } - internal class AuraEngineJson - { - public IDictionary StepDuration => Params.StepDuration; + internal class AuraEngineJson + { + public IDictionary StepDuration => Params.StepDuration; - public IDictionary BlockReward => Params.BlockReward; + public IDictionary BlockReward => Params.BlockReward; - public long MaximumUncleCountTransition => Params.MaximumUncleCountTransition; + public long MaximumUncleCountTransition => Params.MaximumUncleCountTransition; - public long? MaximumUncleCount => Params.MaximumUncleCount; + public long? MaximumUncleCount => Params.MaximumUncleCount; - public Address BlockRewardContractAddress => Params.BlockRewardContractAddress; + public Address BlockRewardContractAddress => Params.BlockRewardContractAddress; - public long? BlockRewardContractTransition => Params.BlockRewardContractTransition; + public long? BlockRewardContractTransition => Params.BlockRewardContractTransition; - public IDictionary BlockRewardContractTransitions => Params.BlockRewardContractTransitions; + public IDictionary BlockRewardContractTransitions => Params.BlockRewardContractTransitions; - public long ValidateScoreTransition => Params.ValidateScoreTransition; + public long ValidateScoreTransition => Params.ValidateScoreTransition; - public long ValidateStepTransition => Params.ValidateStepTransition; + public long ValidateStepTransition => Params.ValidateStepTransition; - public long? PosdaoTransition => Params.PosdaoTransition; + public long? PosdaoTransition => Params.PosdaoTransition; - public long? TwoThirdsMajorityTransition => Params.TwoThirdsMajorityTransition; + public long? TwoThirdsMajorityTransition => Params.TwoThirdsMajorityTransition; - public AuRaValidatorJson Validator => Params.Validators; + public AuRaValidatorJson Validator => Params.Validators; - public IDictionary RandomnessContractAddress => Params.RandomnessContractAddress; + public IDictionary RandomnessContractAddress => Params.RandomnessContractAddress; - public IDictionary BlockGasLimitContractTransitions => Params.BlockGasLimitContractTransitions; + public IDictionary BlockGasLimitContractTransitions => Params.BlockGasLimitContractTransitions; - public IDictionary> RewriteBytecode => Params.RewriteBytecode; + public IDictionary> RewriteBytecode => Params.RewriteBytecode; - public Address WithdrawalContractAddress => Params.WithdrawalContractAddress; + public Address WithdrawalContractAddress => Params.WithdrawalContractAddress; - public AuraEngineParamsJson Params { get; set; } - } + public AuraEngineParamsJson Params { get; set; } + } - internal class OptimismEngineJson - { - public ulong RegolithTimestamp => Params.RegolithTimestamp; - public long BedrockBlockNumber => Params.BedrockBlockNumber; - public Address L1FeeRecipient => Params.L1FeeRecipient; - public Address L1BlockAddress => Params.L1BlockAddress; - public OptimismEngineParamsJson Params { get; set; } - } + internal class OptimismEngineJson + { + public ulong RegolithTimestamp => Params.RegolithTimestamp; + public long BedrockBlockNumber => Params.BedrockBlockNumber; + public Address L1FeeRecipient => Params.L1FeeRecipient; + public Address L1BlockAddress => Params.L1BlockAddress; + public OptimismEngineParamsJson Params { get; set; } + } - internal class OptimismEngineParamsJson - { - public ulong RegolithTimestamp { get; set; } - public long BedrockBlockNumber { get; set; } - public Address L1FeeRecipient { get; set; } - public Address L1BlockAddress { get; set; } - } + internal class OptimismEngineParamsJson + { + public ulong RegolithTimestamp { get; set; } + public long BedrockBlockNumber { get; set; } + public Address L1FeeRecipient { get; set; } + public Address L1BlockAddress { get; set; } + } - internal class NethDevJson - { - } + internal class NethDevJson + { + } - internal class EngineJson - { - public EthashEngineJson Ethash { get; set; } - public CliqueEngineJson Clique { get; set; } - public AuraEngineJson AuthorityRound { get; set; } - public OptimismEngineJson Optimism { get; set; } - public NethDevJson NethDev { get; set; } - - [JsonExtensionData] - public IDictionary CustomEngineData { get; set; } + internal class EngineJson + { + public EthashEngineJson Ethash { get; set; } + public CliqueEngineJson Clique { get; set; } + public AuraEngineJson AuthorityRound { get; set; } + public OptimismEngineJson Optimism { get; set; } + public NethDevJson NethDev { get; set; } + + [JsonExtensionData] + public Dictionary CustomEngineData { get; set; } + } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs index 57f6007a6db..7677c9e84bc 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -1,11 +1,13 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; -using Newtonsoft.Json; [assembly: InternalsVisibleTo("Nethermind.Specs.Test")] namespace Nethermind.Specs.ChainSpecStyle.Json; @@ -15,7 +17,7 @@ internal class ChainSpecParamsJson public ulong? ChainId { get; set; } public ulong? NetworkId { get; set; } - [JsonProperty(PropertyName = "registrar")] + [JsonPropertyName("registrar")] public Address EnsRegistrar { get; set; } public long? GasLimitBoundDivisor { get; set; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/StepDurationJsonConverter.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/StepDurationJsonConverter.cs index 16c84a37cea..4d82b30b88e 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/StepDurationJsonConverter.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/StepDurationJsonConverter.cs @@ -2,37 +2,63 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using Nethermind.Serialization.Json; -using Newtonsoft.Json; + +using System.Text.Json; +using System.Text.Json.Serialization; namespace Nethermind.Specs.ChainSpecStyle.Json { internal class StepDurationJsonConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, ChainSpecJson.AuraEngineParamsJson.StepDurationJson value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, ChainSpecJson.AuraEngineParamsJson.StepDurationJson value, JsonSerializerOptions options) { throw new NotSupportedException(); } - public override ChainSpecJson.AuraEngineParamsJson.StepDurationJson ReadJson(JsonReader reader, Type objectType, ChainSpecJson.AuraEngineParamsJson.StepDurationJson existingValue, bool hasExistingValue, JsonSerializer serializer) + public override ChainSpecJson.AuraEngineParamsJson.StepDurationJson Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - existingValue ??= new ChainSpecJson.AuraEngineParamsJson.StepDurationJson(); - if (reader.TokenType == JsonToken.String || reader.TokenType == JsonToken.Integer) + var value = new ChainSpecJson.AuraEngineParamsJson.StepDurationJson(); + if (reader.TokenType == JsonTokenType.String) { - var stepDuration = serializer.Deserialize(reader); - existingValue.Add(0, stepDuration); + value.Add(0, JsonSerializer.Deserialize(ref reader, options)); } - else + else if (reader.TokenType == JsonTokenType.Number) + { + value.Add(0, reader.GetInt64()); + } + else if (reader.TokenType == JsonTokenType.StartObject) { - var stepDurations = serializer.Deserialize>(reader); - foreach (var stepDuration in stepDurations ?? throw new ArgumentException("Cannot deserialize StepDuration.")) + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) { - existingValue.Add(LongConverter.FromString(stepDuration.Key), stepDuration.Value); + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new ArgumentException("Cannot deserialize BlockReward."); + } + var key = long.Parse(reader.GetString()); + reader.Read(); + if (reader.TokenType == JsonTokenType.String) + { + value.Add(key, long.Parse(reader.GetString())); + } + else if (reader.TokenType == JsonTokenType.Number) + { + value.Add(key, reader.GetInt64()); + } + else + { + throw new ArgumentException("Cannot deserialize BlockReward."); + } + + reader.Read(); } } + else + { + throw new ArgumentException("Cannot deserialize BlockReward."); + } - return existingValue; + return value; } } } diff --git a/src/Nethermind/Nethermind.State/Proofs/AccountProof.cs b/src/Nethermind/Nethermind.State/Proofs/AccountProof.cs index c7e419484fc..e1975c83aaa 100644 --- a/src/Nethermind/Nethermind.State/Proofs/AccountProof.cs +++ b/src/Nethermind/Nethermind.State/Proofs/AccountProof.cs @@ -1,6 +1,10 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; @@ -10,6 +14,8 @@ namespace Nethermind.State.Proofs /// /// EIP-1186 style account proof /// + /// + [JsonConverter(typeof(ProofJsonConverter))] public class AccountProof { public Address? Address { get; set; } @@ -26,4 +32,86 @@ public class AccountProof public StorageProof[]? StorageProofs { get; set; } } + + /// + ///{ + /// "id": 1, + /// "jsonrpc": "2.0", + /// "method": "eth_getProof", + /// "params": [ + /// "0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842", + /// [ "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" ], + /// "latest" + /// ] + ///} + /// + ///{ + /// "id": 1, + /// "jsonrpc": "2.0", + /// "result": { + /// "accountProof": [ + /// "0xf90211a...0701bc80", + /// "0xf90211a...0d832380", + /// "0xf90211a...5fb20c80", + /// "0xf90211a...0675b80", + /// "0xf90151a0...ca08080" + /// ], + /// "balance": "0x0", + /// "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + /// "nonce": "0x0", + /// "storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + /// "storageProof": [ + /// { + /// "key": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + /// "proof": [ + /// "0xf90211a...0701bc80", + /// "0xf90211a...0d832380" + /// ], + /// "value": "0x1" + /// } + /// ] + /// } + ///} + /// + public class ProofJsonConverter : JsonConverter + { + public override AccountProof Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) => throw new NotSupportedException(); + + public override void Write( + Utf8JsonWriter writer, + AccountProof value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + writer.WritePropertyName("accountProof"u8); + JsonSerializer.Serialize(writer, value.Proof, options); + + JsonConverter
addressConverter = (JsonConverter
)options.GetConverter(typeof(Address)); + writer.WritePropertyName("address"u8); + addressConverter.Write(writer, value.Address, options); + + JsonConverter uint256Converter = (JsonConverter)options.GetConverter(typeof(UInt256)); + writer.WritePropertyName("balance"u8); + uint256Converter.Write(writer, value.Balance, options); + + JsonConverter hashConverter = (JsonConverter)options.GetConverter(typeof(Hash256)); + writer.WritePropertyName("codeHash"u8); + hashConverter.Write(writer, value.CodeHash, options); + + writer.WritePropertyName("nonce"u8); + uint256Converter.Write(writer, value.Nonce, options); + + writer.WritePropertyName("storageHash"u8); + hashConverter.Write(writer, value.StorageRoot, options); + + writer.WritePropertyName("storageProof"u8); + JsonSerializer.Serialize(writer, value.StorageProofs, options); + + writer.WriteEndObject(); + } + } } diff --git a/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs b/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs index 037f1366508..62b18eff0bb 100644 --- a/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs +++ b/src/Nethermind/Nethermind.State/Proofs/StorageProof.cs @@ -8,8 +8,8 @@ namespace Nethermind.State.Proofs /// public class StorageProof { - public byte[][]? Proof { get; set; } public byte[]? Key { get; set; } + public byte[][]? Proof { get; set; } public byte[]? Value { get; set; } } } diff --git a/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj b/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj index 48497e9c498..886299977d8 100644 --- a/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj +++ b/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj @@ -8,7 +8,6 @@ - diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceResult.cs b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceResult.cs index 70aaff6a89a..29c91131420 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceResult.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceResult.cs @@ -1,22 +1,22 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Nethermind.Test.Runner { public class StateTestTxTraceResult { - [JsonProperty("output")] + [JsonPropertyName("output")] public byte[] Output { get; set; } - [JsonProperty("gasUsed")] + [JsonPropertyName("gasUsed")] public long GasUsed { get; set; } - [JsonProperty("time")] + [JsonPropertyName("time")] public int Time { get; set; } - [JsonProperty("error")] + [JsonPropertyName("error")] public string Error { get; set; } } } diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceState.cs b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceState.cs index c1f22b50c6a..3aed7ced1b5 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceState.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTraceState.cs @@ -1,14 +1,15 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; + using Nethermind.Core.Crypto; -using Newtonsoft.Json; namespace Nethermind.Test.Runner { public class StateTestTxTraceState { - [JsonProperty("stateRoot")] + [JsonPropertyName("stateRoot")] public Hash256 StateRoot { get; set; } } } diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTxTraceEntry.cs b/src/Nethermind/Nethermind.Test.Runner/StateTxTraceEntry.cs index 50a949f3768..4b384c06e5a 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTxTraceEntry.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTxTraceEntry.cs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Nethermind.Test.Runner { @@ -13,37 +13,37 @@ public StateTestTxTraceEntry() Stack = new List(); } - [JsonProperty(PropertyName = "pc")] + [JsonPropertyName("pc")] public int Pc { get; set; } - [JsonProperty(PropertyName = "op")] + [JsonPropertyName("op")] public byte Operation { get; set; } - [JsonProperty(PropertyName = "gas")] + [JsonPropertyName("gas")] public long Gas { get; set; } - [JsonProperty(PropertyName = "gasCost")] + [JsonPropertyName("gasCost")] public long GasCost { get; set; } - [JsonProperty(PropertyName = "memory")] + [JsonPropertyName("memory")] public string Memory { get; set; } - [JsonProperty(PropertyName = "memSize")] + [JsonPropertyName("memSize")] public int MemSize { get; set; } - [JsonProperty(PropertyName = "stack")] + [JsonPropertyName("stack")] public List Stack { get; set; } - [JsonProperty(PropertyName = "depth")] + [JsonPropertyName("depth")] public int Depth { get; set; } - [JsonProperty(PropertyName = "refund")] + [JsonPropertyName("refund")] public int Refund { get; set; } - [JsonProperty(PropertyName = "opname")] + [JsonPropertyName("opname")] public string? OperationName { get; set; } - [JsonProperty(PropertyName = "error")] + [JsonPropertyName("error")] public string? Error { get; set; } = string.Empty; // public Dictionary Storage { get; set; }