318 lines
11 KiB
C#
Raw Normal View History

2023-12-20 09:48:22 +01:00
using Logging;
using Nethereum.RPC.Eth.DTOs;
2023-12-19 15:30:46 +01:00
using Nethereum.Web3;
using Utils;
namespace NethereumWorkflow
{
2023-12-20 10:33:44 +01:00
public partial class BlockTimeFinder
2023-12-19 15:30:46 +01:00
{
private const ulong FetchRange = 6;
private const int MaxEntries = 1024;
2023-12-20 10:33:44 +01:00
private static readonly Dictionary<ulong, BlockTimeEntry> entries = new Dictionary<ulong, BlockTimeEntry>();
2023-12-19 15:30:46 +01:00
private readonly Web3 web3;
2023-12-20 09:48:22 +01:00
private readonly ILog log;
2023-12-19 15:30:46 +01:00
2023-12-20 09:48:22 +01:00
public BlockTimeFinder(Web3 web3, ILog log)
2023-12-19 15:30:46 +01:00
{
this.web3 = web3;
2023-12-20 09:48:22 +01:00
this.log = log;
2023-12-19 15:30:46 +01:00
}
2024-02-19 15:20:12 +01:00
public BlockRange ConvertTimeRangeToBlockRange(TimeRange timeRange)
{
var lowest = GetLowestBlockNumberAfter(timeRange.From);
var highest = GetHighestBlockNumberBefore(timeRange.To);
var fromBlock = Math.Min(lowest, highest);
var toBlock = Math.Max(lowest, highest);
return new BlockRange(fromBlock, toBlock);
}
2023-12-19 15:30:46 +01:00
public ulong GetHighestBlockNumberBefore(DateTime moment)
{
AssertMomentIsInPast(moment);
Initialize();
2024-02-19 15:20:12 +01:00
var result = GetHighestBlockBefore(moment);
log.Log($"Highest block before [{moment.ToString("o")}] = {result}");
return result;
}
public ulong GetLowestBlockNumberAfter(DateTime moment)
{
AssertMomentIsInPast(moment);
Initialize();
2024-02-19 15:20:12 +01:00
var result = GetLowestBlockAfter(moment);
log.Log($"Lowest block after [{moment.ToString("o")}] = {result}");
return result;
}
private ulong GetHighestBlockBefore(DateTime moment)
{
2023-12-19 15:30:46 +01:00
var closestBefore = FindClosestBeforeEntry(moment);
var closestAfter = FindClosestAfterEntry(moment);
2023-12-20 09:48:22 +01:00
2023-12-20 10:33:44 +01:00
if (closestBefore != null &&
closestAfter != null &&
closestBefore.Utc < moment &&
2023-12-19 15:30:46 +01:00
closestAfter.Utc > moment &&
closestBefore.BlockNumber + 1 == closestAfter.BlockNumber)
{
return closestBefore.BlockNumber;
}
2024-02-19 14:56:49 +01:00
var newBlocks = FetchBlocksAround(moment);
if (newBlocks == 0)
{
2024-02-19 15:20:12 +01:00
log.Debug("Didn't find any new blocks.");
2024-02-19 14:56:49 +01:00
if (closestBefore != null) return closestBefore.BlockNumber;
throw new Exception("Failed to find highest before.");
}
return GetHighestBlockBefore(moment);
2023-12-19 15:30:46 +01:00
}
private ulong GetLowestBlockAfter(DateTime moment)
2023-12-19 15:30:46 +01:00
{
var closestBefore = FindClosestBeforeEntry(moment);
var closestAfter = FindClosestAfterEntry(moment);
2023-12-20 10:33:44 +01:00
if (closestBefore != null &&
closestAfter != null &&
closestBefore.Utc < moment &&
2023-12-19 15:30:46 +01:00
closestAfter.Utc > moment &&
closestBefore.BlockNumber + 1 == closestAfter.BlockNumber)
{
return closestAfter.BlockNumber;
}
2024-02-19 14:56:49 +01:00
var newBlocks = FetchBlocksAround(moment);
if (newBlocks == 0)
{
2024-02-19 15:20:12 +01:00
log.Debug("Didn't find any new blocks.");
2024-02-19 14:56:49 +01:00
if (closestAfter != null) return closestAfter.BlockNumber;
throw new Exception("Failed to find lowest before.");
}
return GetLowestBlockAfter(moment);
2023-12-19 15:30:46 +01:00
}
2024-02-19 14:56:49 +01:00
private int FetchBlocksAround(DateTime moment)
2023-12-19 15:30:46 +01:00
{
var timePerBlock = EstimateTimePerBlock();
log.Debug("Fetching blocks around " + moment.ToString("o") + " timePerBlock: " + timePerBlock.TotalSeconds);
2023-12-19 15:30:46 +01:00
EnsureRecentBlockIfNecessary(moment, timePerBlock);
var max = entries.Keys.Max();
2023-12-20 10:33:44 +01:00
var blockDifference = CalculateBlockDifference(moment, timePerBlock, max);
2023-12-19 15:30:46 +01:00
2024-02-19 14:56:49 +01:00
return
FetchUp(max, blockDifference) +
FetchDown(max, blockDifference);
2023-12-20 10:33:44 +01:00
}
2023-12-19 15:30:46 +01:00
2024-02-19 14:56:49 +01:00
private int FetchDown(ulong max, ulong blockDifference)
2023-12-20 10:33:44 +01:00
{
2024-02-19 14:56:49 +01:00
var target = GetTarget(max, blockDifference);
2023-12-20 09:48:22 +01:00
var fetchDown = FetchRange;
2024-02-19 14:56:49 +01:00
var newBlocks = 0;
2023-12-20 10:33:44 +01:00
while (fetchDown > 0)
2023-12-19 15:30:46 +01:00
{
2023-12-20 09:48:22 +01:00
if (!entries.ContainsKey(target))
{
2024-02-19 14:56:49 +01:00
var newBlock = AddBlockNumber("FD" + fetchDown, target);
if (newBlock == null) return newBlocks;
newBlocks++;
2023-12-20 10:33:44 +01:00
fetchDown--;
2023-12-20 09:48:22 +01:00
}
2023-12-20 10:33:44 +01:00
target--;
2024-02-19 14:56:49 +01:00
if (target <= 0) return newBlocks;
2023-12-20 09:48:22 +01:00
}
2024-02-19 14:56:49 +01:00
return newBlocks;
2023-12-20 10:33:44 +01:00
}
2023-12-20 09:48:22 +01:00
2024-02-19 14:56:49 +01:00
private int FetchUp(ulong max, ulong blockDifference)
2023-12-20 10:33:44 +01:00
{
2024-02-19 14:56:49 +01:00
var target = GetTarget(max, blockDifference);
2023-12-20 10:33:44 +01:00
var fetchUp = FetchRange;
2024-02-19 14:56:49 +01:00
var newBlocks = 0;
2023-12-20 10:33:44 +01:00
while (fetchUp > 0)
2023-12-20 09:48:22 +01:00
{
if (!entries.ContainsKey(target))
{
2024-02-19 14:56:49 +01:00
var newBlock = AddBlockNumber("FU" + fetchUp, target);
if (newBlock == null) return newBlocks;
newBlocks++;
2023-12-20 10:33:44 +01:00
fetchUp--;
2023-12-20 09:48:22 +01:00
}
2023-12-20 10:33:44 +01:00
target++;
2024-02-19 14:56:49 +01:00
if (target >= max) return newBlocks;
2023-12-19 15:30:46 +01:00
}
2024-02-19 14:56:49 +01:00
return newBlocks;
}
private ulong GetTarget(ulong max, ulong blockDifference)
{
if (max <= blockDifference) return 1;
return max - blockDifference;
2023-12-19 15:30:46 +01:00
}
2023-12-20 10:33:44 +01:00
private ulong CalculateBlockDifference(DateTime moment, TimeSpan timePerBlock, ulong 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);
if (blockDifference < 1) blockDifference = 1;
return blockDifference;
}
2023-12-19 15:30:46 +01:00
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();
2023-12-20 09:48:22 +01:00
if (newBlock == null || newBlock.BlockNumber == latest.BlockNumber)
2023-12-19 15:30:46 +01:00
{
maxRetry--;
if (maxRetry == 0) throw new Exception("Unable to fetch recent block after 10x tries.");
Thread.Sleep(timePerBlock);
}
2023-12-20 10:33:44 +01:00
max = entries.Keys.Max();
latest = entries[max];
2023-12-19 15:30:46 +01:00
}
}
2024-02-19 14:56:49 +01:00
private BlockTimeEntry? AddBlockNumber(string a, decimal blockNumber)
2023-12-19 15:30:46 +01:00
{
2024-02-19 14:56:49 +01:00
return AddBlockNumber(a, Convert.ToUInt64(blockNumber));
2023-12-19 15:30:46 +01:00
}
2024-02-19 14:56:49 +01:00
private BlockTimeEntry? AddBlockNumber(string a, ulong blockNumber)
2023-12-19 15:30:46 +01:00
{
2024-02-19 15:20:12 +01:00
log.Debug(a + " - Adding blockNumber: " + blockNumber);
2023-12-19 15:30:46 +01:00
if (entries.ContainsKey(blockNumber))
{
return entries[blockNumber];
}
if (entries.Count > MaxEntries)
{
log.Debug("Entries cleared!");
2023-12-19 15:30:46 +01:00
entries.Clear();
Initialize();
}
var time = GetTimestampFromBlock(blockNumber);
if (time == null)
{
2024-02-19 15:20:12 +01:00
log.Debug("Failed to get block for number: " + blockNumber);
return null;
}
2023-12-20 09:48:22 +01:00
var entry = new BlockTimeEntry(blockNumber, time.Value);
log.Debug("Found block " + entry.BlockNumber + " at " + entry.Utc.ToString("o"));
2023-12-19 15:30:46 +01:00
entries.Add(blockNumber, entry);
return entry;
}
private TimeSpan EstimateTimePerBlock()
{
var min = entries.Keys.Min();
var max = entries.Keys.Max();
2023-12-20 10:33:44 +01:00
var clippedMin = Math.Max(max - 100, min);
2023-12-19 15:30:46 +01:00
var minTime = entries[min].Utc;
2024-02-19 14:56:49 +01:00
var clippedMinBlock = AddBlockNumber("EST", clippedMin);
2023-12-20 10:33:44 +01:00
if (clippedMinBlock != null) minTime = clippedMinBlock.Utc;
2023-12-19 15:30:46 +01:00
var maxTime = entries[max].Utc;
var elapsedTime = maxTime - minTime;
double elapsedSeconds = elapsedTime.TotalSeconds;
double numberOfBlocks = max - min;
double secondsPerBlock = elapsedSeconds / numberOfBlocks;
var result = TimeSpan.FromSeconds(secondsPerBlock);
if (result.TotalSeconds < 1.0) result = TimeSpan.FromSeconds(1.0);
return result;
2023-12-19 15:30:46 +01:00
}
private void Initialize()
{
if (!entries.Any())
{
AddCurrentBlock();
2024-02-19 14:56:49 +01:00
AddBlockNumber("INIT", entries.Single().Key - 1);
2023-12-19 15:30:46 +01:00
}
}
private static void AssertMomentIsInPast(DateTime moment)
{
if (moment > DateTime.UtcNow) throw new Exception("Moment must be UTC and must be in the past.");
}
2023-12-20 09:48:22 +01:00
private BlockTimeEntry? AddCurrentBlock()
2023-12-19 15:30:46 +01:00
{
var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync());
var blockNumber = number.ToDecimal();
2024-02-19 14:56:49 +01:00
return AddBlockNumber("CUR", blockNumber);
2023-12-19 15:30:46 +01:00
}
2023-12-20 09:48:22 +01:00
private DateTime? GetTimestampFromBlock(ulong blockNumber)
2023-12-19 15:30:46 +01:00
{
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)
{
2024-02-19 14:56:49 +01:00
log.Error(nameof(GetTimestampFromBlock) + " Exception: " + ex);
throw;
}
2023-12-19 15:30:46 +01:00
}
2023-12-20 09:48:22 +01:00
private BlockTimeEntry? FindClosestBeforeEntry(DateTime moment)
2023-12-19 15:30:46 +01:00
{
2023-12-20 09:48:22 +01:00
BlockTimeEntry? result = null;
2023-12-19 15:30:46 +01:00
foreach (var entry in entries.Values)
{
2023-12-20 09:48:22 +01:00
if (result == null)
{
if (entry.Utc < moment) result = entry;
}
else
2023-12-19 15:30:46 +01:00
{
2023-12-20 09:48:22 +01:00
if (entry.Utc > result.Utc && entry.Utc < moment) result = entry;
2023-12-19 15:30:46 +01:00
}
}
return result;
}
2023-12-20 09:48:22 +01:00
private BlockTimeEntry? FindClosestAfterEntry(DateTime moment)
2023-12-19 15:30:46 +01:00
{
2023-12-20 09:48:22 +01:00
BlockTimeEntry? result = null;
2023-12-19 15:30:46 +01:00
foreach (var entry in entries.Values)
{
2023-12-20 09:48:22 +01:00
if (result == null)
{
if (entry.Utc > moment) result = entry;
}
else
2023-12-19 15:30:46 +01:00
{
2023-12-20 09:48:22 +01:00
if (entry.Utc < result.Utc && entry.Utc > moment) result = entry;
2023-12-19 15:30:46 +01:00
}
}
return result;
}
}
}