diff --git a/Framework/NethereumWorkflow/BlockTimeEntry.cs b/Framework/NethereumWorkflow/BlockTimeEntry.cs index 03817ae..921f5b5 100644 --- a/Framework/NethereumWorkflow/BlockTimeEntry.cs +++ b/Framework/NethereumWorkflow/BlockTimeEntry.cs @@ -1,22 +1,19 @@ namespace NethereumWorkflow { - public partial class BlockTimeFinder + public class BlockTimeEntry { - public class BlockTimeEntry + public BlockTimeEntry(ulong blockNumber, DateTime utc) { - public BlockTimeEntry(ulong blockNumber, DateTime utc) - { - BlockNumber = blockNumber; - Utc = utc; - } + BlockNumber = blockNumber; + Utc = utc; + } - public ulong BlockNumber { get; } - public DateTime Utc { get; } + public ulong BlockNumber { get; } + public DateTime Utc { get; } - public override string ToString() - { - return $"[{BlockNumber}] @ {Utc.ToString("o")}"; - } + public override string ToString() + { + return $"[{BlockNumber}] @ {Utc.ToString("o")}"; } } } diff --git a/Framework/NethereumWorkflow/BlockTimeFinder.cs b/Framework/NethereumWorkflow/BlockTimeFinder.cs index e7b8c4a..34931e8 100644 --- a/Framework/NethereumWorkflow/BlockTimeFinder.cs +++ b/Framework/NethereumWorkflow/BlockTimeFinder.cs @@ -1,36 +1,33 @@ using Logging; -using Nethereum.RPC.Eth.DTOs; -using Nethereum.Web3; -using Utils; namespace NethereumWorkflow { - public partial class BlockTimeFinder + public class BlockTimeFinder { private const ulong FetchRange = 6; private const int MaxEntries = 1024; private static readonly Dictionary entries = new Dictionary(); - private readonly Web3 web3; + private readonly IWeb3Blocks web3; private readonly ILog log; - public BlockTimeFinder(Web3 web3, ILog log) + public BlockTimeFinder(IWeb3Blocks web3, ILog log) { this.web3 = web3; this.log = log; } - public ulong GetHighestBlockNumberBefore(DateTime moment) + public ulong? GetHighestBlockNumberBefore(DateTime moment) { - log.Log("Looking for highest block before " + moment.ToString("o")); + log.Debug("Looking for highest block before " + moment.ToString("o")); AssertMomentIsInPast(moment); Initialize(); return GetHighestBlockBefore(moment); } - public ulong GetLowestBlockNumberAfter(DateTime moment) + public ulong? GetLowestBlockNumberAfter(DateTime moment) { - log.Log("Looking for lowest block after " + moment.ToString("o")); + log.Debug("Looking for lowest block after " + moment.ToString("o")); AssertMomentIsInPast(moment); Initialize(); @@ -48,7 +45,7 @@ namespace NethereumWorkflow closestAfter.Utc > moment && closestBefore.BlockNumber + 1 == closestAfter.BlockNumber) { - log.Log("Found highest-Before: " + closestBefore); + log.Debug("Found highest-Before: " + closestBefore); return closestBefore.BlockNumber; } @@ -67,7 +64,7 @@ namespace NethereumWorkflow closestAfter.Utc > moment && closestBefore.BlockNumber + 1 == closestAfter.BlockNumber) { - log.Log("Found lowest-after: " + closestAfter); + log.Debug("Found lowest-after: " + closestAfter); return closestAfter.BlockNumber; } @@ -223,24 +220,13 @@ namespace NethereumWorkflow private BlockTimeEntry? AddCurrentBlock() { - var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()); - var blockNumber = number.ToDecimal(); + var blockNumber = web3.GetCurrentBlockNumber(); return AddBlockNumber(blockNumber); } private DateTime? GetTimestampFromBlock(ulong blockNumber) { - try - { - var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(blockNumber))); - if (block == null) return null; - return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime; - } - catch (Exception ex) - { - int i = 0; - throw; - } + return web3.GetTimestampForBlock(blockNumber); } private BlockTimeEntry? FindClosestBeforeEntry(DateTime moment) diff --git a/Framework/NethereumWorkflow/NethereumInteraction.cs b/Framework/NethereumWorkflow/NethereumInteraction.cs index 0a8c2e4..2f58874 100644 --- a/Framework/NethereumWorkflow/NethereumInteraction.cs +++ b/Framework/NethereumWorkflow/NethereumInteraction.cs @@ -3,7 +3,6 @@ using Nethereum.ABI.FunctionEncoding.Attributes; using Nethereum.Contracts; using Nethereum.RPC.Eth.DTOs; using Nethereum.Web3; -using System.Runtime.CompilerServices; using Utils; namespace NethereumWorkflow @@ -88,7 +87,8 @@ namespace NethereumWorkflow public List> GetEvents(string address, TimeRange timeRange) where TEvent : IEventDTO, new() { - var blockTimeFinder = new BlockTimeFinder(web3, log); + var wrapper = new Web3Wrapper(web3, log); + var blockTimeFinder = new BlockTimeFinder(wrapper, log); var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From); var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To); diff --git a/Framework/NethereumWorkflow/Web3Wrapper.cs b/Framework/NethereumWorkflow/Web3Wrapper.cs new file mode 100644 index 0000000..67c8ca9 --- /dev/null +++ b/Framework/NethereumWorkflow/Web3Wrapper.cs @@ -0,0 +1,46 @@ +using Logging; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Web3; +using Utils; + +namespace NethereumWorkflow +{ + public interface IWeb3Blocks + { + decimal GetCurrentBlockNumber(); + DateTime? GetTimestampForBlock(decimal blockNumber); + } + + public class Web3Wrapper : IWeb3Blocks + { + private readonly Web3 web3; + private readonly ILog log; + + public Web3Wrapper(Web3 web3, ILog log) + { + this.web3 = web3; + this.log = log; + } + + public decimal GetCurrentBlockNumber() + { + var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()); + return number.ToDecimal(); + } + + public DateTime? GetTimestampForBlock(decimal blockNumber) + { + try + { + var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(Convert.ToUInt64(blockNumber)))); + if (block == null) return null; + return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime; + } + catch (Exception ex) + { + log.Error("Exception while getting timestamp for block: " + ex); + return null; + } + } + } +} diff --git a/Tests/FrameworkTests/FrameworkTests.csproj b/Tests/FrameworkTests/FrameworkTests.csproj index fbad277..f22fe57 100644 --- a/Tests/FrameworkTests/FrameworkTests.csproj +++ b/Tests/FrameworkTests/FrameworkTests.csproj @@ -7,12 +7,14 @@ + + diff --git a/Tests/FrameworkTests/NethereumWorkflow/BlockTimeFinderTests.cs b/Tests/FrameworkTests/NethereumWorkflow/BlockTimeFinderTests.cs new file mode 100644 index 0000000..a9506d7 --- /dev/null +++ b/Tests/FrameworkTests/NethereumWorkflow/BlockTimeFinderTests.cs @@ -0,0 +1,137 @@ +using Logging; +using Moq; +using NethereumWorkflow; +using NUnit.Framework; + +namespace FrameworkTests.NethereumWorkflow +{ + [TestFixture] + public class BlockTimeFinderTests + { + private Mock web3 = new Mock(); + private Mock log = new Mock(); + private Dictionary blocks = new Dictionary(); + private DateTime start = DateTime.Now; + + private BlockTimeFinder finder = null!; + + private void SetupContinuousBlockchain() + { + start = DateTime.UtcNow.AddDays(-1).AddSeconds(-30); + blocks = new Dictionary(); + + for (var i = 0; i < 30; i++) + { + decimal d = 100 + i; + blocks.Add(d, new Block(d, start + TimeSpan.FromSeconds(i * 2))); + } + } + + [SetUp] + public void SetUp() + { + SetupContinuousBlockchain(); + + web3 = new Mock(); + web3.Setup(w => w.GetCurrentBlockNumber()).Returns(blocks.Keys.Max()); + web3.Setup(w => w.GetTimestampForBlock(It.IsAny())).Returns(d => + { + if (blocks.ContainsKey(d)) return blocks[d].Time; + return null; + }); + + finder = new BlockTimeFinder(web3.Object, log.Object); + } + + [Test] + public void FindsMiddleOfChain() + { + var b1 = blocks[115]; + var b2 = blocks[116]; + + var momentBetween = b1.JustAfter; + + var b1Number = finder.GetHighestBlockNumberBefore(momentBetween); + var b2Number = finder.GetLowestBlockNumberAfter(momentBetween); + + Assert.That(b1Number, Is.EqualTo(b1.Number)); + Assert.That(b2Number, Is.EqualTo(b2.Number)); + } + + [Test] + public void FindsFrontOfChain_Lowest() + { + var first = blocks.First().Value; + + var firstNumber = finder.GetLowestBlockNumberAfter(first.JustBefore); + + Assert.That(firstNumber, Is.EqualTo(first.Number)); + } + + [Test] + public void FindsFrontOfChain_Highest() + { + var first = blocks.First().Value; + + var firstNumber = finder.GetHighestBlockNumberBefore(first.JustAfter); + + Assert.That(firstNumber, Is.EqualTo(first.Number)); + } + + [Test] + public void FindsTailOfChain_Lowest() + { + var last = blocks.Last().Value; + + var lastNumber = finder.GetLowestBlockNumberAfter(last.JustBefore); + + Assert.That(lastNumber, Is.EqualTo(last.Number)); + } + + [Test] + public void FindsTailOfChain_Highest() + { + var last = blocks.Last().Value; + + var lastNumber = finder.GetHighestBlockNumberBefore(last.JustAfter); + + Assert.That(lastNumber, Is.EqualTo(last.Number)); + } + + [Test] + public void FailsToFindBlockBeforeFrontOfChain() + { + var first = blocks.First().Value; + + var notFound = finder.GetHighestBlockNumberBefore(first.Time); + + Assert.That(notFound, Is.Null); + } + + [Test] + public void FailsToFindBlockAfterTailOfChain() + { + var last = blocks.Last().Value; + + var notFound = finder.GetLowestBlockNumberAfter(last.Time); + + Assert.That(notFound, Is.Null); + } + + } + + public class Block + { + public Block(decimal number, DateTime time) + { + Number = number; + Time = time; + } + + public decimal Number { get; } + public DateTime Time { get; } + public DateTime JustBefore { get { return Time.AddSeconds(-1); } } + public DateTime JustAfter { get { return Time.AddSeconds(1); } } + } + +}