Adds proof-period report to chain-events.

This commit is contained in:
Ben 2025-03-04 15:24:25 +01:00
parent c5de3dcb4c
commit d6cd7762c4
No known key found for this signature in database
GPG Key ID: 0F16E812E736C24B
10 changed files with 252 additions and 103 deletions

View File

@ -45,7 +45,11 @@
private static string? GetEnvVar(List<string> error, string name)
{
var result = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(result)) error.Add($"'{name}' is not set.");
if (string.IsNullOrEmpty(result))
{
error.Add($"'{name}' is not set.");
return null;
}
return result.Trim();
}
}

View File

@ -50,6 +50,21 @@ namespace NethereumWorkflow
return Time.Wait(handler.QueryAsync<TResult>(contractAddress, function));
}
public TResult Call<TFunction, TResult>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
{
log.Debug(typeof(TFunction).ToString());
var handler = web3.Eth.GetContractQueryHandler<TFunction>();
return Time.Wait(handler.QueryAsync<TResult>(contractAddress, function, new BlockParameter(blockNumber)));
}
public void Call<TFunction>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
{
log.Debug(typeof(TFunction).ToString());
var handler = web3.Eth.GetContractQueryHandler<TFunction>();
var result = Time.Wait(handler.QueryRawAsync(contractAddress, function, new BlockParameter(blockNumber)));
var aaaa = 0;
}
public string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
{
log.Debug();

View File

@ -46,10 +46,12 @@ namespace CodexContractsPlugin.ChainMonitor
this.contracts = contracts;
handler = changeHandler;
TotalSpan = new TimeRange(startUtc, startUtc);
PeriodMonitor = new PeriodMonitor(this.log, contracts);
}
public TimeRange TotalSpan { get; private set; }
public IChainStateRequest[] Requests => requests.ToArray();
public PeriodMonitor PeriodMonitor { get; }
public int Update()
{
@ -88,11 +90,17 @@ namespace CodexContractsPlugin.ChainMonitor
{
var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray();
ApplyEvents(b, blockEvents, eventUtc);
UpdatePeriodMonitor(b, eventUtc);
eventUtc += spanPerBlock;
}
}
private void UpdatePeriodMonitor(ulong blockNumber, DateTime eventUtc)
{
PeriodMonitor.Update(blockNumber, eventUtc, Requests);
}
private void ApplyEvents(ulong blockNumber, IHasBlock[] blockEvents, DateTime eventsUtc)
{
foreach (var e in blockEvents)

View File

@ -0,0 +1,99 @@
using Logging;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public class PeriodMonitor
{
private readonly ILog log;
private readonly ICodexContracts contracts;
private readonly List<PeriodReport> reports = new List<PeriodReport>();
private ulong? currentPeriod = null;
public PeriodMonitor(ILog log, ICodexContracts contracts)
{
this.log = log;
this.contracts = contracts;
}
public void Update(ulong blockNumber, DateTime eventUtc, IChainStateRequest[] requests)
{
var period = contracts.GetPeriodNumber(eventUtc);
if (!currentPeriod.HasValue)
{
currentPeriod = period;
return;
}
if (period == currentPeriod.Value) return;
CreateReportForPeriod(blockNumber - 1, currentPeriod.Value, requests);
currentPeriod = period;
}
public PeriodReport[] GetAndClearReports()
{
var result = reports.ToArray();
reports.Clear();
return result;
}
private void CreateReportForPeriod(ulong lastBlockInPeriod, ulong periodNumber, IChainStateRequest[] requests)
{
log.Log("Creating report for period " + periodNumber);
ulong total = 0;
ulong required = 0;
var missed = new List<PeriodProofMissed>();
foreach (var request in requests)
{
for (ulong slotIndex = 0; slotIndex < request.Request.Ask.Slots; slotIndex++)
{
var state = contracts.GetProofState(request.Request, slotIndex, lastBlockInPeriod, periodNumber);
total++;
if (state.Required)
{
required++;
if (state.Missing)
{
var idx = Convert.ToInt32(slotIndex);
var host = request.Hosts.GetHost(idx);
missed.Add(new PeriodProofMissed(host, request, idx));
}
}
}
}
reports.Add(new PeriodReport(periodNumber, total, required, missed.ToArray()));
}
}
public class PeriodReport
{
public PeriodReport(ulong periodNumber, ulong totalNumSlots, ulong totalProofsRequired, PeriodProofMissed[] missedProofs)
{
PeriodNumber = periodNumber;
TotalNumSlots = totalNumSlots;
TotalProofsRequired = totalProofsRequired;
MissedProofs = missedProofs;
}
public ulong PeriodNumber { get; }
public ulong TotalNumSlots { get; }
public ulong TotalProofsRequired { get; }
public PeriodProofMissed[] MissedProofs { get; }
}
public class PeriodProofMissed
{
public PeriodProofMissed(EthAddress? host, IChainStateRequest request, int slotIndex)
{
Host = host;
Request = request;
SlotIndex = slotIndex;
}
public EthAddress? Host { get; }
public IChainStateRequest Request { get; }
public int SlotIndex { get; }
}
}

