2
0
mirror of synced 2025-01-11 09:06:56 +00:00

block time finder

This commit is contained in:
benbierens 2023-12-19 15:30:46 +01:00
parent fb57998aa8
commit 7df1f3da7b
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
3 changed files with 244 additions and 0 deletions

View File

@ -0,0 +1,205 @@
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Web3;
using Utils;
namespace NethereumWorkflow
{
public class BlockTimeFinder
{
private class BlockTimeEntry
{
public BlockTimeEntry(ulong blockNumber, DateTime utc)
{
BlockNumber = blockNumber;
Utc = utc;
}
public ulong BlockNumber { get; }
public DateTime Utc { get; }
}
private const ulong FetchRange = 6;
private const int MaxEntries = 1024;
private readonly Web3 web3;
private static readonly Dictionary<ulong, BlockTimeEntry> entries = new Dictionary<ulong, BlockTimeEntry>();
public BlockTimeFinder(Web3 web3)
{
this.web3 = web3;
}
public ulong GetHighestBlockNumberBefore(DateTime moment)
{
AssertMomentIsInPast(moment);
Initialize();
var closestBefore = FindClosestBeforeEntry(moment);
var closestAfter = FindClosestAfterEntry(moment);
if (closestBefore.Utc < moment &&
closestAfter.Utc > moment &&
closestBefore.BlockNumber + 1 == closestAfter.BlockNumber)
{
return closestBefore.BlockNumber;
}
FetchBlocksAround(moment);
return GetHighestBlockNumberBefore(moment);
}
public ulong GetLowestBlockNumberAfter(DateTime moment)
{
AssertMomentIsInPast(moment);
Initialize();
var closestBefore = FindClosestBeforeEntry(moment);
var closestAfter = FindClosestAfterEntry(moment);
if (closestBefore.Utc < moment &&
closestAfter.Utc > moment &&
closestBefore.BlockNumber + 1 == closestAfter.BlockNumber)
{
return closestAfter.BlockNumber;
}
FetchBlocksAround(moment);
return GetLowestBlockNumberAfter(moment);
}
private void FetchBlocksAround(DateTime moment)
{
var timePerBlock = EstimateTimePerBlock();
EnsureRecentBlockIfNecessary(moment, timePerBlock);
var max = entries.Keys.Max();
var latest = entries[max];
var timeDifference = latest.Utc - moment;
double secondsDifference = Math.Abs(timeDifference.TotalSeconds);
double secondsPerBlock = timePerBlock.TotalSeconds;
double numberOfBlocksDifference = secondsDifference / secondsPerBlock;
var blockDifference = Convert.ToUInt64(numberOfBlocksDifference);
var fetchStart = (max - blockDifference) - (FetchRange / 2);
for (ulong i = 0; i < FetchRange; i++)
{
AddBlockNumber(fetchStart + i);
}
}
private void EnsureRecentBlockIfNecessary(DateTime moment, TimeSpan timePerBlock)
{
var max = entries.Keys.Max();
var latest = entries[max];
var maxRetry = 10;
while (moment > latest.Utc)
{
var newBlock = AddCurrentBlock();
if (newBlock.BlockNumber == latest.BlockNumber)
{
maxRetry--;
if (maxRetry == 0) throw new Exception("Unable to fetch recent block after 10x tries.");
Thread.Sleep(timePerBlock);
}
}
}
private BlockTimeEntry AddBlockNumber(decimal blockNumber)
{
return AddBlockNumber(Convert.ToUInt64(blockNumber));
}
private BlockTimeEntry AddBlockNumber(ulong blockNumber)
{
if (entries.ContainsKey(blockNumber))
{
return entries[blockNumber];
}
if (entries.Count > MaxEntries)
{
entries.Clear();
Initialize();
}
var time = GetTimestampFromBlock(blockNumber);
var entry = new BlockTimeEntry(blockNumber, time);
entries.Add(blockNumber, entry);
return entry;
}
private TimeSpan EstimateTimePerBlock()
{
var min = entries.Keys.Min();
var max = entries.Keys.Max();
var minTime = entries[min].Utc;
var maxTime = entries[max].Utc;
var elapsedTime = maxTime - minTime;
double elapsedSeconds = elapsedTime.TotalSeconds;
double numberOfBlocks = max - min;
double secondsPerBlock = elapsedSeconds / numberOfBlocks;
return TimeSpan.FromSeconds(secondsPerBlock);
}
private void Initialize()
{
if (!entries.Any())
{
AddCurrentBlock();
AddBlockNumber(entries.Single().Key - 1);
}
}
private static void AssertMomentIsInPast(DateTime moment)
{
if (moment > DateTime.UtcNow) throw new Exception("Moment must be UTC and must be in the past.");
}
private BlockTimeEntry AddCurrentBlock()
{
var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync());
var blockNumber = number.ToDecimal();
return AddBlockNumber(blockNumber);
}
private DateTime GetTimestampFromBlock(ulong blockNumber)
{
var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(blockNumber)));
return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime;
}
private BlockTimeEntry FindClosestBeforeEntry(DateTime moment)
{
var result = entries.Values.First();
var highestTime = result.Utc;
foreach (var entry in entries.Values)
{
if (entry.Utc > highestTime && entry.Utc < moment)
{
highestTime = entry.Utc;
result = entry;
}
}
return result;
}
private BlockTimeEntry FindClosestAfterEntry(DateTime moment)
{
var result = entries.Values.First();
var lowestTime = result.Utc;
foreach (var entry in entries.Values)
{
if (entry.Utc < lowestTime && entry.Utc > moment)
{
lowestTime = entry.Utc;
result = entry;
}
}
return result;
}
}
}

