From a846d51c0c75a906ffb8d01cc1bac727271fdf7d Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 31 May 2024 11:19:50 +0200 Subject: [PATCH] Implements ChainState in CodexContracts plugin --- Framework/Utils/BlockInterval.cs | 1 + .../ChainMonitor/ChainEvents.cs | 14 ++ .../ChainMonitor/ChainState.cs | 130 +++++++++++++++++- .../ChainMonitor/ChainStateRequest.cs | 41 ++++++ .../Marketplace/Customizations.cs | 15 +- 5 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs diff --git a/Framework/Utils/BlockInterval.cs b/Framework/Utils/BlockInterval.cs index 2daa8f2..6443048 100644 --- a/Framework/Utils/BlockInterval.cs +++ b/Framework/Utils/BlockInterval.cs @@ -20,6 +20,7 @@ public ulong From { get; } public ulong To { get; } public TimeRange TimeRange { get; } + public ulong NumberOfBlocks => To - From; public override string ToString() { diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs index 51a84d0..25d0851 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainEvents.cs @@ -29,6 +29,20 @@ namespace CodexContractsPlugin.ChainMonitor public SlotFilledEventDTO[] SlotFilled { get; } public SlotFreedEventDTO[] SlotFreed { get; } + public IHasBlock[] All + { + get + { + var all = new List(); + all.AddRange(Requests); + all.AddRange(Fulfilled); + all.AddRange(Cancelled); + all.AddRange(SlotFilled); + all.AddRange(SlotFreed); + return all.ToArray(); + } + } + public static ChainEvents FromBlockInterval(ICodexContracts contracts, BlockInterval blockInterval) { return FromContractEvents(contracts.GetEvents(blockInterval)); diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 6dc95f1..b3870dc 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -1,7 +1,135 @@ -namespace CodexContractsPlugin.ChainMonitor +using CodexContractsPlugin.Marketplace; +using Logging; +using System.Numerics; +using Utils; + +namespace CodexContractsPlugin.ChainMonitor { + public interface IChainStateChangeHandler + { + void OnNewRequest(IChainStateRequest request); + void OnRequestStarted(IChainStateRequest request); + void OnRequestFinished(IChainStateRequest request); + void OnRequestFulfilled(IChainStateRequest request); + void OnRequestCancelled(IChainStateRequest request); + void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex); + void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex); + } + public class ChainState { + private readonly List requests = new List(); + private readonly ILog log; + private readonly IChainStateChangeHandler handler; + private ChainState(ILog log, IChainStateChangeHandler changeHandler, TimeRange timeRange) + { + this.log = log; + handler = changeHandler; + TotalSpan = timeRange; + } + + public static ChainState FromEvents(ILog log, ChainEvents events, IChainStateChangeHandler changeHandler) + { + var state = new ChainState(log, changeHandler, events.BlockInterval.TimeRange); + state.Apply(events); + return state; + } + + public TimeRange TotalSpan { get; private set; } + public IChainStateRequest[] Requests => requests.ToArray(); + + public void Apply(ChainEvents events) + { + if (events.BlockInterval.TimeRange.From < TotalSpan.From) + throw new Exception("Attempt to update ChainState with set of events from before its current record."); + + log.Log($"ChainState updating: {events.BlockInterval}"); + + // Run through each block and apply the events to the state in order. + var span = events.BlockInterval.TimeRange.Duration; + var numBlocks = events.BlockInterval.NumberOfBlocks; + var spanPerBlock = span / numBlocks; + + var eventUtc = events.BlockInterval.TimeRange.From; + for (var b = events.BlockInterval.From; b < events.BlockInterval.To; b++) + { + var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray(); + ApplyEvents(blockEvents, eventUtc); + + eventUtc += spanPerBlock; + } + } + + private void ApplyEvents(IHasBlock[] blockEvents, DateTime eventsUtc) + { + foreach (var e in blockEvents) + { + dynamic d = e; + ApplyEvent(d); + } + + ApplyTimeImplicitEvents(eventsUtc); + } + + private void ApplyEvent(Request request) + { + if (requests.Any(r => Equal(r.Request.RequestId, request.RequestId))) + throw new Exception("Received NewRequest event for id that already exists."); + + var newRequest = new ChainStateRequest(log, request, RequestState.New); + requests.Add(newRequest); + + handler.OnNewRequest(newRequest); + } + + private void ApplyEvent(RequestFulfilledEventDTO request) + { + var r = FindRequest(request.RequestId); + r.UpdateState(RequestState.Started); + handler.OnRequestFulfilled(r); + } + + private void ApplyEvent(RequestCancelledEventDTO request) + { + var r = FindRequest(request.RequestId); + r.UpdateState(RequestState.Cancelled); + handler.OnRequestCancelled(r); + } + + private void ApplyEvent(SlotFilledEventDTO request) + { + var r = FindRequest(request.RequestId); + handler.OnSlotFilled(r, request.SlotIndex); + } + + private void ApplyEvent(SlotFreedEventDTO request) + { + var r = FindRequest(request.RequestId); + handler.OnSlotFreed(r, request.SlotIndex); + } + + private void ApplyTimeImplicitEvents(DateTime eventsUtc) + { + foreach (var r in requests) + { + if (r.State == RequestState.Started + && r.FinishedUtc < eventsUtc) + { + r.UpdateState(RequestState.Finished); + handler.OnRequestFinished(r); + } + } + } + + private ChainStateRequest FindRequest(byte[] requestId) + { + return requests.Single(r => Equal(r.Request.RequestId, requestId)); + } + + private bool Equal(byte[] a, byte[] b) + { + return a.SequenceEqual(b); + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs new file mode 100644 index 0000000..ae735b4 --- /dev/null +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainStateRequest.cs @@ -0,0 +1,41 @@ +using CodexContractsPlugin.Marketplace; +using Logging; + +namespace CodexContractsPlugin.ChainMonitor +{ + public interface IChainStateRequest + { + Request Request { get; } + RequestState State { get; } + DateTime ExpiryUtc { get; } + DateTime FinishedUtc { get; } + } + + public class ChainStateRequest : IChainStateRequest + { + private readonly ILog log; + + public ChainStateRequest(ILog log, Request request, RequestState state) + { + this.log = log; + Request = request; + State = state; + + ExpiryUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Expiry); + FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); + + log.Log($"Request created as {State}."); + } + + public Request Request { get; } + public RequestState State { get; private set; } + public DateTime ExpiryUtc { get; } + public DateTime FinishedUtc { get; } + + public void UpdateState(RequestState newState) + { + log.Log($"Request transit: {State} -> {newState}"); + State = newState; + } + } +} diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index 68427b6..5094e12 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -5,7 +5,12 @@ using Newtonsoft.Json; namespace CodexContractsPlugin.Marketplace { - public partial class Request : RequestBase + public interface IHasBlock + { + BlockTimeEntry Block { get; set; } + } + + public partial class Request : RequestBase, IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } @@ -14,26 +19,26 @@ namespace CodexContractsPlugin.Marketplace public EthAddress ClientAddress { get { return new EthAddress(Client); } } } - public partial class RequestFulfilledEventDTO + public partial class RequestFulfilledEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } } - public partial class RequestCancelledEventDTO + public partial class RequestCancelledEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } } - public partial class SlotFilledEventDTO + public partial class SlotFilledEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; } public EthAddress Host { get; set; } } - public partial class SlotFreedEventDTO + public partial class SlotFreedEventDTO : IHasBlock { [JsonIgnore] public BlockTimeEntry Block { get; set; }