cleanup
This commit is contained in:
parent
6d4d6fcdb9
commit
3cc3a2a9dd
|
@ -1,217 +0,0 @@
|
|||
using Logging;
|
||||
|
||||
namespace NethereumWorkflow
|
||||
{
|
||||
public class BlockTimeFinder
|
||||
{
|
||||
private readonly BlockCache cache;
|
||||
private readonly IWeb3Blocks web3;
|
||||
private readonly ILog log;
|
||||
|
||||
public BlockTimeFinder(IWeb3Blocks web3, ILog log)
|
||||
{
|
||||
this.web3 = web3;
|
||||
this.log = log;
|
||||
|
||||
cache = new BlockCache(web3);
|
||||
}
|
||||
|
||||
public ulong? GetHighestBlockNumberBefore(DateTime moment)
|
||||
{
|
||||
cache.Initialize();
|
||||
if (moment <= cache.Genesis.Utc) return null;
|
||||
if (moment >= cache.Current.Utc) return cache.Current.BlockNumber;
|
||||
|
||||
return Search(cache.Genesis, cache.Current, moment, HighestBeforeSelector);
|
||||
}
|
||||
|
||||
public ulong? GetLowestBlockNumberAfter(DateTime moment)
|
||||
{
|
||||
cache.Initialize();
|
||||
if (moment >= cache.Current.Utc) return null;
|
||||
if (moment <= cache.Genesis.Utc) return cache.Genesis.BlockNumber;
|
||||
|
||||
return Search(cache.Genesis, cache.Current, moment, LowestAfterSelector);
|
||||
}
|
||||
|
||||
private ulong Search(BlockTimeEntry lower, BlockTimeEntry upper, DateTime target, Func<DateTime, BlockTimeEntry, bool> isWhatIwant)
|
||||
{
|
||||
var middle = GetMiddle(lower, upper);
|
||||
if (middle.BlockNumber == lower.BlockNumber)
|
||||
{
|
||||
if (isWhatIwant(target, upper)) return upper.BlockNumber;
|
||||
}
|
||||
|
||||
if (isWhatIwant(target, middle))
|
||||
{
|
||||
return middle.BlockNumber;
|
||||
}
|
||||
|
||||
if (middle.Utc > target)
|
||||
{
|
||||
return Search(lower, middle, target, isWhatIwant);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Search(middle, upper, target, isWhatIwant);
|
||||
}
|
||||
}
|
||||
|
||||
private BlockTimeEntry GetMiddle(BlockTimeEntry lower, BlockTimeEntry upper)
|
||||
{
|
||||
ulong range = upper.BlockNumber - lower.BlockNumber;
|
||||
ulong number = lower.BlockNumber + (range / 2);
|
||||
return GetBlock(number);
|
||||
}
|
||||
|
||||
private bool HighestBeforeSelector(DateTime target, BlockTimeEntry entry)
|
||||
{
|
||||
var next = GetBlock(entry.BlockNumber + 1);
|
||||
return
|
||||
entry.Utc < target &&
|
||||
next.Utc > target;
|
||||
}
|
||||
|
||||
private bool LowestAfterSelector(DateTime target, BlockTimeEntry entry)
|
||||
{
|
||||
var previous = GetBlock(entry.BlockNumber - 1);
|
||||
return
|
||||
entry.Utc > target &&
|
||||
previous.Utc < target;
|
||||
}
|
||||
|
||||
private BlockTimeEntry GetBlock(ulong number)
|
||||
{
|
||||
if (number < cache.Genesis.BlockNumber) throw new Exception("Can't fetch block before genesis.");
|
||||
if (number > cache.Current.BlockNumber) throw new Exception("Can't fetch block after current.");
|
||||
|
||||
var dateTime = web3.GetTimestampForBlock(number);
|
||||
if (dateTime == null) throw new Exception("Failed to get dateTime for block that should exist.");
|
||||
return cache.Add(number, dateTime.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public class BlockCache
|
||||
{
|
||||
private const int MaxEntries = 1024;
|
||||
private readonly Dictionary<ulong, BlockTimeEntry> entries = new Dictionary<ulong, BlockTimeEntry>();
|
||||
private readonly IWeb3Blocks web3;
|
||||
|
||||
public BlockTimeEntry Genesis { get; private set; } = null!;
|
||||
public BlockTimeEntry Current { get; private set; } = null!;
|
||||
|
||||
public BlockCache(IWeb3Blocks web3)
|
||||
{
|
||||
this.web3 = web3;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
AddCurrentBlock();
|
||||
LookForGenesisBlock();
|
||||
|
||||
if (Current.BlockNumber == Genesis.BlockNumber)
|
||||
{
|
||||
throw new Exception("Unsupported condition: Current block is genesis block.");
|
||||
}
|
||||
}
|
||||
|
||||
public BlockTimeEntry Add(ulong number, DateTime dateTime)
|
||||
{
|
||||
return Add(new BlockTimeEntry(number, dateTime));
|
||||
}
|
||||
|
||||
public BlockTimeEntry Add(BlockTimeEntry entry)
|
||||
{
|
||||
if (!entries.ContainsKey(entry.BlockNumber))
|
||||
{
|
||||
if (entries.Count > MaxEntries)
|
||||
{
|
||||
entries.Clear();
|
||||
Initialize();
|
||||
}
|
||||
entries.Add(entry.BlockNumber, entry);
|
||||
}
|
||||
|
||||
return entries[entry.BlockNumber];
|
||||
}
|
||||
|
||||
public BlockTimeEntry? Get(ulong number)
|
||||
{
|
||||
if (!entries.TryGetValue(number, out BlockTimeEntry? value)) return null;
|
||||
return value;
|
||||
}
|
||||
|
||||
private void LookForGenesisBlock()
|
||||
{
|
||||
if (Genesis != null) return;
|
||||
|
||||
var blockTime = web3.GetTimestampForBlock(0);
|
||||
if (blockTime != null)
|
||||
{
|
||||
AddGenesisBlock(0, blockTime.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
LookForGenesisBlock(0, Current);
|
||||
}
|
||||
|
||||
private void LookForGenesisBlock(ulong lower, BlockTimeEntry upper)
|
||||
{
|
||||
if (Genesis != null) return;
|
||||
|
||||
var range = upper.BlockNumber - lower;
|
||||
if (range == 1)
|
||||
{
|
||||
var lowTime = web3.GetTimestampForBlock(lower);
|
||||
if (lowTime != null)
|
||||
{
|
||||
AddGenesisBlock(lower, lowTime.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddGenesisBlock(upper);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var current = lower + (range / 2);
|
||||
|
||||
var blockTime = web3.GetTimestampForBlock(current);
|
||||
if (blockTime != null)
|
||||
{
|
||||
var newUpper = Add(current, blockTime.Value);
|
||||
LookForGenesisBlock(lower, newUpper);
|
||||
}
|
||||
else
|
||||
{
|
||||
LookForGenesisBlock(current, upper);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCurrentBlock()
|
||||
{
|
||||
var currentBlockNumber = web3.GetCurrentBlockNumber();
|
||||
var blockTime = web3.GetTimestampForBlock(currentBlockNumber);
|
||||
if (blockTime == null) throw new Exception("Unable to get dateTime for current block.");
|
||||
AddCurrentBlock(currentBlockNumber, blockTime.Value);
|
||||
}
|
||||
|
||||
private void AddCurrentBlock(ulong currentBlockNumber, DateTime dateTime)
|
||||
{
|
||||
Current = new BlockTimeEntry(currentBlockNumber, dateTime);
|
||||
Add(Current);
|
||||
}
|
||||
|
||||
private void AddGenesisBlock(ulong number, DateTime dateTime)
|
||||
{
|
||||
AddGenesisBlock(new BlockTimeEntry(number, dateTime));
|
||||
}
|
||||
|
||||
private void AddGenesisBlock(BlockTimeEntry entry)
|
||||
{
|
||||
Genesis = entry;
|
||||
Add(Genesis);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
namespace NethereumWorkflow.BlockUtils
|
||||
{
|
||||
public class BlockCache
|
||||
{
|
||||
public delegate void CacheClearedEvent();
|
||||
|
||||
private const int MaxEntries = 1024;
|
||||
private readonly Dictionary<ulong, BlockTimeEntry> entries = new Dictionary<ulong, BlockTimeEntry>();
|
||||
|
||||
public event CacheClearedEvent? OnCacheCleared;
|
||||
|
||||
public BlockTimeEntry Add(ulong number, DateTime dateTime)
|
||||
{
|
||||
return Add(new BlockTimeEntry(number, dateTime));
|
||||
}
|
||||
|
||||
public BlockTimeEntry Add(BlockTimeEntry entry)
|
||||
{
|
||||
if (!entries.ContainsKey(entry.BlockNumber))
|
||||
{
|
||||
if (entries.Count > MaxEntries)
|
||||
{
|
||||
entries.Clear();
|
||||
var e = OnCacheCleared;
|
||||
if (e != null) e();
|
||||
}
|
||||
entries.Add(entry.BlockNumber, entry);
|
||||
}
|
||||
|
||||
return entries[entry.BlockNumber];
|
||||
}
|
||||
|
||||
public BlockTimeEntry? Get(ulong number)
|
||||
{
|
||||
if (!entries.TryGetValue(number, out BlockTimeEntry? value)) return null;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace NethereumWorkflow
|
||||
namespace NethereumWorkflow.BlockUtils
|
||||
{
|
||||
public class BlockTimeEntry
|
||||
{
|
|
@ -0,0 +1,95 @@
|
|||
using Logging;
|
||||
|
||||
namespace NethereumWorkflow.BlockUtils
|
||||
{
|
||||
public class BlockTimeFinder
|
||||
{
|
||||
private readonly BlockCache cache;
|
||||
private readonly BlockchainBounds bounds;
|
||||
private readonly IWeb3Blocks web3;
|
||||
private readonly ILog log;
|
||||
|
||||
public BlockTimeFinder(BlockCache cache, IWeb3Blocks web3, ILog log)
|
||||
{
|
||||
this.web3 = web3;
|
||||
this.log = log;
|
||||
|
||||
this.cache = cache;
|
||||
bounds = new BlockchainBounds(cache, web3);
|
||||
}
|
||||
|
||||
public ulong? GetHighestBlockNumberBefore(DateTime moment)
|
||||
{
|
||||
bounds.Initialize();
|
||||
if (moment <= bounds.Genesis.Utc) return null;
|
||||
if (moment >= bounds.Current.Utc) return bounds.Current.BlockNumber;
|
||||
|
||||
return Search(bounds.Genesis, bounds.Current, moment, HighestBeforeSelector);
|
||||
}
|
||||
|
||||
public ulong? GetLowestBlockNumberAfter(DateTime moment)
|
||||
{
|
||||
bounds.Initialize();
|
||||
if (moment >= bounds.Current.Utc) return null;
|
||||
if (moment <= bounds.Genesis.Utc) return bounds.Genesis.BlockNumber;
|
||||
|
||||
return Search(bounds.Genesis, bounds.Current, moment, LowestAfterSelector);
|
||||
}
|
||||
|
||||
private ulong Search(BlockTimeEntry lower, BlockTimeEntry upper, DateTime target, Func<DateTime, BlockTimeEntry, bool> isWhatIwant)
|
||||
{
|
||||
var middle = GetMiddle(lower, upper);
|
||||
if (middle.BlockNumber == lower.BlockNumber)
|
||||
{
|
||||
if (isWhatIwant(target, upper)) return upper.BlockNumber;
|
||||
}
|
||||
|
||||
if (isWhatIwant(target, middle))
|
||||
{
|
||||
return middle.BlockNumber;
|
||||
}
|
||||
|
||||
if (middle.Utc > target)
|
||||
{
|
||||
return Search(lower, middle, target, isWhatIwant);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Search(middle, upper, target, isWhatIwant);
|
||||
}
|
||||
}
|
||||
|
||||
private BlockTimeEntry GetMiddle(BlockTimeEntry lower, BlockTimeEntry upper)
|
||||
{
|
||||
ulong range = upper.BlockNumber - lower.BlockNumber;
|
||||
ulong number = lower.BlockNumber + range / 2;
|
||||
return GetBlock(number);
|
||||
}
|
||||
|
||||
private bool HighestBeforeSelector(DateTime target, BlockTimeEntry entry)
|
||||
{
|
||||
var next = GetBlock(entry.BlockNumber + 1);
|
||||
return
|
||||
entry.Utc < target &&
|
||||
next.Utc > target;
|
||||
}
|
||||
|
||||
private bool LowestAfterSelector(DateTime target, BlockTimeEntry entry)
|
||||
{
|
||||
var previous = GetBlock(entry.BlockNumber - 1);
|
||||
return
|
||||
entry.Utc > target &&
|
||||
previous.Utc < target;
|
||||
}
|
||||
|
||||
private BlockTimeEntry GetBlock(ulong number)
|
||||
{
|
||||
if (number < bounds.Genesis.BlockNumber) throw new Exception("Can't fetch block before genesis.");
|
||||
if (number > bounds.Current.BlockNumber) throw new Exception("Can't fetch block after current.");
|
||||
|
||||
var dateTime = web3.GetTimestampForBlock(number);
|
||||
if (dateTime == null) throw new Exception("Failed to get dateTime for block that should exist.");
|
||||
return cache.Add(number, dateTime.Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
namespace NethereumWorkflow.BlockUtils
|
||||
{
|
||||
public class BlockchainBounds
|
||||
{
|
||||
private readonly BlockCache cache;
|
||||
private readonly IWeb3Blocks web3;
|
||||
|
||||
public BlockTimeEntry Genesis { get; private set; } = null!;
|
||||
public BlockTimeEntry Current { get; private set; } = null!;
|
||||
|
||||
public BlockchainBounds(BlockCache cache, IWeb3Blocks web3)
|
||||
{
|
||||
this.cache = cache;
|
||||
this.web3 = web3;
|
||||
|
||||
cache.OnCacheCleared += Initialize;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
AddCurrentBlock();
|
||||
LookForGenesisBlock();
|
||||
|
||||
if (Current.BlockNumber == Genesis.BlockNumber)
|
||||
{
|
||||
throw new Exception("Unsupported condition: Current block is genesis block.");
|
||||
}
|
||||
}
|
||||
|
||||
private void LookForGenesisBlock()
|
||||
{
|
||||
if (Genesis != null)
|
||||
{
|
||||
cache.Add(Genesis);
|
||||
return;
|
||||
}
|
||||
|
||||
var blockTime = web3.GetTimestampForBlock(0);
|
||||
if (blockTime != null)
|
||||
{
|
||||
AddGenesisBlock(0, blockTime.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
LookForGenesisBlock(0, Current);
|
||||
}
|
||||
|
||||
private void LookForGenesisBlock(ulong lower, BlockTimeEntry upper)
|
||||
{
|
||||
if (Genesis != null) return;
|
||||
|
||||
var range = upper.BlockNumber - lower;
|
||||
if (range == 1)
|
||||
{
|
||||
var lowTime = web3.GetTimestampForBlock(lower);
|
||||
if (lowTime != null)
|
||||
{
|
||||
AddGenesisBlock(lower, lowTime.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddGenesisBlock(upper);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var current = lower + range / 2;
|
||||
|
||||
var blockTime = web3.GetTimestampForBlock(current);
|
||||
if (blockTime != null)
|
||||
{
|
||||
var newUpper = cache.Add(current, blockTime.Value);
|
||||
LookForGenesisBlock(lower, newUpper);
|
||||
}
|
||||
else
|
||||
{
|
||||
LookForGenesisBlock(current, upper);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCurrentBlock()
|
||||
{
|
||||
var currentBlockNumber = web3.GetCurrentBlockNumber();
|
||||
var blockTime = web3.GetTimestampForBlock(currentBlockNumber);
|
||||
if (blockTime == null) throw new Exception("Unable to get dateTime for current block.");
|
||||
AddCurrentBlock(currentBlockNumber, blockTime.Value);
|
||||
}
|
||||
|
||||
private void AddCurrentBlock(ulong currentBlockNumber, DateTime dateTime)
|
||||
{
|
||||
Current = new BlockTimeEntry(currentBlockNumber, dateTime);
|
||||
cache.Add(Current);
|
||||
}
|
||||
|
||||
private void AddGenesisBlock(ulong number, DateTime dateTime)
|
||||
{
|
||||
AddGenesisBlock(new BlockTimeEntry(number, dateTime));
|
||||
}
|
||||
|
||||
private void AddGenesisBlock(BlockTimeEntry entry)
|
||||
{
|
||||
Genesis = entry;
|
||||
cache.Add(Genesis);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,12 +3,16 @@ using Nethereum.ABI.FunctionEncoding.Attributes;
|
|||
using Nethereum.Contracts;
|
||||
using Nethereum.RPC.Eth.DTOs;
|
||||
using Nethereum.Web3;
|
||||
using NethereumWorkflow.BlockUtils;
|
||||
using Utils;
|
||||
|
||||
namespace NethereumWorkflow
|
||||
{
|
||||
public class NethereumInteraction
|
||||
{
|
||||
// BlockCache is a static instance: It stays alive for the duration of the application runtime.
|
||||
private readonly static BlockCache blockCache = new BlockCache();
|
||||
|
||||
private readonly ILog log;
|
||||
private readonly Web3 web3;
|
||||
|
||||
|
@ -88,12 +92,23 @@ namespace NethereumWorkflow
|
|||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new()
|
||||
{
|
||||
var wrapper = new Web3Wrapper(web3, log);
|
||||
var blockTimeFinder = new BlockTimeFinder(wrapper, log);
|
||||
var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log);
|
||||
|
||||
var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(timeRange.From);
|
||||
var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(timeRange.To);
|
||||
|
||||
return GetEvents<TEvent>(address, fromBlock, toBlock);
|
||||
if (!fromBlock.HasValue)
|
||||
{
|
||||
log.Error("Failed to find lowest block for time range: " + timeRange);
|
||||
throw new Exception("Failed");
|
||||
}
|
||||
if (!toBlock.HasValue)
|
||||
{
|
||||
log.Error("Failed to find highest block for time range: " + timeRange);
|
||||
throw new Exception("Failed");
|
||||
}
|
||||
|
||||
return GetEvents<TEvent>(address, fromBlock.Value, toBlock.Value);
|
||||
}
|
||||
|
||||
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using Logging;
|
||||
using Moq;
|
||||
using NethereumWorkflow;
|
||||
using NethereumWorkflow.BlockUtils;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace FrameworkTests.NethereumWorkflow
|
||||
|
@ -39,7 +40,7 @@ namespace FrameworkTests.NethereumWorkflow
|
|||
return null;
|
||||
});
|
||||
|
||||
finder = new BlockTimeFinder(web3.Object, log.Object);
|
||||
finder = new BlockTimeFinder(new BlockCache(), web3.Object, log.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
Loading…
Reference in New Issue