View File

@ -1,4 +1,5 @@
using Logging;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Contracts;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Web3;
@ -77,5 +78,26 @@ namespace NethereumWorkflow
return false;
}
}
public List<EventLog<TEvent>> GetEvent<TEvent>(string address, DateTime from, DateTime to) where TEvent : IEventDTO, new()
{
if (from >= to) throw new Exception("Time range is invalid.");
var blockTimeFinder = new BlockTimeFinder(web3);
var fromBlock = blockTimeFinder.GetLowestBlockNumberAfter(from);
var toBlock = blockTimeFinder.GetHighestBlockNumberBefore(to);
return GetEvent<TEvent>(address, fromBlock, toBlock);
}
public List<EventLog<TEvent>> GetEvent<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
{
var eventHandler = web3.Eth.GetEvent<TEvent>(address);
var from = new BlockParameter(fromBlockNumber);
var to = new BlockParameter(toBlockNumber);
var blockFilter = Time.Wait(eventHandler.CreateFilterBlockRangeAsync(from, to));
return Time.Wait(eventHandler.GetAllChangesAsync(blockFilter));
}
}
}

View File

@ -1,6 +1,7 @@
using Core;
using KubernetesWorkflow.Types;
using Logging;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Contracts;
using NethereumWorkflow;
@ -20,6 +21,9 @@ namespace GethPlugin
decimal? GetSyncedBlockNumber();
bool IsContractAvailable(string abi, string contractAddress);
GethBootstrapNode GetBootstrapRecord();
List<EventLog<TEvent>> GetEvents<TEvent>(string address) where TEvent : IEventDTO, new();
List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new();
List<EventLog<TEvent>> GetEvents<TEvent>(string address, DateTime from, DateTime to) where TEvent : IEventDTO, new();
}
public class DeploymentGethNode : BaseGethNode, IGethNode
@ -133,6 +137,19 @@ namespace GethPlugin
return StartInteraction().IsContractAvailable(abi, contractAddress);
}
public List<EventLog<TEvent>> GetEvents<TEvent>(string address) where TEvent : IEventDTO, new()
{
StartInteraction().GetEvent();
}
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, ulong fromBlockNumber, ulong toBlockNumber) where TEvent : IEventDTO, new()
{
}
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, DateTime from, DateTime to) where TEvent : IEventDTO, new()
{
}
protected abstract NethereumInteraction StartInteraction();
}
}