diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs index 86c7706fe3f..ee7cc539f26 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs @@ -125,7 +125,7 @@ public FilterBuilder WithAddress(Address address) public FilterBuilder WithAddresses(params Address[] addresses) { - _address = new AddressFilter(addresses.Select(static a => new AddressAsKey(a)).ToHashSet()); + _address = new AddressFilter(addresses.Select(static a => new AddressAsKey(a))); return this; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs index cbedcef98a5..69d73ec8c18 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs @@ -73,7 +73,7 @@ public void Accepts_any_address_by_ref() [Test] public void Accepts_any_address_when_set_is_empty() { - HashSet addresses = new(); + AddressAsKey[] addresses = []; AddressFilter filter = new AddressFilter(addresses); filter.Accepts(TestItem.AddressA).Should().BeTrue(); @@ -84,7 +84,7 @@ public void Accepts_any_address_when_set_is_empty() [Test] public void Accepts_any_address_when_set_is_empty_by_ref() { - HashSet addresses = new(); + AddressAsKey[] addresses = []; AddressFilter filter = new AddressFilter(addresses); AddressStructRef addressARef = TestItem.AddressA.ToStructRef(); @@ -98,10 +98,10 @@ public void Accepts_any_address_when_set_is_empty_by_ref() [Test] public void Accepts_only_addresses_in_a_set() { - HashSet addresses = new() - { + AddressAsKey[] addresses = + [ TestItem.AddressA, TestItem.AddressC - }; + ]; AddressFilter filter = new AddressFilter(addresses); filter.Accepts(TestItem.AddressA).Should().BeTrue(); @@ -112,10 +112,10 @@ public void Accepts_only_addresses_in_a_set() [Test] public void Accepts_only_addresses_in_a_set_by_ref() { - HashSet addresses = new() - { + AddressAsKey[] addresses = + [ TestItem.AddressA, TestItem.AddressC - }; + ]; AddressFilter filter = new AddressFilter(addresses); AddressStructRef addressARef = TestItem.AddressA.ToStructRef(); @@ -187,7 +187,7 @@ public void Matches_bloom_using_any_address_by_ref() [Test] public void Matches_any_bloom_when_set_is_empty() { - HashSet addresses = new(); + AddressAsKey[] addresses = []; AddressFilter filter = new AddressFilter(addresses); filter.Matches(BloomFromAddress(TestItem.AddressA)).Should().BeTrue(); @@ -198,7 +198,7 @@ public void Matches_any_bloom_when_set_is_empty() [Test] public void Matches_any_bloom_when_set_is_empty_by_ref() { - HashSet addresses = new(); + AddressAsKey[] addresses = []; AddressFilter filter = new AddressFilter(addresses); BloomStructRef bloomARef = BloomFromAddress(TestItem.AddressA).ToStructRef(); @@ -212,7 +212,7 @@ public void Matches_any_bloom_when_set_is_empty_by_ref() [Test] public void Matches_any_bloom_when_set_is_forced_null() { - AddressFilter filter = new AddressFilter(addresses: null!); + AddressFilter filter = new AddressFilter([]); filter.Matches(BloomFromAddress(TestItem.AddressA)).Should().BeTrue(); filter.Matches(BloomFromAddress(TestItem.AddressB)).Should().BeTrue(); @@ -222,7 +222,7 @@ public void Matches_any_bloom_when_set_is_forced_null() [Test] public void Matches_any_bloom_when_set_is_forced_null_by_ref() { - AddressFilter filter = new AddressFilter(addresses: null!); + AddressFilter filter = new AddressFilter([]); BloomStructRef bloomARef = BloomFromAddress(TestItem.AddressA).ToStructRef(); BloomStructRef bloomBRef = BloomFromAddress(TestItem.AddressB).ToStructRef(); @@ -235,10 +235,10 @@ public void Matches_any_bloom_when_set_is_forced_null_by_ref() [Test] public void Matches_any_bloom_using_addresses_set() { - HashSet addresses = new() - { + AddressAsKey[] addresses = + [ TestItem.AddressA, TestItem.AddressC - }; + ]; AddressFilter filter = new AddressFilter(addresses); filter.Matches(BloomFromAddress(TestItem.AddressA)).Should().BeTrue(); @@ -249,10 +249,10 @@ public void Matches_any_bloom_using_addresses_set() [Test] public void Matches_any_bloom_using_addresses_set_by_ref() { - HashSet addresses = new() - { + AddressAsKey[] addresses = + [ TestItem.AddressA, TestItem.AddressC - }; + ]; AddressFilter filter = new AddressFilter(addresses); BloomStructRef bloomARef = BloomFromAddress(TestItem.AddressA).ToStructRef(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs index 20d90ed8c3f..51e67845edd 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs @@ -11,6 +11,7 @@ using Nethermind.Blockchain.Filters.Topics; using Nethermind.Blockchain.Find; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; using Nethermind.Core.Timers; using NSubstitute; @@ -102,14 +103,14 @@ public static IEnumerable CorrectlyCreatesAddressFilterTestCases get { yield return new TestCaseData(null, AddressFilter.AnyAddress); - yield return new TestCaseData(TestItem.AddressA.ToString(), new AddressFilter(TestItem.AddressA)); - yield return new TestCaseData(new[] { TestItem.AddressA.ToString(), TestItem.AddressB.ToString() }, - new AddressFilter(new HashSet() { TestItem.AddressA, TestItem.AddressB })); + yield return new TestCaseData(new[] { new AddressAsKey(TestItem.AddressA) }, new AddressFilter(TestItem.AddressA)); + yield return new TestCaseData(new[] { new AddressAsKey(TestItem.AddressA), new AddressAsKey(TestItem.AddressB) }, + new AddressFilter([TestItem.AddressA, TestItem.AddressB])); } } [TestCaseSource(nameof(CorrectlyCreatesAddressFilterTestCases))] - public void Correctly_creates_address_filter(object address, AddressFilter expected) + public void Correctly_creates_address_filter(AddressAsKey[] address, AddressFilter expected) { BlockParameter from = new(100); BlockParameter to = new(BlockParameterType.Latest); @@ -124,22 +125,25 @@ public static IEnumerable CorrectlyCreatesTopicsFilterTestCases { yield return new TestCaseData(null, SequenceTopicsFilter.AnyTopic); - yield return new TestCaseData(new[] { TestItem.KeccakA.ToString() }, + yield return new TestCaseData(new[] { new[] { TestItem.KeccakA } }, new SequenceTopicsFilter(new SpecificTopic(TestItem.KeccakA))); - yield return new TestCaseData(new[] { TestItem.KeccakA.ToString(), TestItem.KeccakB.ToString() }, + yield return new TestCaseData(new[] { new[] { TestItem.KeccakA }, new[] { TestItem.KeccakB } }, new SequenceTopicsFilter(new SpecificTopic(TestItem.KeccakA), new SpecificTopic(TestItem.KeccakB))); - yield return new TestCaseData(new[] { null, TestItem.KeccakB.ToString() }, + yield return new TestCaseData(new[] { null, new[] { TestItem.KeccakB } }, new SequenceTopicsFilter(AnyTopic.Instance, new SpecificTopic(TestItem.KeccakB))); - yield return new TestCaseData(new object[] { new[] { TestItem.KeccakA.ToString(), TestItem.KeccakB.ToString(), TestItem.KeccakC.ToString() }, TestItem.KeccakD.ToString() }, - new SequenceTopicsFilter(new OrExpression(new SpecificTopic(TestItem.KeccakA), new SpecificTopic(TestItem.KeccakB), new SpecificTopic(TestItem.KeccakC)), new SpecificTopic(TestItem.KeccakD))); + yield return new TestCaseData( + new[] { new[] { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }, new[] { TestItem.KeccakD } }, + new SequenceTopicsFilter( + new OrExpression(new SpecificTopic(TestItem.KeccakA), new SpecificTopic(TestItem.KeccakB), + new SpecificTopic(TestItem.KeccakC)), new SpecificTopic(TestItem.KeccakD))); } } [TestCaseSource(nameof(CorrectlyCreatesTopicsFilterTestCases))] - public void Correctly_creates_topics_filter(IEnumerable topics, TopicsFilter expected) + public void Correctly_creates_topics_filter(Hash256[]?[]? topics, TopicsFilter expected) { BlockParameter from = new(100); BlockParameter to = new(BlockParameterType.Latest); diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index 430ab4cb28b..80305a26023 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -289,21 +289,21 @@ public ulong GetChainId() public IEnumerable GetLogs( BlockParameter fromBlock, BlockParameter toBlock, - object? address = null, - IEnumerable? topics = null, + AddressAsKey[]? addresses = null, + IEnumerable? topics = null, CancellationToken cancellationToken = default) { - LogFilter filter = GetFilter(fromBlock, toBlock, address, topics); + LogFilter filter = GetFilter(fromBlock, toBlock, addresses, topics); return logFinder.FindLogs(filter, cancellationToken); } public LogFilter GetFilter( BlockParameter fromBlock, BlockParameter toBlock, - object? address = null, - IEnumerable? topics = null) + AddressAsKey[]? addresses = null, + IEnumerable? topics = null) { - return filterStore.CreateLogFilter(fromBlock, toBlock, address, topics, false); + return filterStore.CreateLogFilter(fromBlock, toBlock, addresses, topics, false); } public IEnumerable GetLogs( @@ -325,10 +325,10 @@ public bool TryGetLogs(int filterId, out IEnumerable filterLogs, Canc return filter is not null; } - public int NewFilter(BlockParameter? fromBlock, BlockParameter? toBlock, - object? address = null, IEnumerable? topics = null) + public int NewFilter(BlockParameter fromBlock, BlockParameter toBlock, + AddressAsKey[]? address = null, IEnumerable? topics = null) { - LogFilter filter = filterStore.CreateLogFilter(fromBlock ?? BlockParameter.Latest, toBlock ?? BlockParameter.Latest, address, topics); + LogFilter filter = filterStore.CreateLogFilter(fromBlock, toBlock, address, topics); filterStore.SaveFilter(filter); return filter.Id; } diff --git a/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs index 4e71a417b8e..e3d3b76a058 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs @@ -9,39 +9,27 @@ namespace Nethermind.Blockchain.Filters { public class AddressFilter { - public static readonly AddressFilter AnyAddress = new(addresses: new HashSet()); + public static readonly AddressFilter AnyAddress = new([]); private Bloom.BloomExtract[]? _addressesBloomIndexes; - private Bloom.BloomExtract? _addressBloomExtract; - public AddressFilter(Address address) + public AddressFilter(Address address) : this([address]) { - Address = address; } - public AddressFilter(HashSet addresses) + public AddressFilter(IEnumerable addresses) { - Addresses = addresses; + Addresses = addresses.ToHashSet(); } - public Address? Address { get; } - public HashSet? Addresses { get; } + public HashSet Addresses { get; } private Bloom.BloomExtract[] AddressesBloomExtracts => _addressesBloomIndexes ??= CalculateBloomExtracts(); - private Bloom.BloomExtract AddressBloomExtract => _addressBloomExtract ??= Bloom.GetExtract(Address); - public bool Accepts(Address address) - { - if (Addresses?.Count > 0) - { - return Addresses.Contains(address); - } - - return Address is null || Address == address; - } + public bool Accepts(Address address) => Addresses.Count == 0 || Addresses.Contains(address); public bool Accepts(ref AddressStructRef address) { - if (Addresses?.Count > 0) + if (Addresses.Count > 0) { foreach (var a in Addresses) { @@ -51,55 +39,39 @@ public bool Accepts(ref AddressStructRef address) return false; } - return Address is null || Address == address; + return true; } public bool Matches(Bloom bloom) { - if (Addresses is not null) + bool result = true; + var indexes = AddressesBloomExtracts; + for (var i = 0; i < indexes.Length; i++) { - bool result = true; - var indexes = AddressesBloomExtracts; - for (var i = 0; i < indexes.Length; i++) + result = bloom.Matches(indexes[i]); + if (result) { - result = bloom.Matches(indexes[i]); - if (result) - { - break; - } + break; } - - return result; } - if (Address is null) - { - return true; - } - return bloom.Matches(AddressBloomExtract); + + return result; } public bool Matches(ref BloomStructRef bloom) { - if (Addresses is not null) + bool result = true; + var indexes = AddressesBloomExtracts; + for (var i = 0; i < indexes.Length; i++) { - bool result = true; - var indexes = AddressesBloomExtracts; - for (var i = 0; i < indexes.Length; i++) + result = bloom.Matches(indexes[i]); + if (result) { - result = bloom.Matches(indexes[i]); - if (result) - { - break; - } + break; } - - return result; } - if (Address is null) - { - return true; - } - return bloom.Matches(AddressBloomExtract); + + return result; } private Bloom.BloomExtract[] CalculateBloomExtracts() => Addresses.Select(static a => Bloom.GetExtract(a)).ToArray(); diff --git a/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs b/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs index f7b192cb42c..d7603a778b8 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs @@ -124,11 +124,11 @@ public PendingTransactionFilter CreatePendingTransactionFilter(bool setId = true new(GetFilterId(setId)); public LogFilter CreateLogFilter(BlockParameter fromBlock, BlockParameter toBlock, - object? address = null, IEnumerable? topics = null, bool setId = true) => + AddressAsKey[]? addresses = null, IEnumerable? topics = null, bool setId = true) => new(GetFilterId(setId), fromBlock, toBlock, - GetAddress(address), + GetAddress(addresses), GetTopicsFilter(topics)); public void RemoveFilter(int filterId) @@ -165,7 +165,7 @@ private int GetFilterId(bool generateId) return 0; } - private static TopicsFilter GetTopicsFilter(IEnumerable? topics = null) + private static TopicsFilter GetTopicsFilter(IEnumerable? topics = null) { if (topics is null) { @@ -202,48 +202,40 @@ private static TopicExpression GetTopicExpression(FilterTopic? filterTopic) return AnyTopic.Instance; } - private static AddressFilter GetAddress(object? address) => - address switch + private static AddressFilter GetAddress(AddressAsKey[]? addresses) + { + if (addresses is null) { - null => AddressFilter.AnyAddress, - string s => new AddressFilter(new Address(s)), - IEnumerable e => new AddressFilter(e.Select(static a => new AddressAsKey(new Address(a))).ToHashSet()), - _ => throw new InvalidDataException("Invalid address filter format") - }; + return AddressFilter.AnyAddress; + } - private static FilterTopic?[]? GetFilterTopics(IEnumerable? topics) => topics?.Select(GetTopic).ToArray(); + return new AddressFilter(addresses); + } + + private static FilterTopic?[]? GetFilterTopics(IEnumerable? topics) => topics?.Select(GetTopic).ToArray(); - private static FilterTopic? GetTopic(object? obj) + private static FilterTopic? GetTopic(Hash256[]? topics) { - switch (obj) + if (topics?.Length == 1) { - case null: - return null; - case string topic: - return new FilterTopic - { - Topic = new Hash256(topic) - }; - case Hash256 keccak: - return new FilterTopic - { - Topic = keccak - }; + return new FilterTopic + { + Topic = topics[0] + }; } - - return obj is not IEnumerable topics - ? null - : new FilterTopic + else + { + return new FilterTopic() { - Topics = topics.Select(static t => new Hash256(t)).ToArray() + Topics = topics }; + } } private class FilterTopic { public Hash256? Topic { get; init; } public Hash256[]? Topics { get; init; } - } public void Dispose() diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index d2f41be173a..ba5c19c9ad7 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -36,16 +36,16 @@ public interface IBlockchainBridge : ILogFinder int NewBlockFilter(); int NewPendingTransactionFilter(); - int NewFilter(BlockParameter? fromBlock, BlockParameter? toBlock, object? address = null, IEnumerable? topics = null); + int NewFilter(BlockParameter? fromBlock, BlockParameter? toBlock, AddressAsKey[]? address = null, IEnumerable? topics = null); void UninstallFilter(int filterId); bool FilterExists(int filterId); Hash256[] GetBlockFilterChanges(int filterId); Hash256[] GetPendingTransactionFilterChanges(int filterId); FilterLog[] GetLogFilterChanges(int filterId); FilterType GetFilterType(int filterId); - LogFilter GetFilter(BlockParameter fromBlock, BlockParameter toBlock, object? address = null, IEnumerable? topics = null); + LogFilter GetFilter(BlockParameter fromBlock, BlockParameter toBlock, AddressAsKey[]? addresses = null, IEnumerable? topics = null); IEnumerable GetLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default); - IEnumerable GetLogs(BlockParameter fromBlock, BlockParameter toBlock, object? address = null, IEnumerable? topics = null, CancellationToken cancellationToken = default); + IEnumerable GetLogs(BlockParameter fromBlock, BlockParameter toBlock, AddressAsKey[]? addresses = null, IEnumerable? topics = null, CancellationToken cancellationToken = default); bool TryGetLogs(int filterId, out IEnumerable filterLogs, CancellationToken cancellationToken = default); void RunTreeVisitor(ITreeVisitor treeVisitor, Hash256 stateRoot) where TCtx : struct, INodeContext; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 0e6b8f73d9c..98d795f7cb7 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -579,9 +579,9 @@ void HandleNewBlock(object? sender, BlockReplacementEventArgs e) await test.AddBlock(createCodeTx); - string getLogsSerialized = await test.TestEthRpc("eth_getLogs", $"{{\"fromBlock\":\"{blockHash}\"}}"); + string getLogsSerialized = await test.TestEthRpc("eth_getLogs", $"{{\"blockHash\":\"{blockHash}\"}}"); - using JsonRpcResponse? newFilterResp = await RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", new { fromBlock = blockHash }); + using JsonRpcResponse? newFilterResp = await RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", new { blockHash = blockHash }); Assert.That(newFilterResp is not null && newFilterResp is JsonRpcSuccessResponse, Is.True); @@ -591,13 +591,13 @@ void HandleNewBlock(object? sender, BlockReplacementEventArgs e) } [TestCase("{}", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"0x2","toBlock":"latest","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"earliest","toBlock":"pending","address":["0x00000000000000000001", "0x00000000000000000001"],"topics":["0x00000000000000000000000000000001", "0x00000000000000000000000000000002"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"topics":[null, ["0x00000000000000000000000000000001", "0x00000000000000000000000000000002"]]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"0x10","toBlock":"latest","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid block range params"},"id":67}""")] - [TestCase("""{"fromBlock":"0x2","toBlock":"0x11","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"0x2","toBlock":"0x1","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid block range params"},"id":67}""")] - [TestCase("""{"fromBlock":"0x11","toBlock":"0x12","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[],"id":67}""")] + [TestCase("""{"fromBlock":"0x2","toBlock":"latest","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"fromBlock":"earliest","toBlock":"pending","address":["0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000001"],"topics":["0x0000000000000000000000000000000000000001", "0x00000000000000000000000000000002"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"topics":[null, ["0x0000000000000000000000000000000000000001", "0x00000000000000000000000000000002"]]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"fromBlock":"0x10","toBlock":"latest","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"requested block range is in the future"},"id":67}""")] + [TestCase("""{"fromBlock":"0x2","toBlock":"0x3","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"fromBlock":"0x2","toBlock":"0x1","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid block range params"},"id":67}""")] + [TestCase("""{"fromBlock":"0x11","toBlock":"0x12","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"requested block range is in the future"},"id":67}""")] public async Task Eth_get_logs(string parameter, string expected) { using Context ctx = await Context.Create(); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs index 2ab35c9ae37..af06485f1bd 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs @@ -6,6 +6,8 @@ using FluentAssertions; using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Serialization.Json; @@ -23,7 +25,7 @@ public static IEnumerable JsonTests yield return new TestCaseData("{}", new Filter { - FromBlock = BlockParameter.Latest, + FromBlock = BlockParameter.Earliest, ToBlock = BlockParameter.Latest, }); @@ -31,8 +33,6 @@ public static IEnumerable JsonTests JsonSerializer.Serialize( new { - fromBlock = "earliest", - toBlock = "pending", topics = new object?[] { null, @@ -47,17 +47,16 @@ public static IEnumerable JsonTests new Filter { FromBlock = BlockParameter.Earliest, - ToBlock = BlockParameter.Pending, - Topics = new object?[] - { + ToBlock = BlockParameter.Latest, + Topics = + [ null, - "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7", - new[] - { - "0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001", - "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7" - } - } + [new("0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7")], + [ + new Hash256("0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001"), + new Hash256("0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7") + ] + ] }); yield return new TestCaseData( @@ -66,7 +65,6 @@ public static IEnumerable JsonTests { address = "0xc2d77d118326c33bbe36ebeabf4f7ed6bc2dda5c", fromBlock = "0x1143ade", - toBlock = "latest", topics = new object?[] { "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7", @@ -77,16 +75,16 @@ public static IEnumerable JsonTests }), new Filter { - Address = "0xc2d77d118326c33bbe36ebeabf4f7ed6bc2dda5c", + Address = [new Address("0xc2d77d118326c33bbe36ebeabf4f7ed6bc2dda5c")], FromBlock = new BlockParameter(0x1143ade), ToBlock = BlockParameter.Latest, - Topics = new object?[] - { - "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7", + Topics = + [ + [new("0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7")], null, null, - "0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001" - } + [new("0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001")] + ] }); var blockHash = "0x892a8b3ccc78359e059e67ec44c83bfed496721d48c2d1dd929d6e4cd6559d35"; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs index edf0f8bbfa5..4a988139e3b 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs @@ -603,8 +603,8 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ { FromBlock = BlockParameter.Latest, ToBlock = BlockParameter.Latest, - Address = "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099", - Topics = new[] { TestItem.KeccakA } + Address = [new Address("0xb7705ae4c6f81b66cdb323c65f4e8133690fc099")], + Topics = [[TestItem.KeccakA]] }; LogEntry logEntryA = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA).WithData(TestItem.RandomDataA).TestObject; @@ -651,8 +651,8 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ { FromBlock = BlockParameter.Latest, ToBlock = BlockParameter.Latest, - Address = "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099", - Topics = new[] { TestItem.KeccakA } + Address = [new Address("0xb7705ae4c6f81b66cdb323c65f4e8133690fc099")], + Topics = [[TestItem.KeccakA]] }; LogEntry logEntryA = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA).WithData(TestItem.RandomDataA).TestObject; @@ -699,8 +699,12 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ { FromBlock = BlockParameter.Latest, ToBlock = BlockParameter.Latest, - Address = new[] { "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099", "0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358" }, - Topics = new[] { TestItem.KeccakA, TestItem.KeccakD } + Address = + [ + new Address("0xb7705ae4c6f81b66cdb323c65f4e8133690fc099"), + new Address("0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358") + ], + Topics = [[TestItem.KeccakA, TestItem.KeccakD]] }; LogEntry logEntryA = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA, TestItem.KeccakD).WithData(TestItem.RandomDataA).TestObject; @@ -922,12 +926,14 @@ public async Task MultipleSubscriptions_concurrent_fast_messages(int messages) Task subA = Task.Run(() => { ITxPool txPool = Substitute.For(); - using NewPendingTransactionsSubscription subscription = new( - // ReSharper disable once AccessToDisposedClosure - jsonRpcDuplexClient: client, - txPool: txPool, - specProvider: _specProvider, - logManager: LimboLogs.Instance); + using NewPendingTransactionsSubscription subscription = + new( + // ReSharper disable once AccessToDisposedClosure + jsonRpcDuplexClient: client, + txPool: txPool, + specProvider: _specProvider, + logManager: LimboLogs.Instance + ); for (int i = 0; i < messages; i++) { @@ -938,12 +944,14 @@ public async Task MultipleSubscriptions_concurrent_fast_messages(int messages) Task subB = Task.Run(() => { IBlockTree blockTree = Substitute.For(); - using NewHeadSubscription subscription = new( - // ReSharper disable once AccessToDisposedClosure - jsonRpcDuplexClient: client, - blockTree: blockTree, - specProvider: new TestSpecProvider(new ReleaseSpec()), - logManager: LimboLogs.Instance); + using NewHeadSubscription subscription = + new( + // ReSharper disable once AccessToDisposedClosure + jsonRpcDuplexClient: client, + blockTree: blockTree, + specProvider: new TestSpecProvider(new ReleaseSpec()), + logManager: LimboLogs.Instance + ); for (int i = 0; i < messages; i++) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 6adb304f637..2dffcfa9733 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -584,47 +584,49 @@ public ResultWrapper> eth_getFilterLogs(UInt256 filterId) public ResultWrapper> eth_getLogs(Filter filter) { - BlockParameter fromBlock = filter.FromBlock; - BlockParameter toBlock = filter.ToBlock; + BlockParameter fromBlock = filter.FromBlock!; + BlockParameter toBlock = filter.ToBlock!; // because of lazy evaluation of enumerable, we need to do the validation here first using CancellationTokenSource timeout = BuildTimeoutCancellationTokenSource(); CancellationToken cancellationToken = timeout.Token; - if (!TryFindBlockHeaderOrUseLatest(_blockFinder, ref toBlock, out SearchResult toBlockResult, out long? sourceToBlockNumber)) + long? headNumber = _blockFinder.Head?.Number; + if (headNumber < fromBlock.BlockNumber || headNumber < toBlock.BlockNumber) { - return FailWithNoHeadersSyncedYet(toBlockResult); + return ResultWrapper>.Fail("requested block range is in the future", ErrorCodes.InvalidParams); } - - cancellationToken.ThrowIfCancellationRequested(); - - SearchResult fromBlockResult; - long? sourceFromBlockNumber; - - if (fromBlock == toBlock) + if (fromBlock.BlockNumber > toBlock.BlockNumber) { - fromBlockResult = toBlockResult; - sourceFromBlockNumber = sourceToBlockNumber; + return ResultWrapper>.Fail("invalid block range params", ErrorCodes.InvalidParams); } - else if (!TryFindBlockHeaderOrUseLatest(_blockFinder, ref fromBlock, out fromBlockResult, out sourceFromBlockNumber)) + + SearchResult fromResult = blockFinder.SearchForHeader(fromBlock); + if (fromResult.IsError) { - return FailWithNoHeadersSyncedYet(fromBlockResult); + return FailWithNoHeadersSyncedYet(fromResult); } - if (sourceFromBlockNumber > sourceToBlockNumber) + cancellationToken.ThrowIfCancellationRequested(); + + SearchResult toResult; + if (fromBlock == toBlock) { - return ResultWrapper>.Fail("invalid block range params", ErrorCodes.InvalidParams); + toResult = fromResult; } - - if (_blockFinder.Head?.Number is not null && sourceFromBlockNumber > _blockFinder.Head.Number) + else { - return ResultWrapper>.Success([]); + toResult = blockFinder.SearchForHeader(toBlock); + if (toResult.IsError) + { + return FailWithNoHeadersSyncedYet(toResult); + } } cancellationToken.ThrowIfCancellationRequested(); - BlockHeader fromBlockHeader = fromBlockResult.Object; - BlockHeader toBlockHeader = toBlockResult.Object; + BlockHeader fromBlockHeader = fromResult.Object!; + BlockHeader toBlockHeader = toResult.Object!; try { @@ -655,30 +657,6 @@ public ResultWrapper> eth_getLogs(Filter filter) ResultWrapper> FailWithNoHeadersSyncedYet(SearchResult blockResult) => GetFailureResult, BlockHeader>(blockResult, _ethSyncingInfo.SyncMode.HaveNotSyncedHeadersYet()); - - // If there is an error, we check if we seach by number and it's after the head, then try to use head instead - static bool TryFindBlockHeaderOrUseLatest(IBlockFinder blockFinder, ref BlockParameter blockParameter, out SearchResult blockResult, out long? sourceBlockNumber) - { - blockResult = blockFinder.SearchForHeader(blockParameter); - - if (blockResult.IsError) - { - if (blockParameter.Type is BlockParameterType.BlockNumber && - blockFinder.Head?.Number < blockParameter.BlockNumber) - { - blockResult = new SearchResult(blockFinder.Head.Header); - - sourceBlockNumber = blockParameter.BlockNumber.Value; - return true; - } - - sourceBlockNumber = null; - return false; - } - - sourceBlockNumber = blockResult.Object.Number; - return true; - } } // https://github.com/ethereum/EIPs/issues/1186 diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs index 0b3a0284bdb..f8d9a7bfe85 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs @@ -1,28 +1,29 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Text.Json; - using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.JsonRpc.Data; namespace Nethermind.JsonRpc.Modules.Eth; public class Filter : IJsonRpcParam { - public object? Address { get; set; } + public AddressAsKey[]? Address { get; set; } - public BlockParameter? FromBlock { get; set; } + public BlockParameter FromBlock { get; set; } - public BlockParameter? ToBlock { get; set; } + public BlockParameter ToBlock { get; set; } - public IEnumerable? Topics { get; set; } + public IEnumerable? Topics { get; set; } public void ReadJson(JsonElement filter, JsonSerializerOptions options) { JsonDocument doc = null; - string blockHash = null; try { if (filter.ValueKind == JsonValueKind.String) @@ -31,34 +32,39 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) filter = doc.RootElement; } - if (filter.TryGetProperty("blockHash"u8, out JsonElement blockHashElement)) - { - blockHash = blockHashElement.GetString(); - } + bool hasBlockHash = filter.TryGetProperty("blockHash"u8, out JsonElement blockHashElement); + bool hasFromBlock = filter.TryGetProperty("fromBlock"u8, out JsonElement fromBlockElement); + bool hasToBlock = filter.TryGetProperty("toBlock"u8, out JsonElement toBlockElement); - if (blockHash is null) + if (hasBlockHash && blockHashElement.ValueKind != JsonValueKind.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()); + if (hasFromBlock || hasToBlock) + { + throw new ArgumentException("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other"); + } + + FromBlock = new(new Hash256(blockHashElement.ToString())); + ToBlock = FromBlock; } else { - FromBlock = ToBlock = BlockParameterConverter.GetBlockParameter(blockHash); + FromBlock = hasFromBlock && fromBlockElement.ValueKind != JsonValueKind.Null + ? BlockParameterConverter.GetBlockParameter(fromBlockElement.ToString()) + : BlockParameter.Earliest; + ToBlock = hasToBlock && toBlockElement.ValueKind != JsonValueKind.Null + ? BlockParameterConverter.GetBlockParameter(toBlockElement.ToString()) + : BlockParameter.Latest; } - filter.TryGetProperty("address"u8, out JsonElement addressElement); - Address = GetAddress(addressElement, options); + if (filter.TryGetProperty("address"u8, out JsonElement addressElement)) + { + Address = GetAddress(addressElement, options); + } if (filter.TryGetProperty("topics"u8, out JsonElement topicsElement) && topicsElement.ValueKind == JsonValueKind.Array) { Topics = GetTopics(topicsElement, options); } - else - { - Topics = null; - } } finally { @@ -66,9 +72,29 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) } } - private static object? GetAddress(JsonElement? token, JsonSerializerOptions options) => GetSingleOrMany(token, options); + private static AddressAsKey[]? GetAddress(JsonElement token, JsonSerializerOptions options) + { + switch (token.ValueKind) + { + case JsonValueKind.Undefined or JsonValueKind.Null: + return null; + case JsonValueKind.String: + return [new AddressAsKey(new Address(token.ToString()))]; + case JsonValueKind.Array: + var enumerator = token.EnumerateArray(); + List result = new(); + while (enumerator.MoveNext()) + { + result.Add(new(new Address(enumerator.Current.ToString()))); + } + + return result.ToArray(); + default: + throw new ArgumentException("invalid address field"); + } + } - private static IEnumerable GetTopics(JsonElement? array, JsonSerializerOptions options) + private static IEnumerable? GetTopics(JsonElement? array, JsonSerializerOptions options) { if (array is null) { @@ -77,16 +103,27 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) foreach (var token in array.GetValueOrDefault().EnumerateArray()) { - yield return GetSingleOrMany(token, options); + switch (token.ValueKind) + { + case JsonValueKind.Undefined or JsonValueKind.Null: + yield return null; + break; + case JsonValueKind.String: + yield return [new Hash256(token.GetString()!)]; + break; + case JsonValueKind.Array: + var enumerator = token.EnumerateArray(); + List result = new(); + while (enumerator.MoveNext()) + { + result.Add(new(enumerator.Current.ToString())); + } + + yield return result.ToArray(); + break; + default: + throw new ArgumentException("invalid topics field"); + } } } - - private static object? GetSingleOrMany(JsonElement? token, JsonSerializerOptions options) => token switch - { - null => null, - { ValueKind: JsonValueKind.Undefined } _ => null, - { ValueKind: JsonValueKind.Null } _ => null, - { ValueKind: JsonValueKind.Array } _ => token.GetValueOrDefault().Deserialize(options), - _ => token.GetValueOrDefault().GetString(), - }; }