Better chain state representation

This commit is contained in:
Ben 2024-06-10 14:04:25 +02:00
parent ac7d323201
commit cc2513bd2f
No known key found for this signature in database
GPG Key ID: 541B9D8C9F1426A1
5 changed files with 63 additions and 148 deletions

View File

@ -24,7 +24,7 @@ namespace CodexContractsPlugin.ChainMonitor
private ChainState(ILog log, IChainStateChangeHandler changeHandler, TimeRange timeRange) private ChainState(ILog log, IChainStateChangeHandler changeHandler, TimeRange timeRange)
{ {
this.log = log; this.log = new LogPrefixer(log, "(ChainState) ");
handler = changeHandler; handler = changeHandler;
TotalSpan = timeRange; TotalSpan = timeRange;
} }
@ -39,7 +39,21 @@ namespace CodexContractsPlugin.ChainMonitor
public TimeRange TotalSpan { get; private set; } public TimeRange TotalSpan { get; private set; }
public IChainStateRequest[] Requests => requests.ToArray(); public IChainStateRequest[] Requests => requests.ToArray();
public void Apply(ChainEvents events) public void Update(ICodexContracts contracts)
{
Update(contracts, DateTime.UtcNow);
}
public void Update(ICodexContracts contracts, DateTime toUtc)
{
var span = new TimeRange(TotalSpan.To, toUtc);
var events = ChainEvents.FromTimeRange(contracts, span);
Apply(events);
TotalSpan = new TimeRange(TotalSpan.From, span.To);
}
private void Apply(ChainEvents events)
{ {
if (events.BlockInterval.TimeRange.From < TotalSpan.From) if (events.BlockInterval.TimeRange.From < TotalSpan.From)
throw new Exception("Attempt to update ChainState with set of events from before its current record."); throw new Exception("Attempt to update ChainState with set of events from before its current record.");
@ -52,7 +66,7 @@ namespace CodexContractsPlugin.ChainMonitor
var spanPerBlock = span / numBlocks; var spanPerBlock = span / numBlocks;
var eventUtc = events.BlockInterval.TimeRange.From; var eventUtc = events.BlockInterval.TimeRange.From;
for (var b = events.BlockInterval.From; b < events.BlockInterval.To; b++) for (var b = events.BlockInterval.From; b <= events.BlockInterval.To; b++)
{ {
var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray(); var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray();
ApplyEvents(blockEvents, eventUtc); ApplyEvents(blockEvents, eventUtc);
@ -86,6 +100,7 @@ namespace CodexContractsPlugin.ChainMonitor
private void ApplyEvent(RequestFulfilledEventDTO request) private void ApplyEvent(RequestFulfilledEventDTO request)
{ {
var r = FindRequest(request.RequestId); var r = FindRequest(request.RequestId);
if (r == null) return;
r.UpdateState(RequestState.Started); r.UpdateState(RequestState.Started);
handler.OnRequestFulfilled(r); handler.OnRequestFulfilled(r);
} }
@ -93,6 +108,7 @@ namespace CodexContractsPlugin.ChainMonitor
private void ApplyEvent(RequestCancelledEventDTO request) private void ApplyEvent(RequestCancelledEventDTO request)
{ {
var r = FindRequest(request.RequestId); var r = FindRequest(request.RequestId);
if (r == null) return;
r.UpdateState(RequestState.Cancelled); r.UpdateState(RequestState.Cancelled);
handler.OnRequestCancelled(r); handler.OnRequestCancelled(r);
} }
@ -100,12 +116,16 @@ namespace CodexContractsPlugin.ChainMonitor
private void ApplyEvent(SlotFilledEventDTO request) private void ApplyEvent(SlotFilledEventDTO request)
{ {
var r = FindRequest(request.RequestId); var r = FindRequest(request.RequestId);
if (r == null) return;
r.Log("SlotFilled");
handler.OnSlotFilled(r, request.SlotIndex); handler.OnSlotFilled(r, request.SlotIndex);
} }
private void ApplyEvent(SlotFreedEventDTO request) private void ApplyEvent(SlotFreedEventDTO request)
{ {
var r = FindRequest(request.RequestId); var r = FindRequest(request.RequestId);
if (r == null) return;
r.Log("SlotFreed");
handler.OnSlotFreed(r, request.SlotIndex); handler.OnSlotFreed(r, request.SlotIndex);
} }
@ -122,9 +142,11 @@ namespace CodexContractsPlugin.ChainMonitor
} }
} }
private ChainStateRequest FindRequest(byte[] requestId) private ChainStateRequest? FindRequest(byte[] requestId)
{ {
return requests.Single(r => Equal(r.Request.RequestId, requestId)); var r = requests.SingleOrDefault(r => Equal(r.Request.RequestId, requestId));
if (r == null) log.Log("Unable to find request by ID!");
return r;
} }
private bool Equal(byte[] a, byte[] b) private bool Equal(byte[] a, byte[] b)

