2025-08-21 10:51:05 +02:00
|
|
|
|
using CodexClient;
|
|
|
|
|
|
using CodexContractsPlugin.ChainMonitor;
|
|
|
|
|
|
using CodexContractsPlugin.Marketplace;
|
|
|
|
|
|
using CodexReleaseTests.Utils;
|
|
|
|
|
|
using Nethereum.Hex.HexConvertors.Extensions;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using NUnit.Framework;
|
|
|
|
|
|
using Utils;
|
|
|
|
|
|
|
|
|
|
|
|
namespace CodexReleaseTests.MarketTests
|
|
|
|
|
|
{
|
|
|
|
|
|
[TestFixture]
|
2025-08-22 18:55:01 +02:00
|
|
|
|
public class StabilityTest : MarketplaceAutoBootstrapDistTest
|
2025-08-21 10:51:05 +02:00
|
|
|
|
{
|
|
|
|
|
|
#region Setup
|
|
|
|
|
|
|
|
|
|
|
|
private readonly PurchaseParams purchaseParams = new PurchaseParams(
|
|
|
|
|
|
nodes: 4,
|
|
|
|
|
|
tolerance: 2,
|
|
|
|
|
|
uploadFilesize: 32.MB()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
public StabilityTest()
|
|
|
|
|
|
{
|
|
|
|
|
|
Assert.That(purchaseParams.Nodes, Is.LessThan(NumberOfHosts));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override int NumberOfHosts => 6;
|
|
|
|
|
|
protected override int NumberOfClients => 1;
|
|
|
|
|
|
protected override ByteSize HostAvailabilitySize => purchaseParams.SlotSize.Multiply(1.1); // Each host can hold 1 slot.
|
|
|
|
|
|
protected override TimeSpan HostAvailabilityMaxDuration => TimeSpan.FromDays(5.0);
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2025-08-22 18:55:01 +02:00
|
|
|
|
private int numPeriods = 0;
|
|
|
|
|
|
private bool proofWasMissed = false;
|
|
|
|
|
|
|
2025-08-21 10:51:05 +02:00
|
|
|
|
[Test]
|
|
|
|
|
|
[Combinatorial]
|
|
|
|
|
|
public void Stability(
|
|
|
|
|
|
[Values(10, 120)] int minutes)
|
|
|
|
|
|
{
|
2025-08-22 18:55:01 +02:00
|
|
|
|
var mins = TimeSpan.FromMinutes(minutes);
|
|
|
|
|
|
var periodDuration = GetContracts().Deployment.Config.PeriodDuration;
|
|
|
|
|
|
Assert.That(HostAvailabilityMaxDuration, Is.GreaterThan(mins * 1.1));
|
2025-08-21 10:51:05 +02:00
|
|
|
|
|
2025-08-22 18:55:01 +02:00
|
|
|
|
numPeriods = 0;
|
|
|
|
|
|
proofWasMissed = false;
|
2025-08-21 10:51:05 +02:00
|
|
|
|
|
|
|
|
|
|
StartHosts();
|
|
|
|
|
|
StartValidator();
|
|
|
|
|
|
var client = StartClients().Single();
|
2025-08-22 18:55:01 +02:00
|
|
|
|
var purchase = CreateStorageRequest(client, mins);
|
2025-08-21 11:43:59 +02:00
|
|
|
|
purchase.WaitForStorageContractStarted();
|
2025-08-21 10:51:05 +02:00
|
|
|
|
|
2025-08-22 18:55:01 +02:00
|
|
|
|
Log($"Contract should remain stable for {Time.FormatDuration(mins)}.");
|
|
|
|
|
|
var endUtc = DateTime.UtcNow + mins;
|
|
|
|
|
|
while (DateTime.UtcNow < endUtc)
|
|
|
|
|
|
{
|
|
|
|
|
|
Thread.Sleep(TimeSpan.FromSeconds(10));
|
|
|
|
|
|
if (proofWasMissed)
|
|
|
|
|
|
{
|
|
|
|
|
|
// We wait because we want to log calls to MarkProofAsMissing.
|
|
|
|
|
|
Thread.Sleep(periodDuration * 1.1);
|
|
|
|
|
|
Assert.Fail("Proof was missed.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var minNumPeriod = (mins / periodDuration) - 1.0;
|
|
|
|
|
|
Log($"{numPeriods} periods elapsed. Expected at least {minNumPeriod} periods.");
|
|
|
|
|
|
Assert.That(numPeriods, Is.GreaterThanOrEqualTo(minNumPeriod));
|
2025-08-21 10:51:05 +02:00
|
|
|
|
|
2025-08-22 18:55:01 +02:00
|
|
|
|
var status = client.GetPurchaseStatus(purchase.PurchaseId);
|
|
|
|
|
|
if (status == null) throw new Exception("Purchase status not found");
|
|
|
|
|
|
Assert.That(status.IsStarted || status.IsFinished);
|
2025-08-21 10:51:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-22 18:55:01 +02:00
|
|
|
|
protected override void OnPeriod(PeriodReport report)
|
2025-08-21 10:51:05 +02:00
|
|
|
|
{
|
2025-08-22 18:55:01 +02:00
|
|
|
|
numPeriods++;
|
|
|
|
|
|
|
2025-08-21 10:51:05 +02:00
|
|
|
|
// For each required proof, there should be a submit call.
|
2025-08-22 18:55:01 +02:00
|
|
|
|
var calls = GetSubmitProofCalls(report);
|
2025-08-21 10:51:05 +02:00
|
|
|
|
foreach (var required in report.Required)
|
|
|
|
|
|
{
|
2025-08-22 18:55:01 +02:00
|
|
|
|
var matchingCall = GetMatchingSubmitProofCall(calls, required);
|
|
|
|
|
|
if (matchingCall == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log($"A proof was missed for {required.Describe()}. Failing test after a delay so chain events have time to log...");
|
|
|
|
|
|
proofWasMissed = true;
|
|
|
|
|
|
}
|
2025-08-21 10:51:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// There can't be any calls to mark a proof as missed.
|
|
|
|
|
|
foreach (var call in report.FunctionCalls)
|
|
|
|
|
|
{
|
|
|
|
|
|
var missedCall = nameof(MarkProofAsMissingFunction);
|
|
|
|
|
|
Assert.That(call.Name, Is.Not.EqualTo(missedCall));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-22 18:55:01 +02:00
|
|
|
|
private SubmitProofFunction? GetMatchingSubmitProofCall(SubmitProofFunction[] calls, PeriodRequiredProof required)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var call in calls)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (
|
|
|
|
|
|
call.Id.SequenceEqual(required.SlotId) &&
|
|
|
|
|
|
call.FromAddress.ToLowerInvariant() == required.Host.Address.ToLowerInvariant()
|
|
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
return call;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private SubmitProofFunction[] GetSubmitProofCalls(PeriodReport report)
|
2025-08-21 10:51:05 +02:00
|
|
|
|
{
|
|
|
|
|
|
var submitCall = nameof(SubmitProofFunction);
|
2025-08-22 18:55:01 +02:00
|
|
|
|
var calls = report.FunctionCalls.Where(f => f.Name == submitCall).ToArray();
|
|
|
|
|
|
var callObjs = calls.Select(call => JsonConvert.DeserializeObject<SubmitProofFunction>(call.Payload)).ToArray();
|
|
|
|
|
|
Log($"SubmitProof calls: {callObjs.Length}");
|
|
|
|
|
|
foreach (var c in callObjs)
|
|
|
|
|
|
{
|
|
|
|
|
|
Log($" - slotId:{c.Id.ToHex()} host:{c.FromAddress}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return callObjs!;
|
2025-08-21 10:51:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-22 18:55:01 +02:00
|
|
|
|
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client, TimeSpan minutes)
|
2025-08-21 10:51:05 +02:00
|
|
|
|
{
|
|
|
|
|
|
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
|
|
|
|
|
|
var config = GetContracts().Deployment.Config;
|
|
|
|
|
|
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
|
|
|
|
|
|
{
|
2025-08-22 18:55:01 +02:00
|
|
|
|
Duration = minutes * 1.1,
|
2025-08-21 11:43:59 +02:00
|
|
|
|
Expiry = TimeSpan.FromMinutes(8.0),
|
2025-08-21 10:51:05 +02:00
|
|
|
|
MinRequiredNumberOfNodes = (uint)purchaseParams.Nodes,
|
|
|
|
|
|
NodeFailureTolerance = (uint)purchaseParams.Tolerance,
|
|
|
|
|
|
PricePerBytePerSecond = 10.TstWei(),
|
|
|
|
|
|
ProofProbability = 1, // One proof every period. Free slot as quickly as possible.
|
|
|
|
|
|
CollateralPerByte = 1.TstWei()
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|