diff --git a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs index e94032a..ddd6675 100644 --- a/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs +++ b/ProjectPlugins/CodexContractsPlugin/TestTokenExtensions.cs @@ -46,6 +46,16 @@ namespace CodexContractsPlugin return new TestToken(a.TstWei + b.TstWei); } + public static TestToken operator -(TestToken a, TestToken b) + { + return new TestToken(a.TstWei - b.TstWei); + } + + public static TestToken operator *(TestToken a, int b) + { + return new TestToken(a.TstWei * b); + } + public static bool operator <(TestToken a, TestToken b) { return a.TstWei < b.TstWei; diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs index 3151984..bddfbcd 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs @@ -1,11 +1,7 @@ using CodexContractsPlugin; using CodexPlugin; +using GethPlugin; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Utils; namespace CodexReleaseTests.MarketTests @@ -15,14 +11,135 @@ namespace CodexReleaseTests.MarketTests { private const int NumberOfHosts = 4; private const int FilesizeMb = 10; + private const int PricePerSlotPerSecondTSTWei = 10; [Test] public void ContractSuccessful() { var hosts = StartHosts(); - var client = StartCodex(s => s.WithName("client")); + var contract = CreateStorageRequest(client); + + contract.WaitForStorageContractSubmitted(); + AssertContractIsOnChain(contract); + + contract.WaitForStorageContractStarted(); + var slotFills = AssertContractSlotsAreFilledByHosts(contract, hosts); + + contract.WaitForStorageContractFinished(); + AssertClientHasPaidForContract(client, contract); + AssertHostsWerePaidForContract(contract, hosts, slotFills); + AssertHostsCollateralsAreUnchanged(hosts); + } + + private void AssertContractIsOnChain(IStoragePurchaseContract contract) + { + AssertOnChainEvents(events => + { + var onChainRequests = events.GetStorageRequests(); + if (onChainRequests.Any(r => r.Id == contract.PurchaseId)) return; + throw new Exception($"OnChain request {contract.PurchaseId} not found..."); + }, nameof(AssertContractIsOnChain)); + } + + private SlotFill[] AssertContractSlotsAreFilledByHosts(IStoragePurchaseContract contract, ICodexNodeGroup hosts) + { + var activeHosts = new Dictionary(); + + Time.Retry(() => + { + var fills = GetOnChainSlotFills(hosts, contract.PurchaseId); + foreach (var fill in fills) + { + var index = (int)fill.SlotFilledEvent.SlotIndex; + if (!activeHosts.ContainsKey(index)) + { + activeHosts.Add(index, fill); + } + } + + if (activeHosts.Count != contract.Purchase.MinRequiredNumberOfNodes) throw new Exception("Not all slots were filled..."); + + }, nameof(AssertContractSlotsAreFilledByHosts)); + + return activeHosts.Values.ToArray(); + } + + private void AssertClientHasPaidForContract(ICodexNode client, IStoragePurchaseContract contract) + { + var balance = GetContracts().GetTestTokenBalance(client); + var expectedBalance = StartingBalanceTST.Tst() - GetContractTotalCost(); + + Assert.That(balance, Is.EqualTo(expectedBalance)); + } + + private TestToken GetContractTotalCost() + { + return GetContractCostPerSlot() * NumberOfHosts; + } + + private TestToken GetContractCostPerSlot() + { + var duration = GetContractDuration(); + return PricePerSlotPerSecondTSTWei.TstWei() * ((int)duration.TotalSeconds); + } + + private void AssertHostsWerePaidForContract(IStoragePurchaseContract contract, ICodexNodeGroup hosts, SlotFill[] fills) + { + var expectedBalances = new Dictionary(); + foreach (var host in hosts) expectedBalances.Add(host.EthAddress, StartingBalanceTST.Tst()); + foreach (var fill in fills) + { + expectedBalances[fill.Host.EthAddress] += GetContractCostPerSlot(); + } + + foreach (var pair in expectedBalances) + { + var balance = GetContracts().GetTestTokenBalance(pair.Key); + Assert.That(balance, Is.EqualTo(pair.Value)); + } + } + + private void AssertHostsCollateralsAreUnchanged(ICodexNodeGroup hosts) + { + // There is no separate collateral location yet. + // All host balances should be equal to or greater than the starting balance. + foreach (var host in hosts) + { + Assert.That(GetContracts().GetTestTokenBalance(host), Is.GreaterThanOrEqualTo(StartingBalanceTST.Tst())); + } + } + + private void AssertOnChainEvents(Action onEvents, string description) + { + Time.Retry(() => + { + var events = GetContracts().GetEvents(GetTestRunTimeRange()); + onEvents(events); + }, description); + } + + private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) + { + var cid = client.UploadFile(GenerateTestFile(FilesizeMb.MB())); + var config = GetContracts().Deployment.Config; + return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) + { + Duration = GetContractDuration(), + Expiry = TimeSpan.FromSeconds(((double)config.Proofs.Period) * 1.0), + MinRequiredNumberOfNodes = NumberOfHosts, + NodeFailureTolerance = NumberOfHosts / 2, + PricePerSlotPerSecond = PricePerSlotPerSecondTSTWei.TstWei(), + ProofProbability = 20, + RequiredCollateral = 1.Tst() + }); + } + + private TimeSpan GetContractDuration() + { + var config = GetContracts().Deployment.Config; + return TimeSpan.FromSeconds(((double)config.Proofs.Period) * 2.0); } private ICodexNodeGroup StartHosts() diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs index 045be2f..b7d9a51 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs @@ -1,8 +1,10 @@ using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; using CodexPlugin; using CodexTests; using DistTestCore; using GethPlugin; +using Nethereum.Hex.HexConvertors.Extensions; namespace CodexReleaseTests.MarketTests { @@ -41,6 +43,37 @@ namespace CodexReleaseTests.MarketTests return handles[Get()].Contracts; } + public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts, string purchaseId) + { + return GetOnChainSlotFills(possibleHosts) + .Where(f => f.SlotFilledEvent.RequestId.ToHex(true) == purchaseId) + .ToArray(); + } + + public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts) + { + var events = GetContracts().GetEvents(GetTestRunTimeRange()); + var fills = events.GetSlotFilledEvents(); + return fills.Select(f => + { + var host = possibleHosts.Single(h => h.EthAddress == f.Host); + return new SlotFill(f, host); + + }).ToArray(); + } + + public class SlotFill + { + public SlotFill(SlotFilledEventDTO slotFilledEvent, ICodexNode host) + { + SlotFilledEvent = slotFilledEvent; + Host = host; + } + + public SlotFilledEventDTO SlotFilledEvent { get; } + public ICodexNode Host { get; } + } + private class MarketplaceHandle { public MarketplaceHandle(IGethNode geth, ICodexContracts contracts)