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();
- }
- }
-
}