View File

@ -24,7 +24,7 @@ namespace CodexContractsPlugin.ChainMonitor
ExpiryUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Expiry); ExpiryUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Expiry);
FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration); FinishedUtc = request.Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration);
log.Log($"Request created as {State}."); log.Log($"Created as {State}.");
} }
public Request Request { get; } public Request Request { get; }
@ -34,8 +34,13 @@ namespace CodexContractsPlugin.ChainMonitor
public void UpdateState(RequestState newState) public void UpdateState(RequestState newState)
{ {
log.Log($"Request transit: {State} -> {newState}"); Log($"Transit: {State} -> {newState}");
State = newState; State = newState;
} }
public void Log(string msg)
{
log.Log($"Request '{Request.Id}': {msg}");
}
} }
} }

View File

@ -17,6 +17,17 @@ namespace CodexContractsPlugin.Marketplace
public byte[] RequestId { get; set; } public byte[] RequestId { get; set; }
public EthAddress ClientAddress { get { return new EthAddress(Client); } } public EthAddress ClientAddress { get { return new EthAddress(Client); } }
[JsonIgnore]
public string Id
{
get
{
var id = "";
foreach (var b in RequestId) id += b.ToString();
return id;
}
}
} }
public partial class RequestFulfilledEventDTO : IHasBlock public partial class RequestFulfilledEventDTO : IHasBlock

View File

