From 9e9b147b68c1ebaea079c754c6e43e3a77f8c2a9 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 14:16:33 +0200 Subject: [PATCH] wip log downloading --- Framework/KubernetesWorkflow/LogHandler.cs | 4 +- Framework/Logging/BaseLog.cs | 2 +- Framework/Logging/LogFile.cs | 14 +- Framework/Logging/LogPrefixer.cs | 11 +- Framework/Logging/TimestampPrefixer.cs | 16 ++ .../MetricsPlugin/MetricsDownloader.cs | 4 +- .../ElasticSearchLogDownloader.cs | 2 +- Tests/DistTestCore/Logs/BaseTestLog.cs | 2 +- TraceContract/ChainRequestTracker.cs | 103 +++++++ TraceContract/ChainTracer.cs | 119 ++++++++ TraceContract/Config.cs | 36 +++ TraceContract/ElasticSearchLogDownloader.cs | 242 +++++++++++++++++ TraceContract/Input.cs | 13 + TraceContract/Output.cs | 79 +++++- TraceContract/Program.cs | 254 +++--------------- 15 files changed, 647 insertions(+), 254 deletions(-) create mode 100644 Framework/Logging/TimestampPrefixer.cs create mode 100644 TraceContract/ChainRequestTracker.cs create mode 100644 TraceContract/ChainTracer.cs create mode 100644 TraceContract/ElasticSearchLogDownloader.cs create mode 100644 TraceContract/Input.cs diff --git a/Framework/KubernetesWorkflow/LogHandler.cs b/Framework/KubernetesWorkflow/LogHandler.cs index 44adcc05..b91a61f9 100644 --- a/Framework/KubernetesWorkflow/LogHandler.cs +++ b/Framework/KubernetesWorkflow/LogHandler.cs @@ -33,7 +33,7 @@ namespace KubernetesWorkflow sourceLog.Log(msg); LogFile.Write(msg); - LogFile.WriteRaw(description); + LogFile.Write(description); } public LogFile LogFile { get; } @@ -43,7 +43,7 @@ namespace KubernetesWorkflow if (line.Contains("Received JSON-RPC response")) return; if (line.Contains("object field not marked with serialize, skipping")) return; - LogFile.WriteRaw(line); + LogFile.Write(line); } } } diff --git a/Framework/Logging/BaseLog.cs b/Framework/Logging/BaseLog.cs index 56ff66ec..217b76b7 100644 --- a/Framework/Logging/BaseLog.cs +++ b/Framework/Logging/BaseLog.cs @@ -65,7 +65,7 @@ namespace Logging public void Raw(string message) { - LogFile.WriteRaw(message); + LogFile.Write(message); } public virtual void AddStringReplace(string from, string to) diff --git a/Framework/Logging/LogFile.cs b/Framework/Logging/LogFile.cs index 2ec94be0..15d2cbfc 100644 --- a/Framework/Logging/LogFile.cs +++ b/Framework/Logging/LogFile.cs @@ -1,6 +1,4 @@ -using Utils; - -namespace Logging +namespace Logging { public class LogFile { @@ -16,11 +14,6 @@ namespace Logging public string Filename { get; private set; } public void Write(string message) - { - WriteRaw($"{GetTimestamp()} {message}"); - } - - public void WriteRaw(string message) { try { @@ -50,11 +43,6 @@ namespace Logging } } - private static string GetTimestamp() - { - return $"[{Time.FormatTimestamp(DateTime.UtcNow)}]"; - } - private void EnsurePathExists(string filename) { var path = new FileInfo(filename).Directory!.FullName; diff --git a/Framework/Logging/LogPrefixer.cs b/Framework/Logging/LogPrefixer.cs index f0f303d6..30c6524d 100644 --- a/Framework/Logging/LogPrefixer.cs +++ b/Framework/Logging/LogPrefixer.cs @@ -24,17 +24,17 @@ public void Debug(string message = "", int skipFrames = 0) { - backingLog.Debug(Prefix + message, skipFrames); + backingLog.Debug(GetPrefix() + message, skipFrames); } public void Error(string message) { - backingLog.Error(Prefix + message); + backingLog.Error(GetPrefix() + message); } public void Log(string message) { - backingLog.Log(Prefix + message); + backingLog.Log(GetPrefix() + message); } public void AddStringReplace(string from, string to) @@ -51,5 +51,10 @@ { return backingLog.GetFullName(); } + + protected virtual string GetPrefix() + { + return Prefix; + } } } diff --git a/Framework/Logging/TimestampPrefixer.cs b/Framework/Logging/TimestampPrefixer.cs new file mode 100644 index 00000000..bc7c3843 --- /dev/null +++ b/Framework/Logging/TimestampPrefixer.cs @@ -0,0 +1,16 @@ +using Utils; + +namespace Logging +{ + public class TimestampPrefixer : LogPrefixer + { + public TimestampPrefixer(ILog backingLog) : base(backingLog) + { + } + + protected override string GetPrefix() + { + return $"[{Time.FormatTimestamp(DateTime.UtcNow)}]"; + } + } +} diff --git a/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs b/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs index 507b1ba7..7e1548b2 100644 --- a/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs +++ b/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs @@ -28,11 +28,11 @@ namespace MetricsPlugin var file = log.CreateSubfile("csv"); log.Log($"Downloading metrics for {nodeName} to file {file.Filename}"); - file.WriteRaw(string.Join(",", headers)); + file.Write(string.Join(",", headers)); foreach (var pair in map) { - file.WriteRaw(string.Join(",", new[] { FormatTimestamp(pair.Key) }.Concat(pair.Value))); + file.Write(string.Join(",", new[] { FormatTimestamp(pair.Key) }.Concat(pair.Value))); } return file; diff --git a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs index e198650c..5f1e968c 100644 --- a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs +++ b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs @@ -199,7 +199,7 @@ namespace ContinuousTests private void WriteEntryToFile(LogQueueEntry currentEntry) { - targetFile.WriteRaw(currentEntry.Message); + targetFile.Write(currentEntry.Message); } private void DeleteOldEntries(ulong wantedNumber) diff --git a/Tests/DistTestCore/Logs/BaseTestLog.cs b/Tests/DistTestCore/Logs/BaseTestLog.cs index 51775512..87df6383 100644 --- a/Tests/DistTestCore/Logs/BaseTestLog.cs +++ b/Tests/DistTestCore/Logs/BaseTestLog.cs @@ -8,7 +8,7 @@ namespace DistTestCore.Logs protected BaseTestLog(ILog backingLog, string deployId) { - this.backingLog = backingLog; + this.backingLog = new TimestampPrefixer(backingLog); DeployId = deployId; } diff --git a/TraceContract/ChainRequestTracker.cs b/TraceContract/ChainRequestTracker.cs new file mode 100644 index 00000000..2e313ae8 --- /dev/null +++ b/TraceContract/ChainRequestTracker.cs @@ -0,0 +1,103 @@ +using System.Numerics; +using BlockchainUtils; +using CodexContractsPlugin.ChainMonitor; +using Utils; + +namespace TraceContract +{ + public class ChainRequestTracker : IChainStateChangeHandler + { + private readonly string requestId; + private readonly Output output; + + public ChainRequestTracker(Output output, string requestId) + { + this.requestId = requestId.ToLowerInvariant(); + this.output = output; + } + + public bool IsFinished { get; private set; } = false; + public DateTime FinishUtc { get; private set; } = DateTime.MinValue; + + public void OnError(string msg) + { + } + + public void OnNewRequest(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) output.LogRequestCreated(requestEvent); + } + + public void OnProofSubmitted(BlockTimeEntry block, string id) + { + } + + public void OnRequestCancelled(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + IsFinished = true; + FinishUtc = requestEvent.Block.Utc; + output.LogRequestCancelled(requestEvent); + } + } + + public void OnRequestFailed(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + IsFinished = true; + FinishUtc = requestEvent.Block.Utc; + output.LogRequestFailed(requestEvent); + } + } + + public void OnRequestFinished(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + IsFinished = true; + FinishUtc = requestEvent.Block.Utc; + output.LogRequestFinished(requestEvent); + } + } + + public void OnRequestFulfilled(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + output.LogRequestStarted(requestEvent); + } + } + + public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) + { + if (IsMyRequest(requestEvent)) + { + output.LogSlotFilled(requestEvent, host, slotIndex); + } + } + + public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) + { + if (IsMyRequest(requestEvent)) + { + output.LogSlotFreed(requestEvent, slotIndex); + } + } + + public void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex) + { + if (IsMyRequest(requestEvent)) + { + output.LogSlotReservationsFull(requestEvent, slotIndex); + } + } + + private bool IsMyRequest(RequestEvent requestEvent) + { + return requestId == requestEvent.Request.Request.Id.ToLowerInvariant(); + } + } + +} diff --git a/TraceContract/ChainTracer.cs b/TraceContract/ChainTracer.cs new file mode 100644 index 00000000..c678667a --- /dev/null +++ b/TraceContract/ChainTracer.cs @@ -0,0 +1,119 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using CodexContractsPlugin.Marketplace; +using Logging; +using Nethereum.Hex.HexConvertors.Extensions; +using Utils; + +namespace TraceContract +{ + public class ChainTracer + { + private readonly ILog log; + private readonly ICodexContracts contracts; + private readonly Input input; + private readonly Output output; + + public ChainTracer(ILog log, ICodexContracts contracts, Input input, Output output) + { + this.log = log; + this.contracts = contracts; + this.input = input; + this.output = output; + } + + public TimeRange TraceChainTimeline() + { + log.Log("Querying blockchain..."); + var request = GetRequest(); + if (request == null) throw new Exception("Failed to find the purchase in the last week of transactions."); + + log.Log($"Request started at {request.Block.Utc}"); + var contractEnd = RunToContractEnd(request); + + var requestTimeline = new TimeRange(request.Block.Utc.AddMinutes(-1.0), contractEnd.AddMinutes(1.0)); + log.Log($"Request timeline: {requestTimeline.From} -> {requestTimeline.To}"); + + // For this timeline, we log all the calls to reserve-slot. + var events = contracts.GetEvents(requestTimeline); + output.LogReserveSlotCalls(Filter(events.GetReserveSlotCalls())); + + log.Log("Writing blockchain output..."); + output.WriteContractEvents(); + + return requestTimeline; + } + + private DateTime RunToContractEnd(Request request) + { + var utc = request.Block.Utc.AddMinutes(-1.0); + var tracker = new ChainRequestTracker(output, input.PurchaseId); + var ignoreLog = new NullLog(); + var chainState = new ChainState(ignoreLog, contracts, tracker, utc, false); + + while (!tracker.IsFinished) + { + utc += TimeSpan.FromHours(1.0); + if (utc > DateTime.UtcNow) + { + log.Log("Caught up to present moment without finding contract end."); + return DateTime.UtcNow; + } + + log.Log($"Querying up to {utc}"); + chainState.Update(utc); + } + + return tracker.FinishUtc; + } + + private ReserveSlotFunction[] Filter(ReserveSlotFunction[] calls) + { + return calls.Where(c => IsThisRequest(c.RequestId)).ToArray(); + } + + private Request? GetRequest() + { + var request = FindRequest(LastHour()); + if (request == null) request = FindRequest(LastDay()); + if (request == null) request = FindRequest(LastWeek()); + return request; + } + + private Request? FindRequest(TimeRange timeRange) + { + var events = contracts.GetEvents(timeRange); + var requests = events.GetStorageRequests(); + + foreach (var r in requests) + { + if (IsThisRequest(r.RequestId)) + { + return r; + } + } + + return null; + } + + private bool IsThisRequest(byte[] requestId) + { + return requestId.ToHex().ToLowerInvariant() == input.PurchaseId.ToLowerInvariant(); + } + + private static TimeRange LastHour() + { + return new TimeRange(DateTime.UtcNow.AddHours(-1.0), DateTime.UtcNow); + } + + private static TimeRange LastDay() + { + return new TimeRange(DateTime.UtcNow.AddDays(-1.0), DateTime.UtcNow); + } + + private static TimeRange LastWeek() + { + return new TimeRange(DateTime.UtcNow.AddDays(-7.0), DateTime.UtcNow); + } + } +} diff --git a/TraceContract/Config.cs b/TraceContract/Config.cs index 8f52e7cf..51c5e4d1 100644 --- a/TraceContract/Config.cs +++ b/TraceContract/Config.cs @@ -7,5 +7,41 @@ public string MarketplaceAddress { get; } = "0xDB2908d724a15d05c0B6B8e8441a8b36E67476d3"; public string TokenAddress { get; } = "0x34a22f3911De437307c6f4485931779670f78764"; public string Abi { get; } = @"[{""inputs"":[{""components"":[{""components"":[{""internalType"":""uint8"",""name"":""repairRewardPercentage"",""type"":""uint8""},{""internalType"":""uint8"",""name"":""maxNumberOfSlashes"",""type"":""uint8""},{""internalType"":""uint16"",""name"":""slashCriterion"",""type"":""uint16""},{""internalType"":""uint8"",""name"":""slashPercentage"",""type"":""uint8""}],""internalType"":""struct CollateralConfig"",""name"":""collateral"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""period"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""timeout"",""type"":""uint256""},{""internalType"":""uint8"",""name"":""downtime"",""type"":""uint8""},{""internalType"":""string"",""name"":""zkeyHash"",""type"":""string""}],""internalType"":""struct ProofConfig"",""name"":""proofs"",""type"":""tuple""}],""internalType"":""struct MarketplaceConfig"",""name"":""configuration"",""type"":""tuple""},{""internalType"":""contract IERC20"",""name"":""token_"",""type"":""address""},{""internalType"":""contract IGroth16Verifier"",""name"":""verifier"",""type"":""address""}],""stateMutability"":""nonpayable"",""type"":""constructor""},{""anonymous"":false,""inputs"":[{""indexed"":false,""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""ProofSubmitted"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""RequestCancelled"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""RequestFailed"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""RequestFulfilled"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""indexed"":false,""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""}],""name"":""SlotFilled"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""indexed"":false,""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""}],""name"":""SlotFreed"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":false,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""indexed"":false,""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""indexed"":false,""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""}],""name"":""StorageRequested"",""type"":""event""},{""inputs"":[],""name"":""config"",""outputs"":[{""components"":[{""components"":[{""internalType"":""uint8"",""name"":""repairRewardPercentage"",""type"":""uint8""},{""internalType"":""uint8"",""name"":""maxNumberOfSlashes"",""type"":""uint8""},{""internalType"":""uint16"",""name"":""slashCriterion"",""type"":""uint16""},{""internalType"":""uint8"",""name"":""slashPercentage"",""type"":""uint8""}],""internalType"":""struct CollateralConfig"",""name"":""collateral"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""period"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""timeout"",""type"":""uint256""},{""internalType"":""uint8"",""name"":""downtime"",""type"":""uint8""},{""internalType"":""string"",""name"":""zkeyHash"",""type"":""string""}],""internalType"":""struct ProofConfig"",""name"":""proofs"",""type"":""tuple""}],""internalType"":""struct MarketplaceConfig"",""name"":"""",""type"":""tuple""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""a"",""type"":""tuple""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""x"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""y"",""type"":""tuple""}],""internalType"":""struct G2Point"",""name"":""b"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""c"",""type"":""tuple""}],""internalType"":""struct Groth16Proof"",""name"":""proof"",""type"":""tuple""}],""name"":""fillSlot"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""freeSlot"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""getActiveSlot"",""outputs"":[{""components"":[{""components"":[{""internalType"":""address"",""name"":""client"",""type"":""address""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""components"":[{""internalType"":""string"",""name"":""cid"",""type"":""string""},{""internalType"":""bytes32"",""name"":""merkleRoot"",""type"":""bytes32""}],""internalType"":""struct Content"",""name"":""content"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""},{""internalType"":""bytes32"",""name"":""nonce"",""type"":""bytes32""}],""internalType"":""struct Request"",""name"":""request"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""}],""internalType"":""struct Marketplace.ActiveSlot"",""name"":"""",""type"":""tuple""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""getChallenge"",""outputs"":[{""internalType"":""bytes32"",""name"":"""",""type"":""bytes32""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""getHost"",""outputs"":[{""internalType"":""address"",""name"":"""",""type"":""address""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""getPointer"",""outputs"":[{""internalType"":""uint8"",""name"":"""",""type"":""uint8""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""getRequest"",""outputs"":[{""components"":[{""internalType"":""address"",""name"":""client"",""type"":""address""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""components"":[{""internalType"":""string"",""name"":""cid"",""type"":""string""},{""internalType"":""bytes32"",""name"":""merkleRoot"",""type"":""bytes32""}],""internalType"":""struct Content"",""name"":""content"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""},{""internalType"":""bytes32"",""name"":""nonce"",""type"":""bytes32""}],""internalType"":""struct Request"",""name"":"""",""type"":""tuple""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""isProofRequired"",""outputs"":[{""internalType"":""bool"",""name"":"""",""type"":""bool""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""},{""internalType"":""Periods.Period"",""name"":""period"",""type"":""uint256""}],""name"":""markProofAsMissing"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""missingProofs"",""outputs"":[{""internalType"":""uint256"",""name"":"""",""type"":""uint256""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[],""name"":""myRequests"",""outputs"":[{""internalType"":""RequestId[]"",""name"":"""",""type"":""bytes32[]""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[],""name"":""mySlots"",""outputs"":[{""internalType"":""SlotId[]"",""name"":"""",""type"":""bytes32[]""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""requestEnd"",""outputs"":[{""internalType"":""uint256"",""name"":"""",""type"":""uint256""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""requestExpiry"",""outputs"":[{""internalType"":""uint256"",""name"":"""",""type"":""uint256""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""requestState"",""outputs"":[{""internalType"":""enum RequestState"",""name"":"""",""type"":""uint8""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""components"":[{""internalType"":""address"",""name"":""client"",""type"":""address""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""components"":[{""internalType"":""string"",""name"":""cid"",""type"":""string""},{""internalType"":""bytes32"",""name"":""merkleRoot"",""type"":""bytes32""}],""internalType"":""struct Content"",""name"":""content"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""},{""internalType"":""bytes32"",""name"":""nonce"",""type"":""bytes32""}],""internalType"":""struct Request"",""name"":""request"",""type"":""tuple""}],""name"":""requestStorage"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""slotState"",""outputs"":[{""internalType"":""enum SlotState"",""name"":"""",""type"":""uint8""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""a"",""type"":""tuple""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""x"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""y"",""type"":""tuple""}],""internalType"":""struct G2Point"",""name"":""b"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""c"",""type"":""tuple""}],""internalType"":""struct Groth16Proof"",""name"":""proof"",""type"":""tuple""}],""name"":""submitProof"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[],""name"":""token"",""outputs"":[{""internalType"":""contract IERC20"",""name"":"""",""type"":""address""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""willProofBeRequired"",""outputs"":[{""internalType"":""bool"",""name"":"""",""type"":""bool""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""withdrawFunds"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""}]"; + + /// + /// Naming things is hard. + /// If the storage request is created at T=0, then fetching of the + /// storage node logs will begin from T=0 minus 'LogStartBeforeStorageContractStarts'. + /// + public TimeSpan LogStartBeforeStorageContractStarts { get; } = TimeSpan.FromMinutes(1.0); + + public string StorageNodesKubernetesNamespace = "codex"; + public string[] StorageNodesKubernetesContainerNames = [ + "codex-1-1", + //"codex-2-1", + //"codex-3-1", + //"codex-4-1", + //"codex-5-1", + //"codex-6-1", + //"codex-7-1", + //"codex-8-1", + //"codex-9-1", + "codex-10-1", + // "codex-validator-1-1", + ]; + + public Dictionary LogReplacements = new() + { + { "0xa1f988fBa23EFd5fA36F4c1a2D1E3c83e25bee4e", "codex 01" }, + { "0xa26a91310F9f2987AA7e0b1ca70e5C474c88ed34", "codex 02" }, + { "0x0CDC9d2D375300C46E13a679cD9eA5299A4FAc74", "codex 03" }, + { "0x7AF1a49A4a52e4bCe3789Ce3d43ff8AD8c8F2118", "codex 04" }, + { "0xfbbEB320c6c775f6565c7bcC732b2813Dd6E0cd3", "codex 05" }, + { "0x4A904CA0998B643eb42d4ae190a5821A4ac51E68", "codex 06" }, + { "0x2b8Ea47d0966B26DEec485c0fCcF0D1A8b52A0e8", "codex 07" }, + { "0x78F90A61d9a2aA93B61A7503Cc2177fFEF379021", "codex 08" }, + { "0xE7EEb996B3c817cEd03d10cd64A1325DA33D92e7", "codex 09" }, + { "0xD25C7609e97F40b66E74c0FcEbeA06D09423CC7e", "codex 10" } + }; } } diff --git a/TraceContract/ElasticSearchLogDownloader.cs b/TraceContract/ElasticSearchLogDownloader.cs new file mode 100644 index 00000000..8b40ed0b --- /dev/null +++ b/TraceContract/ElasticSearchLogDownloader.cs @@ -0,0 +1,242 @@ +using Core; +using Logging; +using Utils; +using WebUtils; + +namespace ContinuousTests +{ + public class ElasticSearchLogDownloader + { + private readonly ILog log; + private readonly IPluginTools tools; + private readonly string k8SNamespace; + + public ElasticSearchLogDownloader(ILog log, IPluginTools tools, string k8sNamespace) + { + this.log = log; + this.tools = tools; + k8SNamespace = k8sNamespace; + } + + public void Download(LogFile targetFile, string containerName, DateTime startUtc, DateTime endUtc) + { + try + { + DownloadLog(targetFile, containerName, startUtc, endUtc); + } + catch (Exception ex) + { + log.Error("Failed to download log: " + ex); + } + } + + private void DownloadLog(LogFile targetFile, string containerName, DateTime startUtc, DateTime endUtc) + { + log.Log($"Downloading log (from ElasticSearch) for container '{containerName}' within time range: " + + $"{startUtc.ToString("o")} - {endUtc.ToString("o")}"); + + var endpoint = CreateElasticSearchEndpoint(); + var queryTemplate = CreateQueryTemplate(containerName, startUtc, endUtc); + + targetFile.Write($"Downloading '{containerName}' to '{targetFile.Filename}'."); + var reconstructor = new LogReconstructor(targetFile, endpoint, queryTemplate); + reconstructor.DownloadFullLog(); + + log.Log("Log download finished."); + } + + private string CreateQueryTemplate(string containerName, DateTime startUtc, DateTime endUtc) + { + var start = startUtc.ToString("o"); + var end = endUtc.ToString("o"); + + //container_name : codex3-5 - deploymentName as stored in pod + // pod_namespace : codex - continuous - nolimits - tests - 1 + + //var source = "{ \"sort\": [ { \"@timestamp\": { \"order\": \"asc\" } } ], \"fields\": [ { \"field\": \"@timestamp\", \"format\": \"strict_date_optional_time\" }, { \"field\": \"pod_name\" }, { \"field\": \"message\" } ], \"size\": , \"_source\": false, \"query\": { \"bool\": { \"must\": [], \"filter\": [ { \"range\": { \"@timestamp\": { \"format\": \"strict_date_optional_time\", \"gte\": \"\", \"lte\": \"\" } } }, { \"match_phrase\": { \"pod_name\": \"\" } } ] } } }"; + var source = "{ \"sort\": [ { \"@timestamp\": { \"order\": \"asc\" } } ], \"fields\": [ { \"field\": \"@timestamp\", \"format\": \"strict_date_optional_time\" }, { \"field\": \"message\" } ], \"size\": , \"_source\": false, \"query\": { \"bool\": { \"must\": [], \"filter\": [ { \"range\": { \"@timestamp\": { \"format\": \"strict_date_optional_time\", \"gte\": \"\", \"lte\": \"\" } } }, { \"match_phrase\": { \"container_name\": \"\" } }, { \"match_phrase\": { \"pod_namespace\": \"\" } } ] } } }"; + return source + .Replace("", start) + .Replace("", end) + .Replace("", containerName) + .Replace("", k8SNamespace); + } + + private IEndpoint CreateElasticSearchEndpoint() + { + var serviceName = "elasticsearch"; + var k8sNamespace = "monitoring"; + var address = new Address("ElasticSearchEndpoint", $"http://{serviceName}.{k8sNamespace}.svc.cluster.local", 9200); + var baseUrl = ""; + + var http = tools.CreateHttp(address.ToString(), client => + { + client.DefaultRequestHeaders.Add("kbn-xsrf", "reporting"); + }); + + return http.CreateEndpoint(address, baseUrl); + } + + public class LogReconstructor + { + private readonly List queue = new List(); + private readonly LogFile targetFile; + private readonly IEndpoint endpoint; + private readonly string queryTemplate; + private const int sizeOfPage = 2000; + private string searchAfter = ""; + private int lastHits = 1; + private ulong? lastLogLine; + + public LogReconstructor(LogFile targetFile, IEndpoint endpoint, string queryTemplate) + { + this.targetFile = targetFile; + this.endpoint = endpoint; + this.queryTemplate = queryTemplate; + } + + public void DownloadFullLog() + { + while (lastHits > 0) + { + QueryElasticSearch(); + ProcessQueue(); + } + } + + private void QueryElasticSearch() + { + var query = queryTemplate + .Replace("", sizeOfPage.ToString()) + .Replace("", searchAfter); + + var response = endpoint.HttpPostString("_search", query); + + lastHits = response.hits.hits.Length; + if (lastHits > 0) + { + UpdateSearchAfter(response); + foreach (var hit in response.hits.hits) + { + AddHitToQueue(hit); + } + } + } + + private void AddHitToQueue(SearchHitEntry hit) + { + var message = hit.fields.message.Single(); + var number = ParseCountNumber(message); + if (number != null) + { + queue.Add(new LogQueueEntry(message, number.Value)); + } + } + + private ulong? ParseCountNumber(string message) + { + if (string.IsNullOrEmpty(message)) return null; + var tokens = message.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (!tokens.Any()) return null; + var countToken = tokens.SingleOrDefault(t => t.StartsWith("count=")); + if (countToken == null) return null; + var number = countToken.Substring(6); + if (ulong.TryParse(number, out ulong value)) + { + return value; + } + return null; + } + + private void UpdateSearchAfter(SearchResponse response) + { + var uniqueSearchNumbers = response.hits.hits.Select(h => h.sort.Single()).Distinct().ToList(); + uniqueSearchNumbers.Reverse(); + + var searchNumber = GetSearchNumber(uniqueSearchNumbers); + searchAfter = $"\"search_after\": [{searchNumber}],"; + } + + private long GetSearchNumber(List uniqueSearchNumbers) + { + if (uniqueSearchNumbers.Count == 1) return uniqueSearchNumbers.First(); + return uniqueSearchNumbers.Skip(1).First(); + } + + private void ProcessQueue() + { + if (lastLogLine == null) + { + lastLogLine = queue.Min(q => q.Number) - 1; + } + + while (queue.Any()) + { + ulong wantedNumber = lastLogLine.Value + 1; + + DeleteOldEntries(wantedNumber); + + var currentEntry = queue.FirstOrDefault(e => e.Number == wantedNumber); + + if (currentEntry != null) + { + WriteEntryToFile(currentEntry); + queue.Remove(currentEntry); + lastLogLine = currentEntry.Number; + } + else + { + // The line number we want is not in the queue. + // It will be returned by the elastic search query, some time in the future. + // Stop processing the queue for now. + return; + } + } + } + + private void WriteEntryToFile(LogQueueEntry currentEntry) + { + targetFile.Write(currentEntry.Message); + } + + private void DeleteOldEntries(ulong wantedNumber) + { + queue.RemoveAll(e => e.Number < wantedNumber); + } + + public class LogQueueEntry + { + public LogQueueEntry(string message, ulong number) + { + Message = message; + Number = number; + } + + public string Message { get; } + public ulong Number { get; } + } + + public class SearchResponse + { + public SearchHits hits { get; set; } = new SearchHits(); + } + + public class SearchHits + { + public SearchHitEntry[] hits { get; set; } = Array.Empty(); + } + + public class SearchHitEntry + { + public SearchHitFields fields { get; set; } = new SearchHitFields(); + public long[] sort { get; set; } = Array.Empty(); + } + + public class SearchHitFields + { + public string[] @timestamp { get; set; } = Array.Empty(); + public string[] message { get; set; } = Array.Empty(); + } + } + } +} diff --git a/TraceContract/Input.cs b/TraceContract/Input.cs new file mode 100644 index 00000000..50a23ebb --- /dev/null +++ b/TraceContract/Input.cs @@ -0,0 +1,13 @@ +namespace TraceContract +{ + public class Input + { + public string PurchaseId { get; } = + // expired: + "a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; + + // started: + //"066df09a3a2e2587cfd577a0e96186c915b113d02b331b06e56f808494cff2b4"; + } + +} diff --git a/TraceContract/Output.cs b/TraceContract/Output.cs index 6a731e0f..d7c230ba 100644 --- a/TraceContract/Output.cs +++ b/TraceContract/Output.cs @@ -8,61 +8,112 @@ namespace TraceContract { public class Output { - private readonly ILog log; - - public Output(ILog log) + private class Entry { - this.log = log; + public Entry(DateTime utc, string msg) + { + Utc = utc; + Msg = msg; + } + + public DateTime Utc { get; } + public string Msg { get; } + } + + private readonly ILog log; + private readonly List entries = new(); + private readonly string folder; + private readonly List files = new(); + + public Output(ILog log, Input input, Config config) + { + folder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(folder); + + var filename = Path.Combine(folder, $"Contract_{input.PurchaseId}"); + var fileLog = new FileLog(filename); + files.Add(fileLog.FullFilename); + foreach (var pair in config.LogReplacements) + { + fileLog.AddStringReplace(pair.Key, pair.Value); + fileLog.AddStringReplace(pair.Key.ToLowerInvariant(), pair.Value); + } + + log.Log($"Logging to '{filename}'"); + this.log = new LogSplitter(fileLog, log); } public void LogRequestCreated(RequestEvent requestEvent) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, $"Storage request created: '{requestEvent.Request.Request.Id}'"); } public void LogRequestCancelled(RequestEvent requestEvent) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, "Expired"); } public void LogRequestFailed(RequestEvent requestEvent) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, "Failed"); } public void LogRequestFinished(RequestEvent requestEvent) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, "Finished"); } public void LogRequestStarted(RequestEvent requestEvent) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, "Started"); } public void LogSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, $"Slot filled. Index: {slotIndex} Host: '{host}'"); } public void LogSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, $"Slot freed. Index: {slotIndex}"); } public void LogSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex) { - throw new NotImplementedException(); + Add(requestEvent.Block.Utc, $"Slot reservations full. Index: {slotIndex}"); } public void LogReserveSlotCalls(ReserveSlotFunction[] reserveSlotFunctions) { - throw new NotImplementedException(); + foreach (var call in reserveSlotFunctions) LogReserveSlotCall(call); } public void WriteContractEvents() { - throw new NotImplementedException(); + var sorted = entries.OrderBy(e => e.Utc).ToArray(); + foreach (var e in sorted) Write(e); + } + + private void Write(Entry e) + { + log.Log($"[{Time.FormatTimestamp(e.Utc)}] {e.Msg}"); + } + + private void LogReserveSlotCall(ReserveSlotFunction call) + { + Add(call.Block.Utc, $"Reserve-slot called. Index: {call.SlotIndex} Host: '{call.FromAddress}'"); + } + + private void Add(DateTime utc, string msg) + { + entries.Add(new Entry(utc, msg)); + } + + public LogFile CreateNodeLogTargetFile(string node) + { + var file = log.CreateSubfile(node); + files.Add(file.Filename); + return file; } } } diff --git a/TraceContract/Program.cs b/TraceContract/Program.cs index eabb7e16..0b7f3911 100644 --- a/TraceContract/Program.cs +++ b/TraceContract/Program.cs @@ -1,30 +1,21 @@ -using System.Numerics; -using BlockchainUtils; +using BlockchainUtils; using CodexContractsPlugin; -using CodexContractsPlugin.ChainMonitor; using CodexContractsPlugin.Marketplace; +using ContinuousTests; using Core; using GethPlugin; using Logging; -using Nethereum.Hex.HexConvertors.Extensions; using Utils; namespace TraceContract { - public class Input - { - public string PurchaseId { get; } = - // expired: - //"a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; - - // started: - "066df09a3a2e2587cfd577a0e96186c915b113d02b331b06e56f808494cff2b4"; - } - public class Program { public static void Main(string[] args) { + ProjectPlugin.Load(); + ProjectPlugin.Load(); + var p = new Program(); p.Run(); } @@ -36,7 +27,7 @@ namespace TraceContract public Program() { - output = new(log); + output = new(log, input, config); } private void Run() @@ -54,29 +45,32 @@ namespace TraceContract private void TracePurchase() { Log("Setting up..."); - var contracts = ConnectCodexContracts(); - - var chainTracer = new ChainTracer(log, contracts, input, output); - var requestTimeRange = chainTracer.TraceChainTimeline(); - - Log("Done"); - } - - private ICodexContracts ConnectCodexContracts() - { - ProjectPlugin.Load(); - ProjectPlugin.Load(); - var entryPoint = new EntryPoint(log, new KubernetesWorkflow.Configuration(null, TimeSpan.FromMinutes(1.0), TimeSpan.FromSeconds(10.0), "_Unused!_"), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); entryPoint.Announce(); var ci = entryPoint.CreateInterface(); + var contracts = ConnectCodexContracts(ci); + + var chainTracer = new ChainTracer(log, contracts, input, output); + var requestTimeRange = chainTracer.TraceChainTimeline(); + + Log("Downloading storage nodes logs for the request timerange..."); + DownloadStorageNodeLogs(requestTimeRange, entryPoint.Tools); + + // package everything + + entryPoint.Decommission(false, false, false); + Log("Done"); + } + + private ICodexContracts ConnectCodexContracts(CoreInterface ci) + { var account = EthAccountGenerator.GenerateNew(); var blockCache = new BlockCache(); var geth = new CustomGethNode(log, blockCache, config.RpcEndpoint, config.GethPort, account.PrivateKey); var deployment = new CodexContractsDeployment( - config: new CodexContractsPlugin.Marketplace.MarketplaceConfig(), + config: new MarketplaceConfig(), marketplaceAddress: config.MarketplaceAddress, abi: config.Abi, tokenAddress: config.TokenAddress @@ -84,197 +78,23 @@ namespace TraceContract return ci.WrapCodexContractsDeployment(geth, deployment); } + private void DownloadStorageNodeLogs(TimeRange requestTimeRange, IPluginTools tools) + { + var start = requestTimeRange.From - config.LogStartBeforeStorageContractStarts; + + foreach (var node in config.StorageNodesKubernetesContainerNames) + { + Log($"Downloading logs from '{node}'..."); + + var targetFile = output.CreateNodeLogTargetFile(node); + var downloader = new ElasticSearchLogDownloader(log, tools, config.StorageNodesKubernetesNamespace); + downloader.Download(targetFile, node, start, requestTimeRange.To); + } + } + private void Log(string msg) { log.Log(msg); } } - - public class ChainTracer - { - private readonly ILog log; - private readonly ICodexContracts contracts; - private readonly Input input; - private readonly Output output; - - public ChainTracer(ILog log, ICodexContracts contracts, Input input, Output output) - { - this.log = log; - this.contracts = contracts; - this.input = input; - this.output = output; - } - - public TimeRange TraceChainTimeline() - { - var request = GetRequest(); - if (request == null) throw new Exception("Failed to find the purchase in the last week of transactions."); - - var utc = request.Block.Utc.AddMinutes(-1.0); - var tracker = new ChainRequestTracker(output, input.PurchaseId); - var ignoreLog = new NullLog(); - var chainState = new ChainState(ignoreLog, contracts, tracker, utc, false); - - while (!tracker.IsFinished) - { - utc += TimeSpan.FromHours(1.0); - chainState.Update(utc); - } - - var requestTimeline = new TimeRange(request.Block.Utc.AddMinutes(-1.0), tracker.FinishUtc.AddMinutes(1.0)); - - // For this timeline, we log all the calls to reserve-slot. - var events = contracts.GetEvents(requestTimeline); - output.LogReserveSlotCalls(Filter(events.GetReserveSlotCalls())); - - output.WriteContractEvents(); - - return requestTimeline; - } - - private ReserveSlotFunction[] Filter(ReserveSlotFunction[] calls) - { - return calls.Where(c => IsThisRequest(c.RequestId)).ToArray(); - } - - private Request? GetRequest() - { - var request = FindRequest(LastHour()); - if (request == null) request = FindRequest(LastDay()); - if (request == null) request = FindRequest(LastWeek()); - return request; - } - - private Request? FindRequest(TimeRange timeRange) - { - var events = contracts.GetEvents(timeRange); - var requests = events.GetStorageRequests(); - - foreach (var r in requests) - { - if (IsThisRequest(r.RequestId)) - { - return r; - } - } - - return null; - } - - private bool IsThisRequest(byte[] requestId) - { - return requestId.ToHex().ToLowerInvariant() == input.PurchaseId.ToLowerInvariant(); - } - - private static TimeRange LastHour() - { - return new TimeRange(DateTime.UtcNow.AddHours(-1.0), DateTime.UtcNow); - } - - private static TimeRange LastDay() - { - return new TimeRange(DateTime.UtcNow.AddDays(-1.0), DateTime.UtcNow); - } - - private static TimeRange LastWeek() - { - return new TimeRange(DateTime.UtcNow.AddDays(-7.0), DateTime.UtcNow); - } - } - - public class ChainRequestTracker : IChainStateChangeHandler - { - private readonly string requestId; - private readonly Output output; - - public ChainRequestTracker(Output output, string requestId) - { - this.requestId = requestId.ToLowerInvariant(); - this.output = output; - } - - public bool IsFinished { get; private set; } = false; - public DateTime FinishUtc { get; private set; } = DateTime.MinValue; - - public void OnError(string msg) - { - } - - public void OnNewRequest(RequestEvent requestEvent) - { - if (IsMyRequest(requestEvent)) output.LogRequestCreated(requestEvent); - } - - public void OnProofSubmitted(BlockTimeEntry block, string id) - { - } - - public void OnRequestCancelled(RequestEvent requestEvent) - { - if (IsMyRequest(requestEvent)) - { - IsFinished = true; - FinishUtc = requestEvent.Block.Utc; - output.LogRequestCancelled(requestEvent); - } - } - - public void OnRequestFailed(RequestEvent requestEvent) - { - if (IsMyRequest(requestEvent)) - { - IsFinished = true; - FinishUtc = requestEvent.Block.Utc; - output.LogRequestFailed(requestEvent); - } - } - - public void OnRequestFinished(RequestEvent requestEvent) - { - if (IsMyRequest(requestEvent)) - { - IsFinished = true; - FinishUtc = requestEvent.Block.Utc; - output.LogRequestFinished(requestEvent); - } - } - - public void OnRequestFulfilled(RequestEvent requestEvent) - { - if (IsMyRequest(requestEvent)) - { - output.LogRequestStarted(requestEvent); - } - } - - public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) - { - if (IsMyRequest(requestEvent)) - { - output.LogSlotFilled(requestEvent, host, slotIndex); - } - } - - public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) - { - if (IsMyRequest(requestEvent)) - { - output.LogSlotFreed(requestEvent, slotIndex); - } - } - - public void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex) - { - if (IsMyRequest(requestEvent)) - { - output.LogSlotReservationsFull(requestEvent, slotIndex); - } - } - - private bool IsMyRequest(RequestEvent requestEvent) - { - return requestId == requestEvent.Request.Request.Id.ToLowerInvariant(); - } - } - }