View File

@ -3,6 +3,8 @@ using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.ABI;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Contracts;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Util;
using NethereumWorkflow;
@ -26,7 +28,21 @@ namespace CodexContractsPlugin
ICodexContractsEvents GetEvents(BlockInterval blockInterval);
EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex);
RequestState GetRequestState(Request request);
ulong GetPeriodNumber(DateTime utc);
void WaitUntilNextPeriod();
ProofState GetProofState(Request storageRequest, decimal slotIndex, ulong blockNumber, ulong period);
}
public class ProofState
{
public ProofState(bool required, bool missing)
{
Required = required;
Missing = missing;
}
public bool Required { get; }
public bool Missing { get; }
}
[JsonConverter(typeof(StringEnumConverter))]
@ -89,19 +105,23 @@ namespace CodexContractsPlugin
return new CodexContractsEvents(log, gethNode, Deployment, blockInterval);
}
public EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex)
public byte[] GetSlotId(Request request, decimal slotIndex)
{
var encoder = new ABIEncode();
var encoded = encoder.GetABIEncoded(
new ABIValue("bytes32", storageRequest.RequestId),
new ABIValue("bytes32", request.RequestId),
new ABIValue("uint256", slotIndex.ToBig())
);
var hashed = Sha3Keccack.Current.CalculateHash(encoded);
return Sha3Keccack.Current.CalculateHash(encoded);
}
public EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex)
{
var slotId = GetSlotId(storageRequest, slotIndex);
var func = new GetHostFunction
{
SlotId = hashed
SlotId = slotId
};
var address = gethNode.Call<GetHostFunction, string>(Deployment.MarketplaceAddress, func);
if (string.IsNullOrEmpty(address)) return null;
@ -117,6 +137,15 @@ namespace CodexContractsPlugin
return gethNode.Call<RequestStateFunction, RequestState>(Deployment.MarketplaceAddress, func);
}
public ulong GetPeriodNumber(DateTime utc)
{
DateTimeOffset utco = DateTime.SpecifyKind(utc, DateTimeKind.Utc);
var now = utco.ToUnixTimeSeconds();
var periodSeconds = (int)Deployment.Config.Proofs.Period;
var result = now / periodSeconds;
return Convert.ToUInt64(result);
}
public void WaitUntilNextPeriod()
{
log.Log("Waiting until next proof period...");
@ -126,6 +155,52 @@ namespace CodexContractsPlugin
Thread.Sleep(TimeSpan.FromSeconds(secondsLeft + 1));
}
public ProofState GetProofState(Request storageRequest, decimal slotIndex, ulong blockNumber, ulong period)
{
var slotId = GetSlotId(storageRequest, slotIndex);
var required = IsProofRequired(slotId, blockNumber);
if (!required) return new ProofState(false, false);
var missing = IsProofMissing(slotId, blockNumber, period);
return new ProofState(required, missing);
}
private bool IsProofRequired(byte[] slotId, ulong blockNumber)
{
var func = new IsProofRequiredFunction
{
Id = slotId
};
var result = gethNode.Call<IsProofRequiredFunction, IsProofRequiredOutputDTO>(Deployment.MarketplaceAddress, func, blockNumber);
return result.ReturnValue1;
}
private bool IsProofMissing(byte[] slotId, ulong blockNumber, ulong period)
{
try
{
var funcB = new MarkProofAsMissingFunction
{
SlotId = slotId,
Period = period
};
gethNode.Call<MarkProofAsMissingFunction>(Deployment.MarketplaceAddress, funcB, blockNumber);
}
catch (AggregateException exc)
{
if (exc.InnerExceptions.Count == 1)
{
if (exc.InnerExceptions[0].GetType() == typeof(SmartContractCustomErrorRevertException))
{
return false;
}
}
throw;
}
return true;
}
private ContractInteractions StartInteraction()
{
return new ContractInteractions(log, gethNode);

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,8 @@ namespace GethPlugin
string SendEth(IHasEthAddress account, Ether eth);
string SendEth(EthAddress account, Ether eth);
TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
TResult Call<TFunction, TResult>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new();
void Call<TFunction>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new();
string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
Transaction GetTransaction(string transactionHash);
decimal? GetSyncedBlockNumber();
@ -131,6 +133,16 @@ namespace GethPlugin
return StartInteraction().Call<TFunction, TResult>(contractAddress, function);
}
public TResult Call<TFunction, TResult>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
{
return StartInteraction().Call<TFunction, TResult>(contractAddress, function, blockNumber);
}
public void Call<TFunction>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
{
StartInteraction().Call(contractAddress, function, blockNumber);
}
public string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
{
return StartInteraction().SendTransaction(contractAddress, function);

View File

@ -102,6 +102,28 @@ namespace TestNetRewarder
errors.Add(msg);
}
public void ProcessPeriodReports(PeriodReport[] periodReports)
{
var lines = periodReports.Select(FormatPeriodReport).ToList();
lines.Insert(0, FormatPeriodReportLine("period", "totalSlots", "required", "missed"));
AddBlock(0, "Proof system report", lines.ToArray());
}
private string FormatPeriodReport(PeriodReport report)
{
return FormatPeriodReportLine(
periodNumber: report.PeriodNumber.ToString(),
totalSlots: report.TotalNumSlots.ToString(),
totalRequired: report.TotalProofsRequired.ToString(),
totalMissed: report.MissedProofs.Length.ToString()
);
}
private string FormatPeriodReportLine(string periodNumber, string totalSlots, string totalRequired, string totalMissed)
{
return $"{periodNumber.PadLeft(10)} {totalSlots.PadLeft(10)} {totalRequired.PadLeft(10)} {totalMissed.PadLeft(10)}";
}
private void AddRequestBlock(RequestEvent requestEvent, string eventName, params string[] content)
{
var blockNumber = $"[{requestEvent.Block.BlockNumber} {FormatDateTime(requestEvent.Block.Utc)}]";

View File

@ -1,97 +0,0 @@
using CodexContractsPlugin;
using CodexContractsPlugin.Marketplace;
using Newtonsoft.Json;
using Utils;
namespace TestNetRewarder
{
public class HistoricState
{
private readonly List<StorageRequest> storageRequests = new List<StorageRequest>();
public StorageRequest[] StorageRequests { get { return storageRequests.ToArray(); } }
public void ProcessNewRequests(Request[] requests)
{
storageRequests.AddRange(requests.Select(r => new StorageRequest(r)));
}
public void UpdateStorageRequests(ICodexContracts contracts)
{
foreach (var r in storageRequests) r.Update(contracts);
}
public void CleanUpOldRequests()
{
storageRequests.RemoveAll(r =>
r.State == RequestState.Cancelled ||
r.State == RequestState.Finished ||
r.State == RequestState.Failed
);
}
public string EntireString()
{
return JsonConvert.SerializeObject(StorageRequests);
}
public HistoricState()
{
}
public HistoricState(StorageRequest[] requests)
{
storageRequests.AddRange(requests);
}
}
public class StorageRequest
{
public StorageRequest(Request request)
{
Request = request;
Hosts = Array.Empty<EthAddress>();
}
public Request Request { get; }
public EthAddress[] Hosts { get; private set; }
public RequestState State { get; private set; }
[JsonIgnore]
public bool RecentlyStarted { get; private set; }
[JsonIgnore]
public bool RecentlyFinished { get; private set; }
public void Update(ICodexContracts contracts)
{
var newHosts = GetHosts(contracts);
var newState = contracts.GetRequestState(Request);
RecentlyStarted =
State == RequestState.New &&
newState == RequestState.Started;
RecentlyFinished =
State == RequestState.Started &&
newState == RequestState.Finished;
State = newState;
Hosts = newHosts;
}
private EthAddress[] GetHosts(ICodexContracts contracts)
{
var result = new List<EthAddress>();
for (decimal i = 0; i < Request.Ask.Slots; i++)
{
var host = contracts.GetSlotHost(Request, i);
if (host != null) result.Add(host);
}
return result.ToArray();
}
}
}

View File

@ -14,12 +14,14 @@ namespace TestNetRewarder
private readonly Configuration config;
private readonly BotClient client;
private readonly ILog log;
private DateTime lastPeriodUpdateUtc;
public Processor(Configuration config, BotClient client, ICodexContracts contracts, ILog log)
{
this.config = config;
this.client = client;
this.log = log;
lastPeriodUpdateUtc = DateTime.UtcNow;
builder = new RequestBuilder();
rewardChecker = new RewardChecker(builder);
@ -68,6 +70,7 @@ namespace TestNetRewarder
private async Task<int> ProcessEvents(TimeRange timeRange)
{
var numberOfChainEvents = chainState.Update(timeRange.To);
ProcessPeriodUpdate();
var events = eventsFormatter.GetEvents();
var errors = eventsFormatter.GetErrors();
@ -79,5 +82,13 @@ namespace TestNetRewarder
}
return numberOfChainEvents;
}
private void ProcessPeriodUpdate()
{
if (DateTime.UtcNow < (lastPeriodUpdateUtc + TimeSpan.FromHours(1.0))) return;
lastPeriodUpdateUtc = DateTime.UtcNow;
eventsFormatter.ProcessPeriodReports(chainState.PeriodMonitor.GetAndClearReports());
}
}
}