From 3bb9a290544bda646a7f12828673a22ec2624a3d Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 24 Apr 2025 12:53:08 +0200 Subject: [PATCH 01/24] sets up multiple successfulcontract tests --- Framework/KubernetesWorkflow/LogHandler.cs | 3 + .../ChainMonitor/ChainState.cs | 4 +- .../ChainMonitor/PeriodMonitor.cs | 11 ++- .../MarketTests/ChainMonitor.cs | 69 +++++++++++++++++++ .../MarketTests/ContractSuccessfulTest.cs | 30 +++++--- .../MarketplaceAutoBootstrapDistTest.cs | 21 +++++- .../MarketTests/MultipleContractsTest.cs | 44 ++++++------ Tests/DistTestCore/DistTest.cs | 2 +- Tests/DistTestCore/TestLifecycle.cs | 6 +- 9 files changed, 146 insertions(+), 44 deletions(-) create mode 100644 Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs diff --git a/Framework/KubernetesWorkflow/LogHandler.cs b/Framework/KubernetesWorkflow/LogHandler.cs index af77ef75..44adcc05 100644 --- a/Framework/KubernetesWorkflow/LogHandler.cs +++ b/Framework/KubernetesWorkflow/LogHandler.cs @@ -40,6 +40,9 @@ namespace KubernetesWorkflow protected override void ProcessLine(string line) { + if (line.Contains("Received JSON-RPC response")) return; + if (line.Contains("object field not marked with serialize, skipping")) return; + LogFile.WriteRaw(line); } } diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index 25a028dc..32dfc5d0 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -47,7 +47,7 @@ namespace CodexContractsPlugin.ChainMonitor handler = changeHandler; this.doProofPeriodMonitoring = doProofPeriodMonitoring; TotalSpan = new TimeRange(startUtc, startUtc); - PeriodMonitor = new PeriodMonitor(this.log, contracts); + PeriodMonitor = new PeriodMonitor(contracts); } public TimeRange TotalSpan { get; private set; } @@ -78,7 +78,7 @@ namespace CodexContractsPlugin.ChainMonitor throw new Exception(msg); } - log.Log($"ChainState updating: {events.BlockInterval} = {events.All.Length} events."); + log.Debug($"ChainState updating: {events.BlockInterval} = {events.All.Length} events."); // Run through each block and apply the events to the state in order. var span = events.BlockInterval.TimeRange.Duration; diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs index 538ae124..2fbb5ff3 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs @@ -1,18 +1,15 @@ -using Logging; -using Utils; +using Utils; namespace CodexContractsPlugin.ChainMonitor { public class PeriodMonitor { - private readonly ILog log; private readonly ICodexContracts contracts; private readonly List reports = new List(); private ulong? currentPeriod = null; - public PeriodMonitor(ILog log, ICodexContracts contracts) + public PeriodMonitor(ICodexContracts contracts) { - this.log = log; this.contracts = contracts; } @@ -39,8 +36,6 @@ namespace CodexContractsPlugin.ChainMonitor private void CreateReportForPeriod(ulong lastBlockInPeriod, ulong periodNumber, IChainStateRequest[] requests) { - log.Log("Creating report for period " + periodNumber); - ulong total = 0; ulong required = 0; var missed = new List(); @@ -87,6 +82,8 @@ namespace CodexContractsPlugin.ChainMonitor private void CalcStats() { IsEmpty = Reports.All(r => r.TotalProofsRequired == 0); + if (Reports.Length == 0) return; + PeriodLow = Reports.Min(r => r.PeriodNumber); PeriodHigh = Reports.Max(r => r.PeriodNumber); AverageNumSlots = Reports.Average(r => Convert.ToSingle(r.TotalNumSlots)); diff --git a/Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs b/Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs new file mode 100644 index 00000000..74f6a96a --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs @@ -0,0 +1,69 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using Logging; + +namespace CodexReleaseTests.MarketTests +{ + public class ChainMonitor + { + private readonly ILog log; + private readonly ICodexContracts contracts; + private readonly DateTime startUtc; + private readonly TimeSpan updateInterval; + private CancellationTokenSource cts = new CancellationTokenSource(); + private Task worker = Task.CompletedTask; + + public ChainMonitor(ILog log, ICodexContracts contracts, DateTime startUtc) + : this(log, contracts, startUtc, TimeSpan.FromSeconds(3.0)) + { + } + + public ChainMonitor(ILog log, ICodexContracts contracts, DateTime startUtc, TimeSpan updateInterval) + { + this.log = log; + this.contracts = contracts; + this.startUtc = startUtc; + this.updateInterval = updateInterval; + } + + public void Start() + { + cts = new CancellationTokenSource(); + worker = Task.Run(Worker); + } + + public void Stop() + { + cts.Cancel(); + worker.Wait(); + if (worker.Exception != null) throw worker.Exception; + } + + private void Worker() + { + var state = new ChainState(log, contracts, new DoNothingChainEventHandler(), startUtc, doProofPeriodMonitoring: true); + Thread.Sleep(updateInterval); + + while (!cts.IsCancellationRequested) + { + UpdateChainState(state); + + cts.Token.WaitHandle.WaitOne(updateInterval); + } + } + + private void UpdateChainState(ChainState state) + { + state.Update(); + + var reports = state.PeriodMonitor.GetAndClearReports(); + if (reports.IsEmpty) return; + + var slots = reports.Reports.Sum(r => Convert.ToInt32(r.TotalNumSlots)); + var required = reports.Reports.Sum(r => Convert.ToInt32(r.TotalProofsRequired)); + var missed = reports.Reports.Sum(r => Convert.ToInt32(r.MissedProofs)); + + log.Log($"Proof report: Slots={slots} Required={required} Missed={missed}"); + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs index 8431d121..66fd6542 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -4,16 +4,31 @@ using Utils; namespace CodexReleaseTests.MarketTests { - [TestFixture] + [TestFixture(6, 3, 1)] + [TestFixture(6, 4, 2)] + [TestFixture(8, 5, 2)] + [TestFixture(8, 6, 3)] + [TestFixture(12, 8, 1)] + [TestFixture(12, 8, 4)] public class ContractSuccessfulTest : MarketplaceAutoBootstrapDistTest { - private const int FilesizeMb = 10; + public ContractSuccessfulTest(int hosts, int slots, int tolerance) + { + this.hosts = hosts; + this.slots = slots; + this.tolerance = tolerance; + } - protected override int NumberOfHosts => 6; + private const int FilesizeMb = 10; + private readonly TestToken pricePerBytePerSecond = 10.TstWei(); + private readonly int hosts; + private readonly int slots; + private readonly int tolerance; + + protected override int NumberOfHosts => hosts; protected override int NumberOfClients => 1; protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration(); - private readonly TestToken pricePerBytePerSecond = 10.TstWei(); [Test] public void ContractSuccessful() @@ -44,11 +59,8 @@ namespace CodexReleaseTests.MarketTests { Duration = GetContractDuration(), Expiry = GetContractExpiry(), - // TODO: this should work with NumberOfHosts, but - // an ongoing issue makes hosts sometimes not pick up slots. - // When it's resolved, we can reduce the number of hosts and slim down this test. - MinRequiredNumberOfNodes = 3, - NodeFailureTolerance = 1, + MinRequiredNumberOfNodes = (uint)slots, + NodeFailureTolerance = (uint)tolerance, PricePerBytePerSecond = pricePerBytePerSecond, ProofProbability = 20, CollateralPerByte = 100.TstWei() diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index e5ad35a4..b0db0702 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -5,6 +5,7 @@ using CodexPlugin; using CodexTests; using DistTestCore; using GethPlugin; +using Logging; using Nethereum.Hex.HexConvertors.Extensions; using Utils; @@ -21,11 +22,15 @@ namespace CodexReleaseTests.MarketTests base.LifecycleStart(lifecycle); var geth = StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); - handles.Add(lifecycle, new MarketplaceHandle(geth, contracts)); + var monitor = SetupChainMonitor(lifecycle.Log, contracts, lifecycle.TestStartUtc); + handles.Add(lifecycle, new MarketplaceHandle(geth, contracts, monitor)); } protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) { + var handle = handles[lifecycle]; + if (handle.ChainMonitor != null) handle.ChainMonitor.Stop(); + handles.Remove(lifecycle); base.LifecycleStop(lifecycle, result); } @@ -50,6 +55,7 @@ namespace CodexReleaseTests.MarketTests protected abstract int NumberOfClients { get; } protected abstract ByteSize HostAvailabilitySize { get; } protected abstract TimeSpan HostAvailabilityMaxDuration { get; } + protected virtual bool MonitorChainState { get; } = true; public ICodexNodeGroup StartHosts() { @@ -112,6 +118,15 @@ namespace CodexReleaseTests.MarketTests }); } + private ChainMonitor? SetupChainMonitor(ILog log, ICodexContracts contracts, DateTime startUtc) + { + if (!MonitorChainState) return null; + + var result = new ChainMonitor(log, contracts, startUtc); + result.Start(); + return result; + } + private Retry GetBalanceAssertRetry() { return new Retry("AssertBalance", @@ -330,14 +345,16 @@ namespace CodexReleaseTests.MarketTests private class MarketplaceHandle { - public MarketplaceHandle(IGethNode geth, ICodexContracts contracts) + public MarketplaceHandle(IGethNode geth, ICodexContracts contracts, ChainMonitor? chainMonitor) { Geth = geth; Contracts = contracts; + ChainMonitor = chainMonitor; } public IGethNode Geth { get; } public ICodexContracts Contracts { get; } + public ChainMonitor? ChainMonitor { get; } } } } diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index 6ad10643..d91dec64 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -1,4 +1,5 @@ using CodexClient; +using CodexPlugin; using NUnit.Framework; using Utils; @@ -16,13 +17,25 @@ namespace CodexReleaseTests.MarketTests private readonly TestToken pricePerBytePerSecond = 10.TstWei(); [Test] - [Ignore("TODO - Test where multiple successful contracts are run simultaenously")] - public void MultipleSuccessfulContracts() + [Ignore("TODO - wip")] + [Combinatorial] + public void MultipleContractGenerations( + [Values(1, 5, 10)] int numGenerations) { var hosts = StartHosts(); + + for (var i = 0; i < numGenerations; i++) + { + Log("Generation: " + i); + Generation(hosts); + } + } + + private void Generation(ICodexNodeGroup hosts) + { var clients = StartClients(); - var requests = clients.Select(c => CreateStorageRequest(c)).ToArray(); + var requests = clients.Select(CreateStorageRequest).ToArray(); All(requests, r => { @@ -32,26 +45,17 @@ namespace CodexReleaseTests.MarketTests All(requests, r => r.WaitForStorageContractStarted()); All(requests, r => AssertContractSlotsAreFilledByHosts(r, hosts)); - All(requests, r => r.WaitForStorageContractFinished()); - - // todo: removed from codexclient: - //contracts.WaitUntilNextPeriod(); - //contracts.WaitUntilNextPeriod(); - - //var blocks = 3; - //Log($"Waiting {blocks} blocks for nodes to process payouts..."); - //Thread.Sleep(GethContainerRecipe.BlockInterval * blocks); - - // todo: - //AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts); - //AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts); - //AssertHostsCollateralsAreUnchanged(hosts); } private void All(IStoragePurchaseContract[] requests, Action action) { - foreach (var r in requests) action(r); + var tasks = requests.Select(r => Task.Run(() => action(r))).ToArray(); + Task.WaitAll(tasks); + foreach(var t in tasks) + { + if (t.Exception != null) throw t.Exception; + } } private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) @@ -62,8 +66,8 @@ namespace CodexReleaseTests.MarketTests { Duration = GetContractDuration(), Expiry = GetContractExpiry(), - MinRequiredNumberOfNodes = (uint)NumberOfHosts, - NodeFailureTolerance = (uint)(NumberOfHosts / 2), + MinRequiredNumberOfNodes = (uint)NumberOfHosts / 2, + NodeFailureTolerance = (uint)(NumberOfHosts / 4), PricePerBytePerSecond = pricePerBytePerSecond, ProofProbability = 20, CollateralPerByte = 1.Tst() diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index 534eb74a..104bf8d8 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -164,7 +164,7 @@ namespace DistTestCore protected TimeRange GetTestRunTimeRange() { - return new TimeRange(Get().TestStart, DateTime.UtcNow); + return new TimeRange(Get().TestStartUtc, DateTime.UtcNow); } protected virtual void Initialize(FixtureLog fixtureLog) diff --git a/Tests/DistTestCore/TestLifecycle.cs b/Tests/DistTestCore/TestLifecycle.cs index 3d642d20..53d0f177 100644 --- a/Tests/DistTestCore/TestLifecycle.cs +++ b/Tests/DistTestCore/TestLifecycle.cs @@ -26,7 +26,7 @@ namespace DistTestCore WebCallTimeSet = webCallTimeSet; K8STimeSet = k8sTimeSet; TestNamespace = testNamespace; - TestStart = DateTime.UtcNow; + TestStartUtc = DateTime.UtcNow; entryPoint = new EntryPoint(log, configuration.GetK8sConfiguration(k8sTimeSet, this, testNamespace), configuration.GetFileManagerFolder(), webCallTimeSet, k8sTimeSet); metadata = entryPoint.GetPluginMetadata(); @@ -36,7 +36,7 @@ namespace DistTestCore log.WriteLogTag(); } - public DateTime TestStart { get; } + public DateTime TestStartUtc { get; } public TestLog Log { get; } public Configuration Configuration { get; } public IWebCallTimeSet WebCallTimeSet { get; } @@ -76,7 +76,7 @@ namespace DistTestCore public TimeSpan GetTestDuration() { - return DateTime.UtcNow - TestStart; + return DateTime.UtcNow - TestStartUtc; } public void OnContainersStarted(RunningPod rc) From 9a52b217652ebf3c331645ba569030824d2f5056 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 24 Apr 2025 15:34:31 +0200 Subject: [PATCH 02/24] extended multigeneration contract testing --- .../MarketTests/ContractSuccessfulTest.cs | 9 +++- .../MarketTests/MultipleContractsTest.cs | 51 ++++++++++++++----- .../NodeTests/BasicInfoTests.cs | 8 +-- Tests/CodexReleaseTests/Parallelism.cs | 2 +- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs index 66fd6542..7750ede1 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -6,10 +6,15 @@ namespace CodexReleaseTests.MarketTests { [TestFixture(6, 3, 1)] [TestFixture(6, 4, 2)] + [TestFixture(8, 5, 1)] [TestFixture(8, 5, 2)] + [TestFixture(8, 6, 1)] + [TestFixture(8, 6, 2)] [TestFixture(8, 6, 3)] - [TestFixture(12, 8, 1)] - [TestFixture(12, 8, 4)] + [TestFixture(8, 8, 1)] + [TestFixture(8, 8, 2)] + [TestFixture(8, 8, 3)] + [TestFixture(8, 8, 4)] public class ContractSuccessfulTest : MarketplaceAutoBootstrapDistTest { public ContractSuccessfulTest(int hosts, int slots, int tolerance) diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index d91dec64..815c4697 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -5,22 +5,42 @@ using Utils; namespace CodexReleaseTests.MarketTests { - [TestFixture] + [TestFixture(6, 3, 1)] + [TestFixture(6, 4, 1)] + [TestFixture(6, 4, 2)] + [TestFixture(8, 5, 1)] + [TestFixture(8, 5, 2)] + [TestFixture(8, 6, 1)] + [TestFixture(8, 6, 2)] + [TestFixture(8, 6, 3)] + [TestFixture(8, 8, 1)] + [TestFixture(8, 8, 2)] + [TestFixture(8, 8, 3)] + [TestFixture(8, 8, 4)] public class MultipleContractsTest : MarketplaceAutoBootstrapDistTest { - private const int FilesizeMb = 10; + public MultipleContractsTest(int hosts, int slots, int tolerance) + { + this.hosts = hosts; + this.slots = slots; + this.tolerance = tolerance; + } - protected override int NumberOfHosts => 8; + private const int FilesizeMb = 10; + private readonly int hosts; + private readonly int slots; + private readonly int tolerance; + + protected override int NumberOfHosts => hosts; protected override int NumberOfClients => 3; - protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); - protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration(); + protected override ByteSize HostAvailabilitySize => (100 * FilesizeMb).MB(); + protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration() * 3; private readonly TestToken pricePerBytePerSecond = 10.TstWei(); [Test] - [Ignore("TODO - wip")] [Combinatorial] public void MultipleContractGenerations( - [Values(1, 5, 10)] int numGenerations) + [Values(5)] int numGenerations) { var hosts = StartHosts(); @@ -44,8 +64,13 @@ namespace CodexReleaseTests.MarketTests }); All(requests, r => r.WaitForStorageContractStarted()); - All(requests, r => AssertContractSlotsAreFilledByHosts(r, hosts)); - All(requests, r => r.WaitForStorageContractFinished()); + + Thread.Sleep(TimeSpan.FromSeconds(12.0)); + clients.Stop(waitTillStopped: false); + + // for the time being, we're only interested in whether these contracts start. + //All(requests, r => AssertContractSlotsAreFilledByHosts(r, hosts)); + //All(requests, r => r.WaitForStorageContractFinished()); } private void All(IStoragePurchaseContract[] requests, Action action) @@ -66,11 +91,11 @@ namespace CodexReleaseTests.MarketTests { Duration = GetContractDuration(), Expiry = GetContractExpiry(), - MinRequiredNumberOfNodes = (uint)NumberOfHosts / 2, - NodeFailureTolerance = (uint)(NumberOfHosts / 4), + MinRequiredNumberOfNodes = (uint)slots, + NodeFailureTolerance = (uint)tolerance, PricePerBytePerSecond = pricePerBytePerSecond, ProofProbability = 20, - CollateralPerByte = 1.Tst() + CollateralPerByte = 1.TstWei() }); } @@ -81,7 +106,7 @@ namespace CodexReleaseTests.MarketTests private TimeSpan GetContractDuration() { - return Get8TimesConfiguredPeriodDuration() / 2; + return Get8TimesConfiguredPeriodDuration(); } private TimeSpan Get8TimesConfiguredPeriodDuration() diff --git a/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs index 9b9f4bbf..f6cba228 100644 --- a/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs +++ b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs @@ -1,11 +1,5 @@ -using CodexPlugin; -using CodexTests; +using CodexTests; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Utils; namespace CodexReleaseTests.NodeTests diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs index a1b26c73..ab6a11fb 100644 --- a/Tests/CodexReleaseTests/Parallelism.cs +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -[assembly: LevelOfParallelism(1)] +[assembly: LevelOfParallelism(3)] namespace CodexReleaseTests.DataTests { } From 5d61097838945901e3576244bcad16ab8ba510ed Mon Sep 17 00:00:00 2001 From: ThatBen Date: Thu, 24 Apr 2025 19:33:34 +0200 Subject: [PATCH 03/24] wip --- ProjectPlugins/CodexPlugin/CodexDockerImage.cs | 3 ++- .../MarketTests/ContractSuccessfulTest.cs | 7 +++++-- .../MarketTests/MultipleContractsTest.cs | 9 +++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs index bbb09c0b..ec8d6395 100644 --- a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -2,7 +2,8 @@ { public class CodexDockerImage { - private const string DefaultDockerImage = "codexstorage/nim-codex:latest-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-97e9684-dist-tests"; + //"codexstorage/nim-codex:0.2.1-dist-tests"; public static string Override { get; set; } = string.Empty; diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs index 7750ede1..9aa22eab 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -33,7 +33,7 @@ namespace CodexReleaseTests.MarketTests protected override int NumberOfHosts => hosts; protected override int NumberOfClients => 1; protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); - protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration(); + protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration() * 12; [Test] public void ContractSuccessful() @@ -49,6 +49,9 @@ namespace CodexReleaseTests.MarketTests request.WaitForStorageContractStarted(); AssertContractSlotsAreFilledByHosts(request, hosts); + Thread.Sleep(TimeSpan.FromSeconds(12.0)); + return; + request.WaitForStorageContractFinished(); AssertClientHasPaidForContract(pricePerBytePerSecond, client, request, hosts); @@ -79,7 +82,7 @@ namespace CodexReleaseTests.MarketTests private TimeSpan GetContractDuration() { - return Get8TimesConfiguredPeriodDuration() / 2; + return Get8TimesConfiguredPeriodDuration() * 4; } private TimeSpan Get8TimesConfiguredPeriodDuration() diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index 815c4697..69fe413b 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -34,13 +34,13 @@ namespace CodexReleaseTests.MarketTests protected override int NumberOfHosts => hosts; protected override int NumberOfClients => 3; protected override ByteSize HostAvailabilitySize => (100 * FilesizeMb).MB(); - protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration() * 3; + protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration() * 12; private readonly TestToken pricePerBytePerSecond = 10.TstWei(); [Test] [Combinatorial] public void MultipleContractGenerations( - [Values(5)] int numGenerations) + [Values(5, 10)] int numGenerations) { var hosts = StartHosts(); @@ -49,6 +49,8 @@ namespace CodexReleaseTests.MarketTests Log("Generation: " + i); Generation(hosts); } + + Thread.Sleep(TimeSpan.FromSeconds(12.0)); } private void Generation(ICodexNodeGroup hosts) @@ -65,7 +67,6 @@ namespace CodexReleaseTests.MarketTests All(requests, r => r.WaitForStorageContractStarted()); - Thread.Sleep(TimeSpan.FromSeconds(12.0)); clients.Stop(waitTillStopped: false); // for the time being, we're only interested in whether these contracts start. @@ -106,7 +107,7 @@ namespace CodexReleaseTests.MarketTests private TimeSpan GetContractDuration() { - return Get8TimesConfiguredPeriodDuration(); + return Get8TimesConfiguredPeriodDuration() * 4; } private TimeSpan Get8TimesConfiguredPeriodDuration() From ff78d7e28e877a89965cedcc63586ed2d650926b Mon Sep 17 00:00:00 2001 From: ThatBen Date: Sun, 27 Apr 2025 12:10:45 +0200 Subject: [PATCH 04/24] fixes crash in chain monitor --- .../MarketTests/ChainMonitor.cs | 19 ++++++++++++++----- .../MarketplaceAutoBootstrapDistTest.cs | 6 +++++- .../MarketTests/MultipleContractsTest.cs | 9 +++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs b/Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs index 74f6a96a..f9e6b9d6 100644 --- a/Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs +++ b/Tests/CodexReleaseTests/MarketTests/ChainMonitor.cs @@ -26,10 +26,10 @@ namespace CodexReleaseTests.MarketTests this.updateInterval = updateInterval; } - public void Start() + public void Start(Action onFailure) { cts = new CancellationTokenSource(); - worker = Task.Run(Worker); + worker = Task.Run(() => Worker(onFailure)); } public void Stop() @@ -39,14 +39,23 @@ namespace CodexReleaseTests.MarketTests if (worker.Exception != null) throw worker.Exception; } - private void Worker() + private void Worker(Action onFailure) { var state = new ChainState(log, contracts, new DoNothingChainEventHandler(), startUtc, doProofPeriodMonitoring: true); Thread.Sleep(updateInterval); while (!cts.IsCancellationRequested) { - UpdateChainState(state); + try + { + UpdateChainState(state); + } + catch (Exception ex) + { + log.Error("Exception in chain monitor: " + ex); + onFailure(); + throw; + } cts.Token.WaitHandle.WaitOne(updateInterval); } @@ -61,7 +70,7 @@ namespace CodexReleaseTests.MarketTests var slots = reports.Reports.Sum(r => Convert.ToInt32(r.TotalNumSlots)); var required = reports.Reports.Sum(r => Convert.ToInt32(r.TotalProofsRequired)); - var missed = reports.Reports.Sum(r => Convert.ToInt32(r.MissedProofs)); + var missed = reports.Reports.Sum(r => r.MissedProofs.Length); log.Log($"Proof report: Slots={slots} Required={required} Missed={missed}"); } diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index b0db0702..49a3bcd5 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -7,6 +7,7 @@ using DistTestCore; using GethPlugin; using Logging; using Nethereum.Hex.HexConvertors.Extensions; +using NUnit.Framework; using Utils; namespace CodexReleaseTests.MarketTests @@ -123,7 +124,10 @@ namespace CodexReleaseTests.MarketTests if (!MonitorChainState) return null; var result = new ChainMonitor(log, contracts, startUtc); - result.Start(); + result.Start(() => + { + Assert.Fail("Failure in chain monitor."); + }); return result; } diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index 69fe413b..d6173960 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -43,20 +43,19 @@ namespace CodexReleaseTests.MarketTests [Values(5, 10)] int numGenerations) { var hosts = StartHosts(); + var clients = StartClients(); for (var i = 0; i < numGenerations; i++) { Log("Generation: " + i); - Generation(hosts); + Generation(clients, hosts); } Thread.Sleep(TimeSpan.FromSeconds(12.0)); } - private void Generation(ICodexNodeGroup hosts) + private void Generation(ICodexNodeGroup clients, ICodexNodeGroup hosts) { - var clients = StartClients(); - var requests = clients.Select(CreateStorageRequest).ToArray(); All(requests, r => @@ -67,8 +66,6 @@ namespace CodexReleaseTests.MarketTests All(requests, r => r.WaitForStorageContractStarted()); - clients.Stop(waitTillStopped: false); - // for the time being, we're only interested in whether these contracts start. //All(requests, r => AssertContractSlotsAreFilledByHosts(r, hosts)); //All(requests, r => r.WaitForStorageContractFinished()); From 54227685ecc5c37348f70f99bdf48caa28240518 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Mon, 28 Apr 2025 12:20:36 +0200 Subject: [PATCH 05/24] debugging contract start --- .../CodexPlugin/CodexDockerImage.cs | 2 +- .../MarketTests/MultipleContractsTest.cs | 21 ++++++++++++++----- Tests/CodexReleaseTests/Parallelism.cs | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs index ec8d6395..afcfc747 100644 --- a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -2,7 +2,7 @@ { public class CodexDockerImage { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-97e9684-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-1932aec-dist-tests"; //"codexstorage/nim-codex:0.2.1-dist-tests"; public static string Override { get; set; } = string.Empty; diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index d6173960..e217b280 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -32,8 +32,8 @@ namespace CodexReleaseTests.MarketTests private readonly int tolerance; protected override int NumberOfHosts => hosts; - protected override int NumberOfClients => 3; - protected override ByteSize HostAvailabilitySize => (100 * FilesizeMb).MB(); + protected override int NumberOfClients => 6; + protected override ByteSize HostAvailabilitySize => (1000 * FilesizeMb).MB(); protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration() * 12; private readonly TestToken pricePerBytePerSecond = 10.TstWei(); @@ -56,7 +56,7 @@ namespace CodexReleaseTests.MarketTests private void Generation(ICodexNodeGroup clients, ICodexNodeGroup hosts) { - var requests = clients.Select(CreateStorageRequest).ToArray(); + var requests = All(clients.ToArray(), CreateStorageRequest); All(requests, r => { @@ -71,9 +71,9 @@ namespace CodexReleaseTests.MarketTests //All(requests, r => r.WaitForStorageContractFinished()); } - private void All(IStoragePurchaseContract[] requests, Action action) + private void All(T[] items, Action action) { - var tasks = requests.Select(r => Task.Run(() => action(r))).ToArray(); + var tasks = items.Select(r => Task.Run(() => action(r))).ToArray(); Task.WaitAll(tasks); foreach(var t in tasks) { @@ -81,6 +81,17 @@ namespace CodexReleaseTests.MarketTests } } + private TResult[] All(T[] items, Func action) + { + var tasks = items.Select(r => Task.Run(() => action(r))).ToArray(); + Task.WaitAll(tasks); + foreach (var t in tasks) + { + if (t.Exception != null) throw t.Exception; + } + return tasks.Select(t => t.Result).ToArray(); + } + private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) { var cid = client.UploadFile(GenerateTestFile(FilesizeMb.MB())); diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs index ab6a11fb..a1b26c73 100644 --- a/Tests/CodexReleaseTests/Parallelism.cs +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -[assembly: LevelOfParallelism(3)] +[assembly: LevelOfParallelism(1)] namespace CodexReleaseTests.DataTests { } From 369996afd5bf2503cb7b72ad18d67f5f3bbac87c Mon Sep 17 00:00:00 2001 From: ThatBen Date: Mon, 28 Apr 2025 15:29:40 +0200 Subject: [PATCH 06/24] strip some tests --- .../MarketTests/ContractSuccessfulTest.cs | 6 ------ .../MarketTests/MultipleContractsTest.cs | 9 +-------- Tests/CodexReleaseTests/Parallelism.cs | 2 +- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs index 9aa22eab..9c1ab2e0 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -7,14 +7,8 @@ namespace CodexReleaseTests.MarketTests [TestFixture(6, 3, 1)] [TestFixture(6, 4, 2)] [TestFixture(8, 5, 1)] - [TestFixture(8, 5, 2)] [TestFixture(8, 6, 1)] - [TestFixture(8, 6, 2)] [TestFixture(8, 6, 3)] - [TestFixture(8, 8, 1)] - [TestFixture(8, 8, 2)] - [TestFixture(8, 8, 3)] - [TestFixture(8, 8, 4)] public class ContractSuccessfulTest : MarketplaceAutoBootstrapDistTest { public ContractSuccessfulTest(int hosts, int slots, int tolerance) diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index e217b280..672f9eee 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -7,16 +7,9 @@ namespace CodexReleaseTests.MarketTests { [TestFixture(6, 3, 1)] [TestFixture(6, 4, 1)] - [TestFixture(6, 4, 2)] [TestFixture(8, 5, 1)] - [TestFixture(8, 5, 2)] [TestFixture(8, 6, 1)] - [TestFixture(8, 6, 2)] [TestFixture(8, 6, 3)] - [TestFixture(8, 8, 1)] - [TestFixture(8, 8, 2)] - [TestFixture(8, 8, 3)] - [TestFixture(8, 8, 4)] public class MultipleContractsTest : MarketplaceAutoBootstrapDistTest { public MultipleContractsTest(int hosts, int slots, int tolerance) @@ -40,7 +33,7 @@ namespace CodexReleaseTests.MarketTests [Test] [Combinatorial] public void MultipleContractGenerations( - [Values(5, 10)] int numGenerations) + [Values(10)] int numGenerations) { var hosts = StartHosts(); var clients = StartClients(); diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs index a1b26c73..eb2da742 100644 --- a/Tests/CodexReleaseTests/Parallelism.cs +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -[assembly: LevelOfParallelism(1)] +[assembly: LevelOfParallelism(4)] namespace CodexReleaseTests.DataTests { } From 8d50c008b8dbefee74f238e5ea9cae126393b681 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Mon, 28 Apr 2025 15:44:46 +0200 Subject: [PATCH 07/24] disables pod ips for cluster testing, fix when not at offsite --- Framework/KubernetesWorkflow/K8sController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index ffcac5df..8684dc86 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -747,7 +747,7 @@ namespace KubernetesWorkflow } var pod = pods[0]; if (pod.Status == null) throw new Exception("Pod status unknown"); - if (string.IsNullOrEmpty(pod.Status.PodIP)) throw new Exception("Pod IP unknown"); + //if (string.IsNullOrEmpty(pod.Status.PodIP)) throw new Exception("Pod IP unknown"); return pod; } @@ -977,7 +977,7 @@ namespace KubernetesWorkflow private PodInfo CreatePodInfo(V1Pod pod) { var name = pod.Name(); - var ip = pod.Status.PodIP; + var ip = "disabled"; // pod.Status.PodIP; var k8sNodeName = pod.Spec.NodeName; if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Invalid pod name received. Test infra failure."); From 76ea98e783fd2b89eba6aab513fc45f9d04631f1 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 29 Apr 2025 12:03:13 +0200 Subject: [PATCH 08/24] reverse disable pod ip --- Framework/KubernetesWorkflow/K8sController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/KubernetesWorkflow/K8sController.cs b/Framework/KubernetesWorkflow/K8sController.cs index 8684dc86..ffcac5df 100644 --- a/Framework/KubernetesWorkflow/K8sController.cs +++ b/Framework/KubernetesWorkflow/K8sController.cs @@ -747,7 +747,7 @@ namespace KubernetesWorkflow } var pod = pods[0]; if (pod.Status == null) throw new Exception("Pod status unknown"); - //if (string.IsNullOrEmpty(pod.Status.PodIP)) throw new Exception("Pod IP unknown"); + if (string.IsNullOrEmpty(pod.Status.PodIP)) throw new Exception("Pod IP unknown"); return pod; } @@ -977,7 +977,7 @@ namespace KubernetesWorkflow private PodInfo CreatePodInfo(V1Pod pod) { var name = pod.Name(); - var ip = "disabled"; // pod.Status.PodIP; + var ip = pod.Status.PodIP; var k8sNodeName = pod.Spec.NodeName; if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Invalid pod name received. Test infra failure."); From 90bf718e582c945393aa913edeb7b337dc472dce Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 29 Apr 2025 12:14:06 +0200 Subject: [PATCH 09/24] mergey mistake --- .../MarketTests/MarketplaceAutoBootstrapDistTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index f7d94473..72a56681 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -23,7 +23,7 @@ namespace CodexReleaseTests.MarketTests var geth = StartGethNode(s => s.IsMiner()); var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); var monitor = SetupChainMonitor(GetTestLog(), contracts, GetTestRunTimeRange().From); - handle = new MarketplaceHandle(geth, contracts, monitor)); + handle = new MarketplaceHandle(geth, contracts, monitor); } [TearDown] From 2971e67a512071440ab77320669791f50adbbde4 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 29 Apr 2025 14:26:33 +0200 Subject: [PATCH 10/24] wip fix for race condition in blocktimefinder --- Framework/BlockchainUtils/BlockTimeFinder.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Framework/BlockchainUtils/BlockTimeFinder.cs b/Framework/BlockchainUtils/BlockTimeFinder.cs index a84cda3a..c6f10573 100644 --- a/Framework/BlockchainUtils/BlockTimeFinder.cs +++ b/Framework/BlockchainUtils/BlockTimeFinder.cs @@ -101,10 +101,18 @@ namespace BlockchainUtils previous.Utc < target; } - private BlockTimeEntry GetBlock(ulong number) + private BlockTimeEntry GetBlock(ulong number, bool retry = false) { if (number < bounds.Genesis.BlockNumber) throw new Exception("Can't fetch block before genesis."); - if (number > bounds.Current.BlockNumber) throw new Exception("Can't fetch block after current."); + if (number > bounds.Current.BlockNumber) + { + if (retry) throw new Exception("Can't fetch block after current."); + + // todo test and verify this: + Thread.Sleep(1000); + bounds.Initialize(); + return GetBlock(number, retry: true); + } var dateTime = web3.GetTimestampForBlock(number); if (dateTime == null) throw new Exception("Failed to get dateTime for block that should exist."); From 8a685ffe9a44cfbdfbf60a68a55eaa679153ca5d Mon Sep 17 00:00:00 2001 From: ThatBen Date: Fri, 2 May 2025 10:25:18 +0200 Subject: [PATCH 11/24] 0.2.1 image with three workers. more clients and hosts --- ProjectPlugins/CodexPlugin/CodexDockerImage.cs | 3 +-- .../MarketTests/MultipleContractsTest.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs index afcfc747..55301cf8 100644 --- a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -2,8 +2,7 @@ { public class CodexDockerImage { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-1932aec-dist-tests"; - //"codexstorage/nim-codex:0.2.1-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:0.2.1-dist-tests"; public static string Override { get; set; } = string.Empty; diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index 672f9eee..73b3fa91 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -5,11 +5,11 @@ using Utils; namespace CodexReleaseTests.MarketTests { - [TestFixture(6, 3, 1)] - [TestFixture(6, 4, 1)] - [TestFixture(8, 5, 1)] - [TestFixture(8, 6, 1)] - [TestFixture(8, 6, 3)] + [TestFixture(8, 3, 1)] + [TestFixture(8, 4, 1)] + [TestFixture(10, 5, 1)] + [TestFixture(10, 6, 1)] + [TestFixture(10, 6, 3)] public class MultipleContractsTest : MarketplaceAutoBootstrapDistTest { public MultipleContractsTest(int hosts, int slots, int tolerance) @@ -25,8 +25,8 @@ namespace CodexReleaseTests.MarketTests private readonly int tolerance; protected override int NumberOfHosts => hosts; - protected override int NumberOfClients => 6; - protected override ByteSize HostAvailabilitySize => (1000 * FilesizeMb).MB(); + protected override int NumberOfClients => 8; + protected override ByteSize HostAvailabilitySize => (1000 * FilesizeMb).MB(); protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration() * 12; private readonly TestToken pricePerBytePerSecond = 10.TstWei(); From 809b74b8829c04987970a43c04606c4b80a1a0ae Mon Sep 17 00:00:00 2001 From: ThatBen Date: Sat, 3 May 2025 08:54:38 +0200 Subject: [PATCH 12/24] attempt to harden block finder against rpc timeouts --- Framework/BlockchainUtils/BlockTimeFinder.cs | 3 +- Framework/BlockchainUtils/BlockchainBounds.cs | 2 + Framework/NethereumWorkflow/Web3Wrapper.cs | 41 +++++++++++++------ .../MarketplaceAutoBootstrapDistTest.cs | 3 +- .../MarketTests/MultipleContractsTest.cs | 12 +++--- Tests/CodexReleaseTests/Parallelism.cs | 2 +- 6 files changed, 42 insertions(+), 21 deletions(-) diff --git a/Framework/BlockchainUtils/BlockTimeFinder.cs b/Framework/BlockchainUtils/BlockTimeFinder.cs index c6f10573..ee49836c 100644 --- a/Framework/BlockchainUtils/BlockTimeFinder.cs +++ b/Framework/BlockchainUtils/BlockTimeFinder.cs @@ -20,9 +20,10 @@ namespace BlockchainUtils public BlockTimeEntry Get(ulong blockNumber) { - bounds.Initialize(); var b = cache.Get(blockNumber); if (b != null) return b; + + bounds.Initialize(); return GetBlock(blockNumber); } diff --git a/Framework/BlockchainUtils/BlockchainBounds.cs b/Framework/BlockchainUtils/BlockchainBounds.cs index 27328669..32ce728f 100644 --- a/Framework/BlockchainUtils/BlockchainBounds.cs +++ b/Framework/BlockchainUtils/BlockchainBounds.cs @@ -87,6 +87,8 @@ private void AddCurrentBlock() { var currentBlockNumber = web3.GetCurrentBlockNumber(); + if (Current != null && Current.BlockNumber == currentBlockNumber) return; + var blockTime = web3.GetTimestampForBlock(currentBlockNumber); if (blockTime == null) throw new Exception("Unable to get dateTime for current block."); AddCurrentBlock(currentBlockNumber, blockTime.Value); diff --git a/Framework/NethereumWorkflow/Web3Wrapper.cs b/Framework/NethereumWorkflow/Web3Wrapper.cs index a68ad2e4..b7859685 100644 --- a/Framework/NethereumWorkflow/Web3Wrapper.cs +++ b/Framework/NethereumWorkflow/Web3Wrapper.cs @@ -19,23 +19,40 @@ namespace NethereumWorkflow public ulong GetCurrentBlockNumber() { - var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()); - return Convert.ToUInt64(number.ToDecimal()); + return Retry(() => + { + var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()); + return Convert.ToUInt64(number.ToDecimal()); + }); } public DateTime? GetTimestampForBlock(ulong blockNumber) { - try + return Retry(() => { - var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(blockNumber))); - if (block == null) return null; - return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime; - } - catch (Exception ex) - { - log.Error("Exception while getting timestamp for block: " + ex); - return null; - } + try + { + var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(blockNumber))); + if (block == null) return null; + return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime; + } + catch (Exception ex) + { + log.Error("Exception while getting timestamp for block: " + ex); + return null; + } + }); + } + + private T Retry(Func action) + { + var retry = new Retry(nameof(Web3Wrapper), + maxTimeout: TimeSpan.FromSeconds(30), + sleepAfterFail: TimeSpan.FromSeconds(3), + onFail: f => { }, + failFast: false); + + return retry.Run(action); } } } diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index 72a56681..f22dbaf0 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -122,7 +122,8 @@ namespace CodexReleaseTests.MarketTests var result = new ChainMonitor(log, contracts, startUtc); result.Start(() => { - Assert.Fail("Failure in chain monitor."); + log.Error("Failure in chain monitor. No chain updates after this point."); + //Assert.Fail("Failure in chain monitor."); }); return result; } diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index 73b3fa91..aa5329ff 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -5,11 +5,11 @@ using Utils; namespace CodexReleaseTests.MarketTests { - [TestFixture(8, 3, 1)] + //[TestFixture(8, 3, 1)] [TestFixture(8, 4, 1)] - [TestFixture(10, 5, 1)] - [TestFixture(10, 6, 1)] - [TestFixture(10, 6, 3)] + //[TestFixture(10, 5, 1)] + //[TestFixture(10, 6, 1)] + //[TestFixture(10, 6, 3)] public class MultipleContractsTest : MarketplaceAutoBootstrapDistTest { public MultipleContractsTest(int hosts, int slots, int tolerance) @@ -33,7 +33,7 @@ namespace CodexReleaseTests.MarketTests [Test] [Combinatorial] public void MultipleContractGenerations( - [Values(10)] int numGenerations) + [Values(50)] int numGenerations) { var hosts = StartHosts(); var clients = StartClients(); @@ -96,7 +96,7 @@ namespace CodexReleaseTests.MarketTests MinRequiredNumberOfNodes = (uint)slots, NodeFailureTolerance = (uint)tolerance, PricePerBytePerSecond = pricePerBytePerSecond, - ProofProbability = 20, + ProofProbability = 1, CollateralPerByte = 1.TstWei() }); } diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs index eb2da742..a1b26c73 100644 --- a/Tests/CodexReleaseTests/Parallelism.cs +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -[assembly: LevelOfParallelism(4)] +[assembly: LevelOfParallelism(1)] namespace CodexReleaseTests.DataTests { } From 1b38059559a33157897bf5795f83b8785fa54b82 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Sun, 4 May 2025 11:20:33 +0200 Subject: [PATCH 13/24] wip try get slot reserve calls --- .../NethereumWorkflow/NethereumInteraction.cs | 5 ++++ .../CodexContractsEvents.cs | 26 +++++++++++++++++++ ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- ProjectPlugins/GethPlugin/GethNode.cs | 6 +++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Framework/NethereumWorkflow/NethereumInteraction.cs b/Framework/NethereumWorkflow/NethereumInteraction.cs index 3b2c609c..3407e6b9 100644 --- a/Framework/NethereumWorkflow/NethereumInteraction.cs +++ b/Framework/NethereumWorkflow/NethereumInteraction.cs @@ -143,5 +143,10 @@ namespace NethereumWorkflow var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log); return blockTimeFinder.Get(number); } + + public BlockWithTransactions GetBlk(ulong number) + { + return Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(number))); + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs index 6decfde6..8fb75a07 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs @@ -4,6 +4,8 @@ using GethPlugin; using Logging; using Nethereum.Contracts; using Nethereum.Hex.HexTypes; +using Nethereum.RPC.Eth.DTOs; +using Nethereum.Web3; using Utils; namespace CodexContractsPlugin @@ -19,6 +21,7 @@ namespace CodexContractsPlugin SlotFreedEventDTO[] GetSlotFreedEvents(); SlotReservationsFullEventDTO[] GetSlotReservationsFullEvents(); ProofSubmittedEventDTO[] GetProofSubmittedEvents(); + void Do(); } public class CodexContractsEvents : ICodexContractsEvents @@ -33,10 +36,33 @@ namespace CodexContractsPlugin this.gethNode = gethNode; this.deployment = deployment; BlockInterval = blockInterval; + + Do(); } public BlockInterval BlockInterval { get; } + public void Do() + { + for (ulong i = BlockInterval.From; i <= BlockInterval.To; i++) + { + var block = gethNode.GetBlk(i); + if (block == null) return; + + foreach (var t in block.Transactions) + { + if (t == null) continue; + + var input = t.ConvertToTransactionInput(); + var aaa = t.DecodeTransactionToFunctionMessage(); + if (aaa != null) + { + var a = 0; + } + } + } + } + public Request[] GetStorageRequests() { var events = gethNode.GetEvents(deployment.MarketplaceAddress, BlockInterval); diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index 4b190b34..ba799e60 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -10,7 +10,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "06-B9-41-E8-C8-6C-DE-01-86-83-F3-9A-E4-AC-E7-30-D9-E6-64-60-E0-21-81-9E-4E-C5-93-77-2C-71-79-14"; + private const string OpenApiYamlHash = "FD-C8-0F-19-5E-14-09-C9-05-93-17-4A-97-50-1D-7E-37-50-B2-30-B2-E6-66-37-23-FA-35-F5-AB-A0-C6-BD"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/GethPlugin/GethNode.cs b/ProjectPlugins/GethPlugin/GethNode.cs index 26bf1f03..6aee2d73 100644 --- a/ProjectPlugins/GethPlugin/GethNode.cs +++ b/ProjectPlugins/GethPlugin/GethNode.cs @@ -31,6 +31,7 @@ namespace GethPlugin List> GetEvents(string address, TimeRange timeRange) where TEvent : IEventDTO, new(); BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange); BlockTimeEntry GetBlockForNumber(ulong number); + BlockWithTransactions GetBlk(ulong number); } public class DeploymentGethNode : BaseGethNode, IGethNode @@ -183,6 +184,11 @@ namespace GethPlugin return StartInteraction().GetBlockForNumber(number); } + public BlockWithTransactions GetBlk(ulong number) + { + return StartInteraction().GetBlk(number); + } + protected abstract NethereumInteraction StartInteraction(); } } From 69cf4283fa13c2270444d9ac055410a0d798b38b Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 6 May 2025 20:33:37 +0200 Subject: [PATCH 14/24] Adds assert all slots were fully reserved. Adds logs for calls to reserveslot when start fails. --- .../NethereumWorkflow/NethereumInteraction.cs | 2 +- .../CodexContractsEvents.cs | 32 +++-------- ProjectPlugins/GethPlugin/GethNode.cs | 20 ++++++- .../MarketTests/ContractSuccessfulTest.cs | 3 +- .../MarketplaceAutoBootstrapDistTest.cs | 55 +++++++++++++++++++ .../MarketTests/MultipleContractsTest.cs | 3 +- 6 files changed, 86 insertions(+), 29 deletions(-) diff --git a/Framework/NethereumWorkflow/NethereumInteraction.cs b/Framework/NethereumWorkflow/NethereumInteraction.cs index 3407e6b9..6a34e848 100644 --- a/Framework/NethereumWorkflow/NethereumInteraction.cs +++ b/Framework/NethereumWorkflow/NethereumInteraction.cs @@ -144,7 +144,7 @@ namespace NethereumWorkflow return blockTimeFinder.Get(number); } - public BlockWithTransactions GetBlk(ulong number) + public BlockWithTransactions GetBlockWithTransactions(ulong number) { return Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(number))); } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs index 8fb75a07..3fd07634 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs @@ -21,7 +21,7 @@ namespace CodexContractsPlugin SlotFreedEventDTO[] GetSlotFreedEvents(); SlotReservationsFullEventDTO[] GetSlotReservationsFullEvents(); ProofSubmittedEventDTO[] GetProofSubmittedEvents(); - void Do(); + ReserveSlotFunction[] GetReserveSlotCalls(); } public class CodexContractsEvents : ICodexContractsEvents @@ -36,33 +36,10 @@ namespace CodexContractsPlugin this.gethNode = gethNode; this.deployment = deployment; BlockInterval = blockInterval; - - Do(); } public BlockInterval BlockInterval { get; } - public void Do() - { - for (ulong i = BlockInterval.From; i <= BlockInterval.To; i++) - { - var block = gethNode.GetBlk(i); - if (block == null) return; - - foreach (var t in block.Transactions) - { - if (t == null) continue; - - var input = t.ConvertToTransactionInput(); - var aaa = t.DecodeTransactionToFunctionMessage(); - if (aaa != null) - { - var a = 0; - } - } - } - } - public Request[] GetStorageRequests() { var events = gethNode.GetEvents(deployment.MarketplaceAddress, BlockInterval); @@ -125,6 +102,13 @@ namespace CodexContractsPlugin return events.Select(SetBlockOnEvent).ToArray(); } + public ReserveSlotFunction[] GetReserveSlotCalls() + { + var result = new List(); + gethNode.IterateFunctionCalls(BlockInterval, result.Add); + return result.ToArray(); + } + private T SetBlockOnEvent(EventLog e) where T : IHasBlock { var result = e.Event; diff --git a/ProjectPlugins/GethPlugin/GethNode.cs b/ProjectPlugins/GethPlugin/GethNode.cs index 6aee2d73..9cd11ac3 100644 --- a/ProjectPlugins/GethPlugin/GethNode.cs +++ b/ProjectPlugins/GethPlugin/GethNode.cs @@ -31,7 +31,8 @@ namespace GethPlugin List> GetEvents(string address, TimeRange timeRange) where TEvent : IEventDTO, new(); BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange); BlockTimeEntry GetBlockForNumber(ulong number); - BlockWithTransactions GetBlk(ulong number); + void IterateFunctionCalls(BlockInterval blockInterval, Action onCall) where TFunc : FunctionMessage; + } public class DeploymentGethNode : BaseGethNode, IGethNode @@ -186,7 +187,22 @@ namespace GethPlugin public BlockWithTransactions GetBlk(ulong number) { - return StartInteraction().GetBlk(number); + return StartInteraction().GetBlockWithTransactions(number); + } + + public void IterateFunctionCalls(BlockInterval blockRange, Action onCall) where TFunc : FunctionMessage, new() + { + var i = StartInteraction(); + for (var blkI = blockRange.From; blkI <= blockRange.To; blkI++) + { + var blk = i.GetBlockWithTransactions(blkI); + + foreach (var t in blk.Transactions) + { + var func = t.DecodeTransactionToFunctionMessage(); + if (func != null) onCall(func); + } + } } protected abstract NethereumInteraction StartInteraction(); diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs index 9c1ab2e0..a89059f3 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -39,8 +39,9 @@ namespace CodexReleaseTests.MarketTests request.WaitForStorageContractSubmitted(); AssertContractIsOnChain(request); + WaitUntilSlotReservationsFull(request); - request.WaitForStorageContractStarted(); + WaitForContractStarted(request); AssertContractSlotsAreFilledByHosts(request, hosts); Thread.Sleep(TimeSpan.FromSeconds(12.0)); diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index f22dbaf0..b6f624db 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -254,6 +254,28 @@ namespace CodexReleaseTests.MarketTests } } + protected void WaitForContractStarted(IStoragePurchaseContract r) + { + try + { + r.WaitForStorageContractStarted(); + } + catch + { + // Contract failed to start. Retrieve and log every call to ReserveSlot to identify which hosts + // should have filled the slot. + + var requestId = r.PurchaseId.ToLowerInvariant(); + var calls = GetContracts().GetEvents(GetTestRunTimeRange()).GetReserveSlotCalls(); + + Log($"Request '{requestId}' failed to start. There were {calls.Length} hosts who called reserve-slot for it:"); + foreach (var c in calls) + { + Log($" - Host: {c.FromAddress} RequestId: {c.RequestId.ToHex()} SlotIndex: {c.SlotIndex}"); + } + } + } + private TestToken GetContractFinalCost(TestToken pricePerBytePerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts) { var fills = GetOnChainSlotFills(hosts); @@ -323,6 +345,39 @@ namespace CodexReleaseTests.MarketTests }, nameof(AssertContractIsOnChain)); } + protected void WaitUntilSlotReservationsFull(IStoragePurchaseContract contract) + { + var requestId = contract.PurchaseId.ToLowerInvariant(); + var slots = contract.Purchase.MinRequiredNumberOfNodes; + + var timeout = TimeSpan.FromMinutes(1.0); + var start = DateTime.UtcNow; + var fullIndices = new List(); + + while (DateTime.UtcNow - start < timeout) + { + Thread.Sleep(TimeSpan.FromSeconds(3.0)); + + var fullEvents = GetContracts().GetEvents(GetTestRunTimeRange()).GetSlotReservationsFullEvents(); + foreach (var e in fullEvents) + { + if (e.RequestId.ToHex().ToLowerInvariant() == requestId) + { + if (!fullIndices.Contains(e.SlotIndex)) + { + fullIndices.Add(e.SlotIndex); + if (fullIndices.Count == slots) return; + } + } + } + } + + Assert.Fail( + $"Slot reservations were not full after {Time.FormatDuration(timeout)}." + + $" Slots: {slots} Filled: {string.Join(",", fullIndices.Select(i => i.ToString()))}" + ); + } + protected void AssertOnChainEvents(Action onEvents, string description) { Time.Retry(() => diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index aa5329ff..e5588efb 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -57,7 +57,8 @@ namespace CodexReleaseTests.MarketTests AssertContractIsOnChain(r); }); - All(requests, r => r.WaitForStorageContractStarted()); + All(requests, WaitUntilSlotReservationsFull); + All(requests, WaitForContractStarted); // for the time being, we're only interested in whether these contracts start. //All(requests, r => AssertContractSlotsAreFilledByHosts(r, hosts)); From b3d35933a8e77514094049390560fc3c4fe66f64 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Wed, 7 May 2025 15:20:24 +0200 Subject: [PATCH 15/24] Trying to log slot reservation calls --- ProjectPlugins/CodexPlugin/ApiChecker.cs | 2 +- ProjectPlugins/GethPlugin/GethNode.cs | 10 +++--- .../MarketTests/ContractSuccessfulTest.cs | 1 - .../MarketplaceAutoBootstrapDistTest.cs | 33 ------------------- .../MarketTests/MultipleContractsTest.cs | 1 - 5 files changed, 7 insertions(+), 40 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index ba799e60..4b190b34 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -10,7 +10,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "FD-C8-0F-19-5E-14-09-C9-05-93-17-4A-97-50-1D-7E-37-50-B2-30-B2-E6-66-37-23-FA-35-F5-AB-A0-C6-BD"; + private const string OpenApiYamlHash = "06-B9-41-E8-C8-6C-DE-01-86-83-F3-9A-E4-AC-E7-30-D9-E6-64-60-E0-21-81-9E-4E-C5-93-77-2C-71-79-14"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/GethPlugin/GethNode.cs b/ProjectPlugins/GethPlugin/GethNode.cs index 9cd11ac3..e94a5e09 100644 --- a/ProjectPlugins/GethPlugin/GethNode.cs +++ b/ProjectPlugins/GethPlugin/GethNode.cs @@ -31,8 +31,7 @@ namespace GethPlugin List> GetEvents(string address, TimeRange timeRange) where TEvent : IEventDTO, new(); BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange); BlockTimeEntry GetBlockForNumber(ulong number); - void IterateFunctionCalls(BlockInterval blockInterval, Action onCall) where TFunc : FunctionMessage; - + void IterateFunctionCalls(BlockInterval blockInterval, Action onCall) where TFunc : FunctionMessage, new(); } public class DeploymentGethNode : BaseGethNode, IGethNode @@ -199,8 +198,11 @@ namespace GethPlugin foreach (var t in blk.Transactions) { - var func = t.DecodeTransactionToFunctionMessage(); - if (func != null) onCall(func); + if (t.IsTransactionForFunctionMessage()) + { + var func = t.DecodeTransactionToFunctionMessage(); + if (func != null) onCall(func); + } } } } diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs index a89059f3..51f9b79b 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -39,7 +39,6 @@ namespace CodexReleaseTests.MarketTests request.WaitForStorageContractSubmitted(); AssertContractIsOnChain(request); - WaitUntilSlotReservationsFull(request); WaitForContractStarted(request); AssertContractSlotsAreFilledByHosts(request, hosts); diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index b6f624db..146cc993 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -345,39 +345,6 @@ namespace CodexReleaseTests.MarketTests }, nameof(AssertContractIsOnChain)); } - protected void WaitUntilSlotReservationsFull(IStoragePurchaseContract contract) - { - var requestId = contract.PurchaseId.ToLowerInvariant(); - var slots = contract.Purchase.MinRequiredNumberOfNodes; - - var timeout = TimeSpan.FromMinutes(1.0); - var start = DateTime.UtcNow; - var fullIndices = new List(); - - while (DateTime.UtcNow - start < timeout) - { - Thread.Sleep(TimeSpan.FromSeconds(3.0)); - - var fullEvents = GetContracts().GetEvents(GetTestRunTimeRange()).GetSlotReservationsFullEvents(); - foreach (var e in fullEvents) - { - if (e.RequestId.ToHex().ToLowerInvariant() == requestId) - { - if (!fullIndices.Contains(e.SlotIndex)) - { - fullIndices.Add(e.SlotIndex); - if (fullIndices.Count == slots) return; - } - } - } - } - - Assert.Fail( - $"Slot reservations were not full after {Time.FormatDuration(timeout)}." + - $" Slots: {slots} Filled: {string.Join(",", fullIndices.Select(i => i.ToString()))}" - ); - } - protected void AssertOnChainEvents(Action onEvents, string description) { Time.Retry(() => diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs index e5588efb..df71eb3b 100644 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs @@ -57,7 +57,6 @@ namespace CodexReleaseTests.MarketTests AssertContractIsOnChain(r); }); - All(requests, WaitUntilSlotReservationsFull); All(requests, WaitForContractStarted); // for the time being, we're only interested in whether these contracts start. From a85caae203b6fa401d6dfaa86a3d586c787cb9b7 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 10:19:07 +0200 Subject: [PATCH 16/24] Setting up contract tracer --- .../CodexContractsEvents.cs | 8 +- .../Marketplace/Customizations.cs | 17 +- ProjectPlugins/GethPlugin/GethNode.cs | 10 +- .../MarketplaceAutoBootstrapDistTest.cs | 2 +- TraceContract/Config.cs | 11 ++ TraceContract/Output.cs | 18 ++ TraceContract/Program.cs | 169 ++++++++++++++++++ TraceContract/TraceContract.csproj | 16 ++ cs-codex-dist-testing.sln | 7 + 9 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 TraceContract/Config.cs create mode 100644 TraceContract/Output.cs create mode 100644 TraceContract/Program.cs create mode 100644 TraceContract/TraceContract.csproj diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs index 3fd07634..e2d8a11a 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs @@ -4,8 +4,6 @@ using GethPlugin; using Logging; using Nethereum.Contracts; using Nethereum.Hex.HexTypes; -using Nethereum.RPC.Eth.DTOs; -using Nethereum.Web3; using Utils; namespace CodexContractsPlugin @@ -105,7 +103,11 @@ namespace CodexContractsPlugin public ReserveSlotFunction[] GetReserveSlotCalls() { var result = new List(); - gethNode.IterateFunctionCalls(BlockInterval, result.Add); + gethNode.IterateFunctionCalls(BlockInterval, (b, fn) => + { + fn.Block = b; + result.Add(fn); + }); return result.ToArray(); } diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index a93df5d1..222dc631 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -15,6 +15,11 @@ namespace CodexContractsPlugin.Marketplace byte[] RequestId { get; set; } } + public interface IHasSlotIndex + { + ulong SlotIndex { get; set; } + } + public partial class Request : RequestBase, IHasBlock, IHasRequestId { [JsonIgnore] @@ -51,20 +56,20 @@ namespace CodexContractsPlugin.Marketplace public BlockTimeEntry Block { get; set; } } - public partial class SlotFilledEventDTO : IHasBlock, IHasRequestId + public partial class SlotFilledEventDTO : IHasBlock, IHasRequestId, IHasSlotIndex { [JsonIgnore] public BlockTimeEntry Block { get; set; } public EthAddress Host { get; set; } } - public partial class SlotFreedEventDTO : IHasBlock, IHasRequestId + public partial class SlotFreedEventDTO : IHasBlock, IHasRequestId, IHasSlotIndex { [JsonIgnore] public BlockTimeEntry Block { get; set; } } - public partial class SlotReservationsFullEventDTO : IHasBlock, IHasRequestId + public partial class SlotReservationsFullEventDTO : IHasBlock, IHasRequestId, IHasSlotIndex { [JsonIgnore] public BlockTimeEntry Block { get; set; } @@ -75,5 +80,11 @@ namespace CodexContractsPlugin.Marketplace [JsonIgnore] public BlockTimeEntry Block { get; set; } } + + public partial class ReserveSlotFunction : IHasBlock, IHasRequestId, IHasSlotIndex + { + [JsonIgnore] + public BlockTimeEntry Block { get; set; } + } } #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/ProjectPlugins/GethPlugin/GethNode.cs b/ProjectPlugins/GethPlugin/GethNode.cs index e94a5e09..cfd0af37 100644 --- a/ProjectPlugins/GethPlugin/GethNode.cs +++ b/ProjectPlugins/GethPlugin/GethNode.cs @@ -31,7 +31,7 @@ namespace GethPlugin List> GetEvents(string address, TimeRange timeRange) where TEvent : IEventDTO, new(); BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange); BlockTimeEntry GetBlockForNumber(ulong number); - void IterateFunctionCalls(BlockInterval blockInterval, Action onCall) where TFunc : FunctionMessage, new(); + void IterateFunctionCalls(BlockInterval blockInterval, Action onCall) where TFunc : FunctionMessage, new(); } public class DeploymentGethNode : BaseGethNode, IGethNode @@ -189,7 +189,7 @@ namespace GethPlugin return StartInteraction().GetBlockWithTransactions(number); } - public void IterateFunctionCalls(BlockInterval blockRange, Action onCall) where TFunc : FunctionMessage, new() + public void IterateFunctionCalls(BlockInterval blockRange, Action onCall) where TFunc : FunctionMessage, new() { var i = StartInteraction(); for (var blkI = blockRange.From; blkI <= blockRange.To; blkI++) @@ -201,7 +201,11 @@ namespace GethPlugin if (t.IsTransactionForFunctionMessage()) { var func = t.DecodeTransactionToFunctionMessage(); - if (func != null) onCall(func); + if (func != null) + { + var b = GetBlockForNumber(blkI); + onCall(b, func); + } } } } diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index 146cc993..39d2e1b3 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -271,7 +271,7 @@ namespace CodexReleaseTests.MarketTests Log($"Request '{requestId}' failed to start. There were {calls.Length} hosts who called reserve-slot for it:"); foreach (var c in calls) { - Log($" - Host: {c.FromAddress} RequestId: {c.RequestId.ToHex()} SlotIndex: {c.SlotIndex}"); + Log($" - {c.Block.Utc} Host: {c.FromAddress} RequestId: {c.RequestId.ToHex()} SlotIndex: {c.SlotIndex}"); } } } diff --git a/TraceContract/Config.cs b/TraceContract/Config.cs new file mode 100644 index 00000000..8f52e7cf --- /dev/null +++ b/TraceContract/Config.cs @@ -0,0 +1,11 @@ +namespace TraceContract +{ + public class Config + { + public string RpcEndpoint { get; } = "https://rpc.testnet.codex.storage"; + public int GethPort { get; } = 443; + 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""}]"; + } +} diff --git a/TraceContract/Output.cs b/TraceContract/Output.cs new file mode 100644 index 00000000..124b2796 --- /dev/null +++ b/TraceContract/Output.cs @@ -0,0 +1,18 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; + +namespace TraceContract +{ + public class Output + { + public void LogRequest(Request request) + { + throw new NotImplementedException(); + } + + public void LogEventOrCall(T[] calls) where T : IHasRequestId, IHasBlock + { + throw new NotImplementedException(); + } + } +} diff --git a/TraceContract/Program.cs b/TraceContract/Program.cs new file mode 100644 index 00000000..41c4cfd7 --- /dev/null +++ b/TraceContract/Program.cs @@ -0,0 +1,169 @@ +using BlockchainUtils; +using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; +using Core; +using GethPlugin; +using Logging; +using Nethereum.Hex.HexConvertors.Extensions; +using Utils; + +namespace TraceContract +{ + public class Input + { + public string PurchaseId { get; } = "a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; + } + + public class Program + { + public static void Main(string[] args) + { + var p = new Program(); + p.Run(); + } + + private readonly ILog log = new ConsoleLog(); + private readonly Input input = new(); + private readonly Output output = new(); + private readonly Config config = new(); + + private void Run() + { + try + { + TracePurchase(); + } + catch (Exception exc) + { + log.Error(exc.ToString()); + } + } + + private void TracePurchase() + { + Log("Setting up..."); + var contracts = ConnectCodexContracts(); + + var chainTracer = new ChainTracer(log, contracts, input, output); + 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 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(), + marketplaceAddress: config.MarketplaceAddress, + abi: config.Abi, + tokenAddress: config.TokenAddress + ); + return ci.WrapCodexContractsDeployment(geth, deployment); + } + + 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 void TraceChainTimeline() + { + var request = GetRequest(); + if (request == null) throw new Exception("Failed to find the purchase in the last week of transactions."); + output.LogRequest(request); + + var requestTimeRange = new TimeRange(request.Block.Utc.AddMinutes(-1.0), DateTime.UtcNow); + var events = contracts.GetEvents(requestTimeRange); + + // Log calls to reserve slot for request + var calls = Filter(events.GetReserveSlotCalls()); + output.LogEventOrCall(calls); + + // log all events. + var fulls = events.GetSlotReservationsFullEvents(); + output.LogEventOrCall(Filter(fulls)); + + } + + private T[] Filter(T[] calls) where T : IHasRequestId + { + 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 TimeRange LastHour() + { + return new TimeRange(DateTime.UtcNow.AddHours(-1.0), DateTime.UtcNow); + } + + private TimeRange LastDay() + { + return new TimeRange(DateTime.UtcNow.AddDays(-1.0), DateTime.UtcNow); + } + + private TimeRange LastWeek() + { + return new TimeRange(DateTime.UtcNow.AddDays(-7.0), DateTime.UtcNow); + } + + private void Log(string msg) + { + log.Log(msg); + } + } +} diff --git a/TraceContract/TraceContract.csproj b/TraceContract/TraceContract.csproj new file mode 100644 index 00000000..3bcdeedb --- /dev/null +++ b/TraceContract/TraceContract.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index 9b1c0977..18dd9a2e 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -86,6 +86,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexClient", "ProjectPlugi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUtils", "Framework\WebUtils\WebUtils.csproj", "{372C9E5D-5453-4D45-9948-E9324E21AD65}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceContract", "TraceContract\TraceContract.csproj", "{6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -232,6 +234,10 @@ Global {372C9E5D-5453-4D45-9948-E9324E21AD65}.Debug|Any CPU.Build.0 = Debug|Any CPU {372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.ActiveCfg = Release|Any CPU {372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.Build.0 = Release|Any CPU + {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -272,6 +278,7 @@ Global {4648B5AA-A0A7-44BA-89BC-2FD57370943C} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} {9AF12703-29AF-416D-9781-204223D6D0E5} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124} {372C9E5D-5453-4D45-9948-E9324E21AD65} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} + {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C} From 8776591094f6e1649ac6fc3b7e03539c0ba17659 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 12:47:36 +0200 Subject: [PATCH 17/24] wip --- TraceContract/Output.cs | 56 ++++++++++++++- TraceContract/Program.cs | 151 +++++++++++++++++++++++++++++++++------ 2 files changed, 184 insertions(+), 23 deletions(-) diff --git a/TraceContract/Output.cs b/TraceContract/Output.cs index 124b2796..6a731e0f 100644 --- a/TraceContract/Output.cs +++ b/TraceContract/Output.cs @@ -1,16 +1,66 @@ -using CodexContractsPlugin; +using System.Numerics; +using CodexContractsPlugin.ChainMonitor; using CodexContractsPlugin.Marketplace; +using Logging; +using Utils; namespace TraceContract { public class Output { - public void LogRequest(Request request) + private readonly ILog log; + + public Output(ILog log) + { + this.log = log; + } + + public void LogRequestCreated(RequestEvent requestEvent) { throw new NotImplementedException(); } - public void LogEventOrCall(T[] calls) where T : IHasRequestId, IHasBlock + public void LogRequestCancelled(RequestEvent requestEvent) + { + throw new NotImplementedException(); + } + + public void LogRequestFailed(RequestEvent requestEvent) + { + throw new NotImplementedException(); + } + + public void LogRequestFinished(RequestEvent requestEvent) + { + throw new NotImplementedException(); + } + + public void LogRequestStarted(RequestEvent requestEvent) + { + throw new NotImplementedException(); + } + + public void LogSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) + { + throw new NotImplementedException(); + } + + public void LogSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) + { + throw new NotImplementedException(); + } + + public void LogSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex) + { + throw new NotImplementedException(); + } + + public void LogReserveSlotCalls(ReserveSlotFunction[] reserveSlotFunctions) + { + throw new NotImplementedException(); + } + + public void WriteContractEvents() { throw new NotImplementedException(); } diff --git a/TraceContract/Program.cs b/TraceContract/Program.cs index 41c4cfd7..eabb7e16 100644 --- a/TraceContract/Program.cs +++ b/TraceContract/Program.cs @@ -1,5 +1,7 @@ -using BlockchainUtils; +using System.Numerics; +using BlockchainUtils; using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; using CodexContractsPlugin.Marketplace; using Core; using GethPlugin; @@ -11,7 +13,12 @@ namespace TraceContract { public class Input { - public string PurchaseId { get; } = "a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; + public string PurchaseId { get; } = + // expired: + //"a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; + + // started: + "066df09a3a2e2587cfd577a0e96186c915b113d02b331b06e56f808494cff2b4"; } public class Program @@ -24,8 +31,13 @@ namespace TraceContract private readonly ILog log = new ConsoleLog(); private readonly Input input = new(); - private readonly Output output = new(); private readonly Config config = new(); + private readonly Output output; + + public Program() + { + output = new(log); + } private void Run() { @@ -45,7 +57,7 @@ namespace TraceContract var contracts = ConnectCodexContracts(); var chainTracer = new ChainTracer(log, contracts, input, output); - chainTracer.TraceChainTimeline(); + var requestTimeRange = chainTracer.TraceChainTimeline(); Log("Done"); } @@ -93,26 +105,34 @@ namespace TraceContract this.output = output; } - public void TraceChainTimeline() + public TimeRange TraceChainTimeline() { var request = GetRequest(); if (request == null) throw new Exception("Failed to find the purchase in the last week of transactions."); - output.LogRequest(request); - var requestTimeRange = new TimeRange(request.Block.Utc.AddMinutes(-1.0), DateTime.UtcNow); - var events = contracts.GetEvents(requestTimeRange); + 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); - // Log calls to reserve slot for request - var calls = Filter(events.GetReserveSlotCalls()); - output.LogEventOrCall(calls); + while (!tracker.IsFinished) + { + utc += TimeSpan.FromHours(1.0); + chainState.Update(utc); + } - // log all events. - var fulls = events.GetSlotReservationsFullEvents(); - output.LogEventOrCall(Filter(fulls)); + 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 T[] Filter(T[] calls) where T : IHasRequestId + private ReserveSlotFunction[] Filter(ReserveSlotFunction[] calls) { return calls.Where(c => IsThisRequest(c.RequestId)).ToArray(); } @@ -146,24 +166,115 @@ namespace TraceContract return requestId.ToHex().ToLowerInvariant() == input.PurchaseId.ToLowerInvariant(); } - private TimeRange LastHour() + private static TimeRange LastHour() { return new TimeRange(DateTime.UtcNow.AddHours(-1.0), DateTime.UtcNow); } - private TimeRange LastDay() + private static TimeRange LastDay() { return new TimeRange(DateTime.UtcNow.AddDays(-1.0), DateTime.UtcNow); } - private TimeRange LastWeek() + private static TimeRange LastWeek() { return new TimeRange(DateTime.UtcNow.AddDays(-7.0), DateTime.UtcNow); } + } - private void Log(string msg) + public class ChainRequestTracker : IChainStateChangeHandler + { + private readonly string requestId; + private readonly Output output; + + public ChainRequestTracker(Output output, string requestId) { - log.Log(msg); + 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(); } } + } From 9e9b147b68c1ebaea079c754c6e43e3a77f8c2a9 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 14:16:33 +0200 Subject: [PATCH 18/24] 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(); - } - } - } From 74c44c4ef87bfd20558b01431b41632212814b14 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 14:41:33 +0200 Subject: [PATCH 19/24] wip setting up for packing and upload --- TraceContract/Config.cs | 34 ++++++++++++++++----- TraceContract/ElasticSearchLogDownloader.cs | 29 ++++++++++++------ TraceContract/Input.cs | 20 ++++++++---- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/TraceContract/Config.cs b/TraceContract/Config.cs index 51c5e4d1..189beb5e 100644 --- a/TraceContract/Config.cs +++ b/TraceContract/Config.cs @@ -15,17 +15,18 @@ /// public TimeSpan LogStartBeforeStorageContractStarts { get; } = TimeSpan.FromMinutes(1.0); + public string ElasticSearchUrl { get; } = $"https://es.testnet.codex.storage"; 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-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", ]; @@ -43,5 +44,22 @@ { "0xE7EEb996B3c817cEd03d10cd64A1325DA33D92e7", "codex 09" }, { "0xD25C7609e97F40b66E74c0FcEbeA06D09423CC7e", "codex 10" } }; + + public string GetElasticSearchUsername() + { + return GetEnvVar("ES_USERNAME", "username"); + } + + public string GetElasticSearchPassword() + { + return GetEnvVar("ES_PASSWORD", "password"); + } + + private string GetEnvVar(string name, string defaultValue) + { + var v = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(v)) return defaultValue; + return v; + } } } diff --git a/TraceContract/ElasticSearchLogDownloader.cs b/TraceContract/ElasticSearchLogDownloader.cs index 8b40ed0b..f1a76557 100644 --- a/TraceContract/ElasticSearchLogDownloader.cs +++ b/TraceContract/ElasticSearchLogDownloader.cs @@ -1,21 +1,22 @@ -using Core; +using System.Text; +using Core; using Logging; using Utils; using WebUtils; -namespace ContinuousTests +namespace TraceContract { public class ElasticSearchLogDownloader { private readonly ILog log; private readonly IPluginTools tools; - private readonly string k8SNamespace; + private readonly Config config; - public ElasticSearchLogDownloader(ILog log, IPluginTools tools, string k8sNamespace) + public ElasticSearchLogDownloader(ILog log, IPluginTools tools, Config config) { this.log = log; this.tools = tools; - k8SNamespace = k8sNamespace; + this.config = config; } public void Download(LogFile targetFile, string containerName, DateTime startUtc, DateTime endUtc) @@ -59,19 +60,29 @@ namespace ContinuousTests .Replace("", start) .Replace("", end) .Replace("", containerName) - .Replace("", k8SNamespace); + .Replace("", config.StorageNodesKubernetesNamespace); } private IEndpoint CreateElasticSearchEndpoint() { - var serviceName = "elasticsearch"; - var k8sNamespace = "monitoring"; - var address = new Address("ElasticSearchEndpoint", $"http://{serviceName}.{k8sNamespace}.svc.cluster.local", 9200); + //var serviceName = "elasticsearch"; + //var k8sNamespace = "monitoring"; + //var address = new Address("ElasticSearchEndpoint", $"http://{serviceName}.{k8sNamespace}.svc.cluster.local", 9200); + + var address = new Address("TestnetElasticSearchEndpoint", config.ElasticSearchUrl, 443); var baseUrl = ""; + var username = config.GetElasticSearchUsername(); + var password = config.GetElasticSearchPassword(); + + var base64Creds = Convert.ToBase64String( + Encoding.ASCII.GetBytes($"{username}:{password}") + ); + var http = tools.CreateHttp(address.ToString(), client => { client.DefaultRequestHeaders.Add("kbn-xsrf", "reporting"); + client.DefaultRequestHeaders.Add("Authorization", "Basic " + base64Creds); }); return http.CreateEndpoint(address, baseUrl); diff --git a/TraceContract/Input.cs b/TraceContract/Input.cs index 50a23ebb..a0c63a03 100644 --- a/TraceContract/Input.cs +++ b/TraceContract/Input.cs @@ -2,12 +2,20 @@ { public class Input { - public string PurchaseId { get; } = - // expired: - "a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; + public string PurchaseId + { + get + { + var v = Environment.GetEnvironmentVariable("PURCHASE_ID"); + if (!string.IsNullOrEmpty(v)) return v; - // started: - //"066df09a3a2e2587cfd577a0e96186c915b113d02b331b06e56f808494cff2b4"; + return + // expired: + "a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; + + // started: + //"066df09a3a2e2587cfd577a0e96186c915b113d02b331b06e56f808494cff2b4"; + } + } } - } From 873e4750ab4ec6f3188db32aa89a786b93dfe591 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 15:09:38 +0200 Subject: [PATCH 20/24] packaging --- TraceContract/Config.cs | 5 +++++ TraceContract/Output.cs | 35 +++++++++++++++++++++++++---------- TraceContract/Program.cs | 10 ++++++---- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/TraceContract/Config.cs b/TraceContract/Config.cs index 189beb5e..f6b7f1ad 100644 --- a/TraceContract/Config.cs +++ b/TraceContract/Config.cs @@ -55,6 +55,11 @@ return GetEnvVar("ES_PASSWORD", "password"); } + public string GetOuputFolder() + { + return GetEnvVar("OUTPUT_FOLDER", "/tmp"); + } + private string GetEnvVar(string name, string defaultValue) { var v = Environment.GetEnvironmentVariable(name); diff --git a/TraceContract/Output.cs b/TraceContract/Output.cs index d7c230ba..a5d09bb1 100644 --- a/TraceContract/Output.cs +++ b/TraceContract/Output.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System.IO.Compression; +using System.Numerics; using CodexContractsPlugin.ChainMonitor; using CodexContractsPlugin.Marketplace; using Logging; @@ -24,15 +25,17 @@ namespace TraceContract private readonly List entries = new(); private readonly string folder; private readonly List files = new(); + private readonly Input input; + private readonly Config config; 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 filename = Path.Combine(folder, $"contract_{input.PurchaseId}"); var fileLog = new FileLog(filename); - files.Add(fileLog.FullFilename); + files.Add(fileLog.FullFilename + ".log"); foreach (var pair in config.LogReplacements) { fileLog.AddStringReplace(pair.Key, pair.Value); @@ -41,6 +44,8 @@ namespace TraceContract log.Log($"Logging to '{filename}'"); this.log = new LogSplitter(fileLog, log); + this.input = input; + this.config = config; } public void LogRequestCreated(RequestEvent requestEvent) @@ -94,6 +99,13 @@ namespace TraceContract foreach (var e in sorted) Write(e); } + public LogFile CreateNodeLogTargetFile(string node) + { + var file = log.CreateSubfile(node); + files.Add(file.Filename); + return file; + } + private void Write(Entry e) { log.Log($"[{Time.FormatTimestamp(e.Utc)}] {e.Msg}"); @@ -104,16 +116,19 @@ namespace TraceContract Add(call.Block.Utc, $"Reserve-slot called. Index: {call.SlotIndex} Host: '{call.FromAddress}'"); } + public string Package() + { + var outputFolder = config.GetOuputFolder(); + Directory.CreateDirectory(outputFolder); + var filename = Path.Combine(outputFolder, $"contract_{input.PurchaseId}.zip"); + + ZipFile.CreateFromDirectory(folder, filename); + return filename; + } + 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 0b7f3911..67189161 100644 --- a/TraceContract/Program.cs +++ b/TraceContract/Program.cs @@ -1,7 +1,6 @@ using BlockchainUtils; using CodexContractsPlugin; using CodexContractsPlugin.Marketplace; -using ContinuousTests; using Core; using GethPlugin; using Logging; @@ -56,7 +55,9 @@ namespace TraceContract Log("Downloading storage nodes logs for the request timerange..."); DownloadStorageNodeLogs(requestTimeRange, entryPoint.Tools); - // package everything + Log("Packaging..."); + var zipFilename = output.Package(); + Log($"Saved to '{zipFilename}'"); entryPoint.Decommission(false, false, false); Log("Done"); @@ -87,8 +88,9 @@ namespace TraceContract 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); + targetFile.Write("TODO!"); + //var downloader = new ElasticSearchLogDownloader(log, tools, config); + //downloader.Download(targetFile, node, start, requestTimeRange.To); } } From 2ca2a6149cfd003c4ffccad489e7d97ad459f428 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 15:22:08 +0200 Subject: [PATCH 21/24] Setting up github action --- .github/workflows/trace-contract.yaml | 42 +++++++++++++++++++ .../TraceContract}/ChainRequestTracker.cs | 0 .../TraceContract}/ChainTracer.cs | 0 .../TraceContract}/Config.cs | 0 .../ElasticSearchLogDownloader.cs | 0 .../TraceContract}/Input.cs | 0 .../TraceContract}/Output.cs | 0 .../TraceContract}/Program.cs | 0 .../TraceContract}/TraceContract.csproj | 4 +- cs-codex-dist-testing.sln | 12 +++--- 10 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/trace-contract.yaml rename {TraceContract => Tools/TraceContract}/ChainRequestTracker.cs (100%) rename {TraceContract => Tools/TraceContract}/ChainTracer.cs (100%) rename {TraceContract => Tools/TraceContract}/Config.cs (100%) rename {TraceContract => Tools/TraceContract}/ElasticSearchLogDownloader.cs (100%) rename {TraceContract => Tools/TraceContract}/Input.cs (100%) rename {TraceContract => Tools/TraceContract}/Output.cs (100%) rename {TraceContract => Tools/TraceContract}/Program.cs (100%) rename {TraceContract => Tools/TraceContract}/TraceContract.csproj (51%) diff --git a/.github/workflows/trace-contract.yaml b/.github/workflows/trace-contract.yaml new file mode 100644 index 00000000..b24451ef --- /dev/null +++ b/.github/workflows/trace-contract.yaml @@ -0,0 +1,42 @@ +name: Trace Contract + +on: + workflow_dispatch: + inputs: + purchaseid: + description: "Testnet Purchase ID" + required: true + type: string + +env: + SOURCE: ${{ format('{0}/{1}', github.server_url, github.repository) }} + BRANCH: ${{ github.ref_name }} + OUTPUT_FOLDER: "/tmp" + ES_USERNAME: ${{ secrets.ES_USERNAME }} + ES_PASSWORD: ${{ secrets.ES_PASSWORD }} + +jobs: + run_tests: + name: Run Release Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ inputs.workflow_source }} + + - name: Variables + run: | + echo "PURCHASE_ID=${{ inputs.purchaseid }}" >> $GITHUB_ENV + + - name: Run Trace + run: | + dotnet run --project Tools/TraceContract + + - name: Upload output + uses: actions/upload-artifact@v4 + with: + name: contract-trace + path: /tmp/* + if-no-files-found: error + retention-days: 7 diff --git a/TraceContract/ChainRequestTracker.cs b/Tools/TraceContract/ChainRequestTracker.cs similarity index 100% rename from TraceContract/ChainRequestTracker.cs rename to Tools/TraceContract/ChainRequestTracker.cs diff --git a/TraceContract/ChainTracer.cs b/Tools/TraceContract/ChainTracer.cs similarity index 100% rename from TraceContract/ChainTracer.cs rename to Tools/TraceContract/ChainTracer.cs diff --git a/TraceContract/Config.cs b/Tools/TraceContract/Config.cs similarity index 100% rename from TraceContract/Config.cs rename to Tools/TraceContract/Config.cs diff --git a/TraceContract/ElasticSearchLogDownloader.cs b/Tools/TraceContract/ElasticSearchLogDownloader.cs similarity index 100% rename from TraceContract/ElasticSearchLogDownloader.cs rename to Tools/TraceContract/ElasticSearchLogDownloader.cs diff --git a/TraceContract/Input.cs b/Tools/TraceContract/Input.cs similarity index 100% rename from TraceContract/Input.cs rename to Tools/TraceContract/Input.cs diff --git a/TraceContract/Output.cs b/Tools/TraceContract/Output.cs similarity index 100% rename from TraceContract/Output.cs rename to Tools/TraceContract/Output.cs diff --git a/TraceContract/Program.cs b/Tools/TraceContract/Program.cs similarity index 100% rename from TraceContract/Program.cs rename to Tools/TraceContract/Program.cs diff --git a/TraceContract/TraceContract.csproj b/Tools/TraceContract/TraceContract.csproj similarity index 51% rename from TraceContract/TraceContract.csproj rename to Tools/TraceContract/TraceContract.csproj index 3bcdeedb..51fd39c6 100644 --- a/TraceContract/TraceContract.csproj +++ b/Tools/TraceContract/TraceContract.csproj @@ -8,9 +8,7 @@ - - - + diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index 18dd9a2e..a10e32d6 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -86,7 +86,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexClient", "ProjectPlugi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUtils", "Framework\WebUtils\WebUtils.csproj", "{372C9E5D-5453-4D45-9948-E9324E21AD65}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceContract", "TraceContract\TraceContract.csproj", "{6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceContract", "Tools\TraceContract\TraceContract.csproj", "{58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -234,10 +234,10 @@ Global {372C9E5D-5453-4D45-9948-E9324E21AD65}.Debug|Any CPU.Build.0 = Debug|Any CPU {372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.ActiveCfg = Release|Any CPU {372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.Build.0 = Release|Any CPU - {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC}.Release|Any CPU.Build.0 = Release|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -278,7 +278,7 @@ Global {4648B5AA-A0A7-44BA-89BC-2FD57370943C} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} {9AF12703-29AF-416D-9781-204223D6D0E5} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124} {372C9E5D-5453-4D45-9948-E9324E21AD65} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} - {6F4C72D7-4B6E-45FD-93C6-2099CBE5B2AC} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C} From 9fec8b341b3c449c312e4e947be3e2630bc0894a Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 15:42:03 +0200 Subject: [PATCH 22/24] fixes elastic-search query --- Tools/TraceContract/Config.cs | 3 +-- .../ElasticSearchLogDownloader.cs | 22 +++++++++---------- Tools/TraceContract/Program.cs | 8 +++---- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Tools/TraceContract/Config.cs b/Tools/TraceContract/Config.cs index f6b7f1ad..f52a0053 100644 --- a/Tools/TraceContract/Config.cs +++ b/Tools/TraceContract/Config.cs @@ -16,8 +16,7 @@ public TimeSpan LogStartBeforeStorageContractStarts { get; } = TimeSpan.FromMinutes(1.0); public string ElasticSearchUrl { get; } = $"https://es.testnet.codex.storage"; - public string StorageNodesKubernetesNamespace = "codex"; - public string[] StorageNodesKubernetesContainerNames = [ + public string[] StorageNodesKubernetesPodNames = [ "codex-1-1", "codex-2-1", "codex-3-1", diff --git a/Tools/TraceContract/ElasticSearchLogDownloader.cs b/Tools/TraceContract/ElasticSearchLogDownloader.cs index f1a76557..a55d85ca 100644 --- a/Tools/TraceContract/ElasticSearchLogDownloader.cs +++ b/Tools/TraceContract/ElasticSearchLogDownloader.cs @@ -19,11 +19,11 @@ namespace TraceContract this.config = config; } - public void Download(LogFile targetFile, string containerName, DateTime startUtc, DateTime endUtc) + public void Download(LogFile targetFile, string podName, DateTime startUtc, DateTime endUtc) { try { - DownloadLog(targetFile, containerName, startUtc, endUtc); + DownloadLog(targetFile, podName, startUtc, endUtc); } catch (Exception ex) { @@ -31,22 +31,22 @@ namespace TraceContract } } - private void DownloadLog(LogFile targetFile, string containerName, DateTime startUtc, DateTime endUtc) + private void DownloadLog(LogFile targetFile, string podName, DateTime startUtc, DateTime endUtc) { - log.Log($"Downloading log (from ElasticSearch) for container '{containerName}' within time range: " + + log.Log($"Downloading log (from ElasticSearch) for pod '{podName}' within time range: " + $"{startUtc.ToString("o")} - {endUtc.ToString("o")}"); var endpoint = CreateElasticSearchEndpoint(); - var queryTemplate = CreateQueryTemplate(containerName, startUtc, endUtc); + var queryTemplate = CreateQueryTemplate(podName, startUtc, endUtc); - targetFile.Write($"Downloading '{containerName}' to '{targetFile.Filename}'."); + targetFile.Write($"Downloading '{podName}' 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) + private string CreateQueryTemplate(string podName, DateTime startUtc, DateTime endUtc) { var start = startUtc.ToString("o"); var end = endUtc.ToString("o"); @@ -55,12 +55,12 @@ namespace TraceContract // 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\": \"\" } } ] } } }"; + 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\": { \"pod_name\": \"\" } } ] } } }"; return source .Replace("", start) .Replace("", end) - .Replace("", containerName) - .Replace("", config.StorageNodesKubernetesNamespace); + .Replace("", podName); + //.Replace("", config.StorageNodesKubernetesNamespace); } private IEndpoint CreateElasticSearchEndpoint() @@ -121,7 +121,7 @@ namespace TraceContract .Replace("", sizeOfPage.ToString()) .Replace("", searchAfter); - var response = endpoint.HttpPostString("_search", query); + var response = endpoint.HttpPostString("/_search", query); lastHits = response.hits.hits.Length; if (lastHits > 0) diff --git a/Tools/TraceContract/Program.cs b/Tools/TraceContract/Program.cs index 67189161..d239373c 100644 --- a/Tools/TraceContract/Program.cs +++ b/Tools/TraceContract/Program.cs @@ -65,7 +65,6 @@ namespace TraceContract 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); @@ -83,14 +82,13 @@ namespace TraceContract { var start = requestTimeRange.From - config.LogStartBeforeStorageContractStarts; - foreach (var node in config.StorageNodesKubernetesContainerNames) + foreach (var node in config.StorageNodesKubernetesPodNames) { Log($"Downloading logs from '{node}'..."); var targetFile = output.CreateNodeLogTargetFile(node); - targetFile.Write("TODO!"); - //var downloader = new ElasticSearchLogDownloader(log, tools, config); - //downloader.Download(targetFile, node, start, requestTimeRange.To); + var downloader = new ElasticSearchLogDownloader(log, tools, config); + downloader.Download(targetFile, node, start, requestTimeRange.To); } } From 0add69c42e871d5da15c1acc0e415677bbfa36e9 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 15:56:06 +0200 Subject: [PATCH 23/24] pulls ES host from env vars --- Tools/TraceContract/Config.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Tools/TraceContract/Config.cs b/Tools/TraceContract/Config.cs index f52a0053..5cdce6e7 100644 --- a/Tools/TraceContract/Config.cs +++ b/Tools/TraceContract/Config.cs @@ -15,7 +15,14 @@ /// public TimeSpan LogStartBeforeStorageContractStarts { get; } = TimeSpan.FromMinutes(1.0); - public string ElasticSearchUrl { get; } = $"https://es.testnet.codex.storage"; + public string ElasticSearchUrl + { + get + { + return GetEnvVar("ES_HOST", "es_host"); + } + } + public string[] StorageNodesKubernetesPodNames = [ "codex-1-1", "codex-2-1", From 6e610f847008cd897917374ed0a975bc24a72af6 Mon Sep 17 00:00:00 2001 From: ThatBen Date: Tue, 20 May 2025 16:01:28 +0200 Subject: [PATCH 24/24] sets codex image to 0.2.2 --- ProjectPlugins/CodexPlugin/CodexDockerImage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs index 55301cf8..787727e5 100644 --- a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -2,7 +2,7 @@ { public class CodexDockerImage { - private const string DefaultDockerImage = "codexstorage/nim-codex:0.2.1-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:0.2.2-dist-tests"; public static string Override { get; set; } = string.Empty;