diff --git a/Framework/Core/Http.cs b/Framework/Core/Http.cs index 61d13f8..0bd3117 100644 --- a/Framework/Core/Http.cs +++ b/Framework/Core/Http.cs @@ -51,7 +51,6 @@ namespace Core private string GetDescription() { - // todo: check this: return DebugStack.GetCallerName(skipFrames: 2); } diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index ae9984e..498aeb1 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -9,7 +9,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "DC-90-1B-63-76-1B-92-01-05-65-33-DA-17-C2-34-83-E1-2E-6C-A9-04-4D-68-ED-96-43-F5-E5-6A-00-0F-5F"; + private const string OpenApiYamlHash = "63-7F-46-5E-2C-60-7A-BD-0C-EC-32-87-61-1B-79-FA-C2-EF-73-81-BA-FA-28-77-33-02-81-30-80-5D-00-97"; private const string OpenApiFilePath = "/codex/openapi.yaml"; private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs index 7e7c111..4e2cc8f 100644 --- a/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs +++ b/ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs @@ -7,7 +7,7 @@ namespace CodexPlugin { public class CodexContainerRecipe : ContainerRecipeFactory { - private const string DefaultDockerImage = "codexstorage/nim-codex:sha-cd280d4-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:sha-f2f1dd5-dist-tests"; public const string ApiPortTag = "codex_api_port"; public const string ListenPortTag = "codex_listen_port"; public const string MetricsPortTag = "codex_metrics_port"; diff --git a/ProjectPlugins/CodexPlugin/Mapper.cs b/ProjectPlugins/CodexPlugin/Mapper.cs index 014fbc7..9db2a84 100644 --- a/ProjectPlugins/CodexPlugin/Mapper.cs +++ b/ProjectPlugins/CodexPlugin/Mapper.cs @@ -57,8 +57,8 @@ namespace CodexPlugin Reward = ToDecInt(purchase.PricePerSlotPerSecond), Collateral = ToDecInt(purchase.RequiredCollateral), Expiry = ToDecInt(DateTimeOffset.UtcNow.ToUnixTimeSeconds() + purchase.Expiry.TotalSeconds), - Nodes = purchase.MinRequiredNumberOfNodes, - Tolerance = purchase.NodeFailureTolerance + Nodes = Convert.ToInt32(purchase.MinRequiredNumberOfNodes), + Tolerance = Convert.ToInt32(purchase.NodeFailureTolerance) }; } diff --git a/ProjectPlugins/CodexPlugin/openapi.yaml b/ProjectPlugins/CodexPlugin/openapi.yaml index 3a1ad70..6099ed5 100644 --- a/ProjectPlugins/CodexPlugin/openapi.yaml +++ b/ProjectPlugins/CodexPlugin/openapi.yaml @@ -69,7 +69,7 @@ components: type: object properties: totalChunks: - type: number + type: integer PoRParameters: description: Parameters for Proof of Retrievability @@ -186,12 +186,12 @@ components: proofProbability: $ref: "#/components/schemas/ProofProbability" nodes: - type: number description: Minimal number of nodes the content should be stored on + type: integer default: 1 tolerance: - type: number description: Additional number of nodes on top of the `nodes` property that can be lost before pronouncing the content lost + type: integer default: 0 collateral: type: string @@ -206,8 +206,8 @@ components: - reward properties: slots: - type: number description: Number of slots (eq. hosts) that the Request want to have the content spread over + type: integer slotSize: type: string description: Amount of storage per slot (in bytes) as decimal string @@ -218,7 +218,7 @@ components: reward: $ref: "#/components/schemas/Reward" maxSlotLoss: - type: number + type: integer description: Max slots that can be lost without data considered to be lost StorageRequest: @@ -274,10 +274,10 @@ components: $ref: "#/components/schemas/Cid" description: "Root hash of the content" originalBytes: - type: number + type: integer description: "Length of original content in bytes" blockSize: - type: number + type: integer description: "Size of blocks" protected: type: boolean @@ -287,16 +287,16 @@ components: type: object properties: totalBlocks: - type: number description: "Number of blocks stored by the node" + type: integer quotaMaxBytes: - type: number + type: integer description: "Maximum storage space used by the node" quotaUsedBytes: - type: number + type: integer description: "Amount of storage space currently in use" quotaReservedBytes: - type: number + type: integer description: "Amount of storage space reserved" servers: diff --git a/ProjectPlugins/CodexPluginPrebuild/Program.cs b/ProjectPlugins/CodexPluginPrebuild/Program.cs index 8f4a8ec..d6d2a5a 100644 --- a/ProjectPlugins/CodexPluginPrebuild/Program.cs +++ b/ProjectPlugins/CodexPluginPrebuild/Program.cs @@ -4,6 +4,7 @@ using System.Text; public static class Program { private const string OpenApiFile = "../CodexPlugin/openapi.yaml"; + private const string ClientFile = "../CodexPlugin/obj/openapiClient.cs"; private const string Search = ""; private const string TargetFile = "ApiChecker.cs"; @@ -11,6 +12,9 @@ public static class Program { Console.WriteLine("Injecting hash of 'openapi.yaml'..."); + // Force client rebuild by deleting previous artifact. + File.Delete(ClientFile); + var hash = CreateHash(); // This hash is used to verify that the Codex docker image being used is compatible // with the openapi.yaml being used by the Codex plugin. diff --git a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs index 9b7e608..80203af 100644 --- a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs +++ b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs @@ -34,11 +34,11 @@ namespace ContinuousTests $"{startUtc.ToString("o")} - {endUtc.ToString("o")}"); log.Log(openingLine); - var http = CreateElasticSearchHttp(); + var endpoint = CreateElasticSearchEndpoint(); var queryTemplate = CreateQueryTemplate(container, startUtc, endUtc); targetFile.Write($"Downloading '{container.Name}' to '{targetFile.FullFilename}'."); - var reconstructor = new LogReconstructor(targetFile, http, queryTemplate); + var reconstructor = new LogReconstructor(targetFile, endpoint, queryTemplate); reconstructor.DownloadFullLog(); log.Log("Log download finished."); @@ -64,34 +64,36 @@ namespace ContinuousTests .Replace("", namespaceName); } - private IHttp CreateElasticSearchHttp() + private IEndpoint CreateElasticSearchEndpoint() { var serviceName = "elasticsearch"; var k8sNamespace = "monitoring"; var address = new Address($"http://{serviceName}.{k8sNamespace}.svc.cluster.local", 9200); var baseUrl = ""; - return tools.CreateHttp(address, baseUrl, client => + var http = tools.CreateHttp(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 IHttp http; + 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, IHttp http, string queryTemplate) + public LogReconstructor(LogFile targetFile, IEndpoint endpoint, string queryTemplate) { this.targetFile = targetFile; - this.http = http; + this.endpoint = endpoint; this.queryTemplate = queryTemplate; } @@ -110,7 +112,7 @@ namespace ContinuousTests .Replace("", sizeOfPage.ToString()) .Replace("", searchAfter); - var response = http.HttpPostString("_search", query); + var response = endpoint.HttpPostString("_search", query); lastHits = response.hits.hits.Length; if (lastHits > 0) diff --git a/Tests/CodexContinuousTests/NodeRunner.cs b/Tests/CodexContinuousTests/NodeRunner.cs index 488e47a..31f1f2e 100644 --- a/Tests/CodexContinuousTests/NodeRunner.cs +++ b/Tests/CodexContinuousTests/NodeRunner.cs @@ -44,7 +44,7 @@ namespace ContinuousTests try { var debugInfo = bootstrapNode.GetDebugInfo(); - Assert.That(!string.IsNullOrEmpty(debugInfo.spr)); + Assert.That(!string.IsNullOrEmpty(debugInfo.Spr)); var node = entryPoint.CreateInterface().StartCodexNode(s => { diff --git a/Tests/CodexContinuousTests/SingleTestRun.cs b/Tests/CodexContinuousTests/SingleTestRun.cs index 4552b46..7c6c5a6 100644 --- a/Tests/CodexContinuousTests/SingleTestRun.cs +++ b/Tests/CodexContinuousTests/SingleTestRun.cs @@ -128,7 +128,7 @@ namespace ContinuousTests var deploymentName = container.RunningContainers.StartResult.Deployment.Name; var namespaceName = container.RunningContainers.StartResult.Cluster.Configuration.KubernetesNamespace; var openingLine = - $"{namespaceName} - {deploymentName} = {node.Container.Name} = {node.GetDebugInfo().id}"; + $"{namespaceName} - {deploymentName} = {node.Container.Name} = {node.GetDebugInfo().Id}"; elasticSearchLogDownloader.Download(fixtureLog.CreateSubfile(), node.Container, effectiveStart, effectiveEnd, openingLine); } diff --git a/Tests/CodexContinuousTests/StartupChecker.cs b/Tests/CodexContinuousTests/StartupChecker.cs index 1527589..d3fb456 100644 --- a/Tests/CodexContinuousTests/StartupChecker.cs +++ b/Tests/CodexContinuousTests/StartupChecker.cs @@ -123,10 +123,10 @@ namespace ContinuousTests try { var info = n.GetDebugInfo(); - if (info == null || string.IsNullOrEmpty(info.id)) return false; + if (info == null || string.IsNullOrEmpty(info.Id)) return false; - log.Log($"Codex version: '{info.codex.version}' revision: '{info.codex.revision}'"); - LogReplacements.Add(new BaseLogStringReplacement(info.id, n.GetName())); + log.Log($"Codex version: '{info.Version.Version}' revision: '{info.Version.Revision}'"); + LogReplacements.Add(new BaseLogStringReplacement(info.Id, n.GetName())); } catch { diff --git a/Tests/CodexContinuousTests/Tests/PeersTest.cs b/Tests/CodexContinuousTests/Tests/PeersTest.cs index 38d2f4f..87bcb82 100644 --- a/Tests/CodexContinuousTests/Tests/PeersTest.cs +++ b/Tests/CodexContinuousTests/Tests/PeersTest.cs @@ -24,12 +24,12 @@ namespace CodexContinuousTests.Tests var allInfos = Nodes.Select(n => { var info = n.GetDebugInfo(); - Log.Log($"{n.GetName()} = {info.table.localNode.nodeId}"); - Log.AddStringReplace(info.table.localNode.nodeId, n.GetName()); + Log.Log($"{n.GetName()} = {info.Table.LocalNode.NodeId}"); + Log.AddStringReplace(info.Table.LocalNode.NodeId, n.GetName()); return info; }).ToArray(); - var allIds = allInfos.Select(i => i.table.localNode.nodeId).ToArray(); + var allIds = allInfos.Select(i => i.Table.LocalNode.NodeId).ToArray(); var errors = Nodes.Select(n => AreAllPresent(n, allIds)).Where(s => !string.IsNullOrEmpty(s)).ToArray(); if (errors.Any()) @@ -41,13 +41,13 @@ namespace CodexContinuousTests.Tests private string AreAllPresent(ICodexNode n, string[] allIds) { var info = n.GetDebugInfo(); - var known = info.table.nodes.Select(n => n.nodeId).ToArray(); - var expected = allIds.Where(i => i != info.table.localNode.nodeId).ToArray(); + var known = info.Table.Nodes.Select(n => n.NodeId).ToArray(); + var expected = allIds.Where(i => i != info.Table.LocalNode.NodeId).ToArray(); if (!expected.All(ex => known.Contains(ex))) { var nl = Environment.NewLine; - return $"{nl}At node '{info.table.localNode.nodeId}'{nl}" + + return $"{nl}At node '{info.Table.LocalNode.NodeId}'{nl}" + $"Not all of{nl}'{string.Join(",", expected)}'{nl}" + $"were present in routing table:{nl}'{string.Join(",", known)}'"; } diff --git a/Tests/CodexTests/BasicTests/ExampleTests.cs b/Tests/CodexTests/BasicTests/ExampleTests.cs index 67548ee..e7453b5 100644 --- a/Tests/CodexTests/BasicTests/ExampleTests.cs +++ b/Tests/CodexTests/BasicTests/ExampleTests.cs @@ -1,11 +1,9 @@ -using CodexContractsPlugin; -using CodexDiscordBotPlugin; -using CodexPlugin; +using CodexPlugin; +using DistTestCore; using GethPlugin; -using Nethereum.Hex.HexConvertors.Extensions; +using MetricsPlugin; using NUnit.Framework; using Utils; -using Request = CodexContractsPlugin.Marketplace.Request; namespace CodexTests.BasicTests { @@ -13,118 +11,40 @@ namespace CodexTests.BasicTests public class ExampleTests : CodexDistTest { [Test] - public void BotRewardTest() + public void CodexLogExample() { - var myAccount = EthAccount.GenerateNew(); + var primary = AddCodex(s => s.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Warn, CodexLogLevel.Warn))); - var sellerInitialBalance = 234.TestTokens(); - var buyerInitialBalance = 100000.TestTokens(); - var fileSize = 10.MB(); + var cid = primary.UploadFile(GenerateTestFile(5.MB())); - var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); - var contracts = Ci.StartCodexContracts(geth); + var localDatasets = primary.LocalFiles(); + CollectionAssert.Contains(localDatasets.Content.Select(c => c.Cid), cid); - // start bot and rewarder - var gethInfo = new DiscordBotGethInfo( - host: geth.Container.GetInternalAddress(GethContainerRecipe.HttpPortTag).Host, - port: geth.Container.GetInternalAddress(GethContainerRecipe.HttpPortTag).Port, - privKey: geth.StartResult.Account.PrivateKey, - marketplaceAddress: contracts.Deployment.MarketplaceAddress, - tokenAddress: contracts.Deployment.TokenAddress, - abi: contracts.Deployment.Abi - ); - var bot = Ci.DeployCodexDiscordBot(new DiscordBotStartupConfig( - name: "bot", - token: "aaa", - serverName: "ThatBen's server", - adminRoleName: "bottest-admins", - adminChannelName: "admin-channel", - rewardChannelName: "rewards-channel", - kubeNamespace: "notneeded", - gethInfo: gethInfo - )); - var botContainer = bot.Containers.Single(); - Ci.DeployRewarderBot(new RewarderBotStartupConfig( - //discordBotHost: "http://" + botContainer.GetAddress(GetTestLog(), DiscordBotContainerRecipe.RewardsPort).Host, - //discordBotPort: botContainer.GetAddress(GetTestLog(), DiscordBotContainerRecipe.RewardsPort).Port, - discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host, - discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port, - interval: "60", - historyStartUtc: DateTime.UtcNow.AddHours(-1), - gethInfo: gethInfo, - dataPath: null - )); + var log = Ci.DownloadLog(primary); - var numberOfHosts = 3; + log.AssertLogContains("Uploaded file"); + } - for (var i = 0; i < numberOfHosts; i++) - { - var seller = AddCodex(s => s - .WithName("Seller") - .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) - { - ContractClock = CodexLogLevel.Trace, - }) - .WithStorageQuota(11.GB()) - .EnableMarketplace(geth, contracts, m => m - .WithAccount(myAccount) - .WithInitial(10.Eth(), sellerInitialBalance) - .AsStorageNode() - .AsValidator())); + [Test] + public void TwoMetricsExample() + { + var group = AddCodex(2, s => s.EnableMetrics()); + var group2 = AddCodex(2, s => s.EnableMetrics()); - AssertBalance(contracts, seller, Is.EqualTo(sellerInitialBalance)); + var primary = group[0]; + var secondary = group[1]; + var primary2 = group2[0]; + var secondary2 = group2[1]; - var availability = new StorageAvailability( - totalSpace: 10.GB(), - maxDuration: TimeSpan.FromMinutes(30), - minPriceForTotalSpace: 1.TestTokens(), - maxCollateral: 20.TestTokens() - ); - seller.Marketplace.MakeStorageAvailable(availability); - } + var metrics = Ci.GetMetricsFor(primary, primary2); - var testFile = GenerateTestFile(fileSize); + primary.ConnectToPeer(secondary); + primary2.ConnectToPeer(secondary2); - var buyer = AddCodex(s => s - .WithName("Buyer") - .EnableMarketplace(geth, contracts, m => m - .WithInitial(10.Eth(), buyerInitialBalance))); + Thread.Sleep(TimeSpan.FromMinutes(2)); - AssertBalance(contracts, buyer, Is.EqualTo(buyerInitialBalance)); - - var contentId = buyer.UploadFile(testFile); - - var purchase = new StoragePurchaseRequest(contentId) - { - PricePerSlotPerSecond = 2.TestTokens(), - RequiredCollateral = 10.TestTokens(), - MinRequiredNumberOfNodes = 5, - NodeFailureTolerance = 2, - ProofProbability = 5, - Duration = TimeSpan.FromMinutes(5), - Expiry = TimeSpan.FromMinutes(4) - }; - - var purchaseContract = buyer.Marketplace.RequestStorage(purchase); - - purchaseContract.WaitForStorageContractStarted(); - - //AssertBalance(contracts, seller, Is.LessThan(sellerInitialBalance), "Collateral was not placed."); - - //var blockRange = geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange()); - - //var request = GetOnChainStorageRequest(contracts, blockRange); - //AssertStorageRequest(request, purchase, contracts, buyer); - //AssertSlotFilledEvents(contracts, purchase, request, seller, blockRange); - //AssertContractSlot(contracts, request, 0, seller); - - purchaseContract.WaitForStorageContractFinished(); - - var hold = 0; - - //AssertBalance(contracts, seller, Is.GreaterThan(sellerInitialBalance), "Seller was not paid for storage."); - //AssertBalance(contracts, buyer, Is.LessThan(buyerInitialBalance), "Buyer was not charged for storage."); - //Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); + metrics[0].AssertThat("libp2p_peers", Is.EqualTo(1)); + metrics[1].AssertThat("libp2p_peers", Is.EqualTo(1)); } [Test] @@ -143,43 +63,5 @@ namespace CodexTests.BasicTests Assert.That(bootN, Is.EqualTo(followN)); Assert.That(discN, Is.LessThan(bootN)); } - - private void AssertSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, Request request, ICodexNode seller, BlockInterval blockRange) - { - // Expect 1 fulfilled event for the purchase. - var requestFulfilledEvents = contracts.GetRequestFulfilledEvents(blockRange); - Assert.That(requestFulfilledEvents.Length, Is.EqualTo(1)); - CollectionAssert.AreEqual(request.RequestId, requestFulfilledEvents[0].RequestId); - - // Expect 1 filled-slot event for each slot in the purchase. - var filledSlotEvents = contracts.GetSlotFilledEvents(blockRange); - Assert.That(filledSlotEvents.Length, Is.EqualTo(purchase.MinRequiredNumberOfNodes)); - for (var i = 0; i < purchase.MinRequiredNumberOfNodes; i++) - { - var filledSlotEvent = filledSlotEvents.Single(e => e.SlotIndex == i); - Assert.That(filledSlotEvent.RequestId.ToHex(), Is.EqualTo(request.RequestId.ToHex())); - Assert.That(filledSlotEvent.Host, Is.EqualTo(seller.EthAddress)); - } - } - - private void AssertStorageRequest(Request request, StoragePurchaseRequest purchase, ICodexContracts contracts, ICodexNode buyer) - { - Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Started)); - Assert.That(request.ClientAddress, Is.EqualTo(buyer.EthAddress)); - Assert.That(request.Ask.Slots, Is.EqualTo(purchase.MinRequiredNumberOfNodes)); - } - - private Request GetOnChainStorageRequest(ICodexContracts contracts, BlockInterval blockRange) - { - var requests = contracts.GetStorageRequests(blockRange); - Assert.That(requests.Length, Is.EqualTo(1)); - return requests.Single(); - } - - private void AssertContractSlot(ICodexContracts contracts, Request request, int contractSlotIndex, ICodexNode expectedSeller) - { - var slotHost = contracts.GetSlotHost(request, contractSlotIndex); - Assert.That(slotHost, Is.EqualTo(expectedSeller.EthAddress)); - } } } diff --git a/Tests/CodexTests/BasicTests/MarketplaceTests.cs b/Tests/CodexTests/BasicTests/MarketplaceTests.cs new file mode 100644 index 0000000..fbd40d8 --- /dev/null +++ b/Tests/CodexTests/BasicTests/MarketplaceTests.cs @@ -0,0 +1,180 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; +using CodexDiscordBotPlugin; +using CodexPlugin; +using GethPlugin; +using Nethereum.Hex.HexConvertors.Extensions; +using NUnit.Framework; +using Utils; + +namespace CodexTests.BasicTests +{ + [TestFixture] + public class MarketplaceTests : AutoBootstrapDistTest + { + [Test] + public void BotRewardTest() + { + var myAccount = EthAccount.GenerateNew(); + + var sellerInitialBalance = 234.TestTokens(); + var buyerInitialBalance = 100000.TestTokens(); + var fileSize = 10.MB(); + + var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth")); + var contracts = Ci.StartCodexContracts(geth); + + // start bot and rewarder + var gethInfo = new DiscordBotGethInfo( + host: geth.Container.GetInternalAddress(GethContainerRecipe.HttpPortTag).Host, + port: geth.Container.GetInternalAddress(GethContainerRecipe.HttpPortTag).Port, + privKey: geth.StartResult.Account.PrivateKey, + marketplaceAddress: contracts.Deployment.MarketplaceAddress, + tokenAddress: contracts.Deployment.TokenAddress, + abi: contracts.Deployment.Abi + ); + var bot = Ci.DeployCodexDiscordBot(new DiscordBotStartupConfig( + name: "bot", + token: "aaa", + serverName: "ThatBen's server", + adminRoleName: "bottest-admins", + adminChannelName: "admin-channel", + rewardChannelName: "rewards-channel", + kubeNamespace: "notneeded", + gethInfo: gethInfo + )); + var botContainer = bot.Containers.Single(); + Ci.DeployRewarderBot(new RewarderBotStartupConfig( + //discordBotHost: "http://" + botContainer.GetAddress(GetTestLog(), DiscordBotContainerRecipe.RewardsPort).Host, + //discordBotPort: botContainer.GetAddress(GetTestLog(), DiscordBotContainerRecipe.RewardsPort).Port, + discordBotHost: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Host, + discordBotPort: botContainer.GetInternalAddress(DiscordBotContainerRecipe.RewardsPort).Port, + interval: "60", + historyStartUtc: DateTime.UtcNow.AddHours(-1), + gethInfo: gethInfo, + dataPath: null + )); + + var numberOfHosts = 3; + + for (var i = 0; i < numberOfHosts; i++) + { + var seller = AddCodex(s => s + .WithName("Seller") + .WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn) + { + ContractClock = CodexLogLevel.Trace, + }) + .WithStorageQuota(11.GB()) + .EnableMarketplace(geth, contracts, m => m + .WithAccount(myAccount) + .WithInitial(10.Eth(), sellerInitialBalance) + .AsStorageNode() + .AsValidator())); + + AssertBalance(contracts, seller, Is.EqualTo(sellerInitialBalance)); + + var availability = new StorageAvailability( + totalSpace: 10.GB(), + maxDuration: TimeSpan.FromMinutes(30), + minPriceForTotalSpace: 1.TestTokens(), + maxCollateral: 20.TestTokens() + ); + seller.Marketplace.MakeStorageAvailable(availability); + } + + var testFile = GenerateTestFile(fileSize); + + var buyer = AddCodex(s => s + .WithName("Buyer") + .EnableMarketplace(geth, contracts, m => m + .WithInitial(10.Eth(), buyerInitialBalance))); + + AssertBalance(contracts, buyer, Is.EqualTo(buyerInitialBalance)); + + var contentId = buyer.UploadFile(testFile); + + var purchase = new StoragePurchaseRequest(contentId) + { + PricePerSlotPerSecond = 2.TestTokens(), + RequiredCollateral = 10.TestTokens(), + MinRequiredNumberOfNodes = 5, + NodeFailureTolerance = 2, + ProofProbability = 5, + Duration = TimeSpan.FromMinutes(5), + Expiry = TimeSpan.FromMinutes(4) + }; + + var purchaseContract = buyer.Marketplace.RequestStorage(purchase); + + purchaseContract.WaitForStorageContractStarted(); + + //AssertBalance(contracts, seller, Is.LessThan(sellerInitialBalance), "Collateral was not placed."); + + //var blockRange = geth.ConvertTimeRangeToBlockRange(GetTestRunTimeRange()); + + //var request = GetOnChainStorageRequest(contracts, blockRange); + //AssertStorageRequest(request, purchase, contracts, buyer); + //AssertSlotFilledEvents(contracts, purchase, request, seller, blockRange); + //AssertContractSlot(contracts, request, 0, seller); + + purchaseContract.WaitForStorageContractFinished(); + + var hold = 0; + + //AssertBalance(contracts, seller, Is.GreaterThan(sellerInitialBalance), "Seller was not paid for storage."); + //AssertBalance(contracts, buyer, Is.LessThan(buyerInitialBalance), "Buyer was not charged for storage."); + //Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished)); + } + + private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase) + { + Time.Retry(() => + { + var slotFilledEvents = contracts.GetSlotFilledEvents(GetTestRunTimeRange()); + + Log($"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}"); + + if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception(); + }, Convert.ToInt32(purchase.Duration.TotalSeconds / 5) + 10, TimeSpan.FromSeconds(5), "Checking SlotFilled events"); + } + + private void AssertSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, Request request, ICodexNode seller) + { + // Expect 1 fulfilled event for the purchase. + var requestFulfilledEvents = contracts.GetRequestFulfilledEvents(GetTestRunTimeRange()); + Assert.That(requestFulfilledEvents.Length, Is.EqualTo(1)); + CollectionAssert.AreEqual(request.RequestId, requestFulfilledEvents[0].RequestId); + + // Expect 1 filled-slot event for each slot in the purchase. + var filledSlotEvents = contracts.GetSlotFilledEvents(GetTestRunTimeRange()); + Assert.That(filledSlotEvents.Length, Is.EqualTo(purchase.MinRequiredNumberOfNodes)); + for (var i = 0; i < purchase.MinRequiredNumberOfNodes; i++) + { + var filledSlotEvent = filledSlotEvents.Single(e => e.SlotIndex == i); + Assert.That(filledSlotEvent.RequestId.ToHex(), Is.EqualTo(request.RequestId.ToHex())); + Assert.That(filledSlotEvent.Host, Is.EqualTo(seller.EthAddress)); + } + } + + private void AssertStorageRequest(Request request, StoragePurchaseRequest purchase, ICodexContracts contracts, ICodexNode buyer) + { + Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Started)); + Assert.That(request.ClientAddress, Is.EqualTo(buyer.EthAddress)); + Assert.That(request.Ask.Slots, Is.EqualTo(purchase.MinRequiredNumberOfNodes)); + } + + private Request GetOnChainStorageRequest(ICodexContracts contracts) + { + var requests = contracts.GetStorageRequests(GetTestRunTimeRange()); + Assert.That(requests.Length, Is.EqualTo(1)); + return requests.Single(); + } + + private void AssertContractSlot(ICodexContracts contracts, Request request, int contractSlotIndex, ICodexNode expectedSeller) + { + var slotHost = contracts.GetSlotHost(request, contractSlotIndex); + Assert.That(slotHost, Is.EqualTo(expectedSeller.EthAddress)); + } + } +} diff --git a/Tools/BiblioTech/BiblioTech.csproj b/Tools/BiblioTech/BiblioTech.csproj index 2f796a8..85fa95a 100644 --- a/Tools/BiblioTech/BiblioTech.csproj +++ b/Tools/BiblioTech/BiblioTech.csproj @@ -12,7 +12,6 @@ -