@ -6,6 +6,7 @@ namespace CodexPlugin
{ {
public interface IStoragePurchaseContract public interface IStoragePurchaseContract
{ {
string PurchaseId { get; }
void WaitForStorageContractSubmitted(); void WaitForStorageContractSubmitted();
void WaitForStorageContractStarted(); void WaitForStorageContractStarted();
void WaitForStorageContractFinished(); void WaitForStorageContractFinished();

View File

@ -1,16 +1,13 @@
using CodexContractsPlugin; using CodexContractsPlugin;
using CodexContractsPlugin.ChainMonitor; using CodexContractsPlugin.ChainMonitor;
using CodexContractsPlugin.Marketplace;
using CodexDiscordBotPlugin; using CodexDiscordBotPlugin;
using CodexPlugin; using CodexPlugin;
using Core; using Core;
using DiscordRewards; using DiscordRewards;
using GethPlugin; using GethPlugin;
using KubernetesWorkflow.Types; using KubernetesWorkflow.Types;
using Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using NUnit.Framework; using NUnit.Framework;
using TestNetRewarder;
using Utils; using Utils;
namespace CodexTests.UtilityTests namespace CodexTests.UtilityTests
@ -33,9 +30,6 @@ namespace CodexTests.UtilityTests
var contracts = Ci.StartCodexContracts(geth); var contracts = Ci.StartCodexContracts(geth);
var gethInfo = CreateGethInfo(geth, contracts); var gethInfo = CreateGethInfo(geth, contracts);
var monitor = new ChainMonitor(contracts, geth, GetTestLog());
monitor.Start();
var botContainer = StartDiscordBot(gethInfo); var botContainer = StartDiscordBot(gethInfo);
StartHosts(geth, contracts); StartHosts(geth, contracts);
@ -44,49 +38,45 @@ namespace CodexTests.UtilityTests
var client = StartClient(geth, contracts); var client = StartClient(geth, contracts);
var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange()); var events = ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange());
var chainState = CodexContractsPlugin.ChainMonitor.ChainState.FromEvents( var chainState = ChainState.FromEvents(
GetTestLog(), GetTestLog(),
events, events,
new DoNothingChainEventHandler()); new DoNothingChainEventHandler());
var apiCalls = new RewardApiCalls(Ci, botContainer); var apiCalls = new RewardApiCalls(Ci, botContainer);
apiCalls.Start(OnCommand); apiCalls.Start(OnCommand);
var rewarderLog = new RewarderLogMonitor(Ci, rewarderContainer.Containers.Single());
rewarderLog.Start(l => Log("Rewarder ChainState: " + l));
var purchaseContract = ClientPurchasesStorage(client); var purchaseContract = ClientPurchasesStorage(client);
chainState.Update(contracts);
Assert.That(chainState.Requests.Length, Is.EqualTo(1));
purchaseContract.WaitForStorageContractStarted();
chainState.Update(contracts);
purchaseContract.WaitForStorageContractFinished(); purchaseContract.WaitForStorageContractFinished();
rewarderLog.Stop();
apiCalls.Stop();
monitor.Stop();
Log("Done!");
Thread.Sleep(rewarderInterval * 2); Thread.Sleep(rewarderInterval * 2);
chainState.Apply(ChainEvents.FromTimeRange(contracts, GetTestRunTimeRange())); apiCalls.Stop();
chainState.Update(contracts);
Log("Seen:");
foreach (var seen in rewardsSeen)
{
Log(seen.ToString());
}
Log("");
foreach (var r in repo.Rewards) foreach (var r in repo.Rewards)
{ {
var seen = rewardsSeen.Any(s => r.RoleId == s); var seen = rewardsSeen.Any(s => r.RoleId == s);
Log($"{r.RoleId} = {seen}"); Log($"{Lookup(r.RoleId)} = {seen}");
} }
Assert.That(repo.Rewards.All(r => rewardsSeen.Contains(r.RoleId))); Assert.That(repo.Rewards.All(r => rewardsSeen.Contains(r.RoleId)));
} }
private string Lookup(ulong rewardId)
{
var reward = repo.Rewards.Single(r => r.RoleId == rewardId);
return $"({rewardId})'{reward.Message}'";
}
private void OnCommand(GiveRewardsCommand call) private void OnCommand(GiveRewardsCommand call)
{ {
if (call.Averages.Any()) Log($"API call: {call.Averages.Length} average."); if (call.Averages.Any()) Log($"API call: {call.Averages.Length} average.");
@ -292,55 +282,6 @@ namespace CodexTests.UtilityTests
} }
} }
public class RewarderLogMonitor
{
private readonly ContainerFileMonitor monitor;
private readonly Dictionary<string, GiveRewardsCommand> commands = new Dictionary<string, GiveRewardsCommand>();
public RewarderLogMonitor(CoreInterface ci, RunningContainer botContainer)
{
monitor = new ContainerFileMonitor(ci, botContainer, "/app/datapath/logs/testnetrewarder.log");
}
public void Start(Action<string> onCommand)
{
monitor.Start(l => ProcessLine(l, onCommand));
}
public void Stop()
{
monitor.Stop();
}
private void ProcessLine(string line, Action<string> log)
{
//$"ChainState=[{JsonConvert.SerializeObject(this)}]" +
//$"HistoricState=[{historicState.EntireString()}]";
//var stateOpenTag = "ChainState=[";
//var historicOpenTag = "]HistoricState=[";
//if (!line.Contains(stateOpenTag)) return;
//if (!line.Contains(historicOpenTag)) return;
//var stateStr = Between(line, stateOpenTag, historicOpenTag);
//var historicStr = Between(line, historicOpenTag, "]");
//var chainState = JsonConvert.DeserializeObject<ChainState>(stateStr);
//var historicState = JsonConvert.DeserializeObject<TestNetRewarder.StorageRequest[]>(historicStr)!;
//chainState!.Set(new HistoricState(historicState));
//log(string.Join(",", chainState!.GenerateOverview()));
}
private string Between(string s, string open, string close)
{
var start = s.IndexOf(open) + open.Length;
var end = s.LastIndexOf(close);
return s.Substring(start, end - start);
}
}
public class ContainerFileMonitor public class ContainerFileMonitor
{ {
private readonly CoreInterface ci; private readonly CoreInterface ci;
@ -395,70 +336,5 @@ namespace CodexTests.UtilityTests
} }
} }
} }
public class ChainMonitor
{
private readonly ICodexContracts contracts;
private readonly IGethNode geth;
private readonly ILog log;
private readonly CancellationTokenSource cts = new CancellationTokenSource();
private Task worker = Task.CompletedTask;
private DateTime last = DateTime.UtcNow;
public ChainMonitor(ICodexContracts contracts, IGethNode geth, ILog log)
{
this.contracts = contracts;
this.geth = geth;
this.log = log;
}
public void Start()
{
last = DateTime.UtcNow;
worker = Task.Run(Worker);
}
public void Stop()
{
cts.Cancel();
worker.Wait();
}
private void Worker()
{
while (!cts.IsCancellationRequested)
{
Thread.Sleep(TimeSpan.FromSeconds(10));
if (cts.IsCancellationRequested) return;
Update();
}
}
private void Update()
{
//var start = last;
//var stop = DateTime.UtcNow;
//last = stop;
//var range = geth.ConvertTimeRangeToBlockRange(new TimeRange(start, stop));
//throw new Exception();
//LogEvents(nameof(contracts.GetStorageRequests), contracts.GetStorageRequests, range);
//LogEvents(nameof(contracts.GetRequestFulfilledEvents), contracts.GetRequestFulfilledEvents, range);
//LogEvents(nameof(contracts.GetRequestCancelledEvents), contracts.GetRequestCancelledEvents, range);
//LogEvents(nameof(contracts.GetSlotFilledEvents), contracts.GetSlotFilledEvents, range);
//LogEvents(nameof(contracts.GetSlotFreedEvents), contracts.GetSlotFreedEvents, range);
}
private void LogEvents(string n, Func<BlockInterval, object> f, BlockInterval r)
{
var a = (object[])f(r);
a.ToList().ForEach(request => log.Log(n + " - " + JsonConvert.SerializeObject(request)));
}
}
} }
} }