Finishes implementation of marketplace support

This commit is contained in:
benbierens 2023-04-18 15:33:12 +02:00
parent e36d910f2f
commit 12d122ad83
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
10 changed files with 185 additions and 51 deletions

View File

@ -26,6 +26,16 @@ namespace DistTestCore.Codex
return Http().HttpGetStream("download/" + contentId);
}
public CodexSalesAvailabilityResponse SalesAvailability(CodexSalesAvailabilityRequest request)
{
return Http().HttpPostJson<CodexSalesAvailabilityRequest, CodexSalesAvailabilityResponse>("sales/availability", request);
}
public CodexSalesRequestStorageResponse RequestStorage(CodexSalesRequestStorageRequest request, string contentId)
{
return Http().HttpPostJson<CodexSalesRequestStorageRequest, CodexSalesRequestStorageResponse>($"storage/request/{contentId}", request);
}
private Http Http()
{
var ip = Container.Pod.Cluster.IP;
@ -53,4 +63,37 @@ namespace DistTestCore.Codex
public string version { get; set; } = string.Empty;
public string revision { get; set; } = string.Empty;
}
public class CodexSalesAvailabilityRequest
{
public string size { get; set; } = string.Empty;
public string duration { get; set; } = string.Empty;
public string minPrice { get; set; } = string.Empty;
public string maxCollateral { get; set; } = string.Empty;
}
public class CodexSalesAvailabilityResponse
{
public string id { get; set; } = string.Empty;
public string size { get; set; } = string.Empty;
public string duration { get; set; } = string.Empty;
public string minPrice { get; set; } = string.Empty;
public string maxCollateral { get; set; } = string.Empty;
}
public class CodexSalesRequestStorageRequest
{
public string duration { get; set; } = string.Empty;
public string proofProbability { get; set; } = string.Empty;
public string reward { get; set; } = string.Empty;
public string collateral { get; set; } = string.Empty;
public string? expiry { get; set; }
public uint? nodes { get; set; }
public uint? tolerance { get; set;}
}
public class CodexSalesRequestStorageResponse
{
public string purchaseId { get; set; } = string.Empty;
}
}

View File

@ -32,7 +32,7 @@ namespace DistTestCore
private void TransferInitialBalance(MarketplaceNetwork marketplaceNetwork, MarketplaceInitialConfig marketplaceConfig, GethCompanionNodeInfo[] companionNodes)
{
var interaction = marketplaceNetwork.StartInteraction(lifecycle.Log);
var tokenAddress = interaction.GetTokenAddress(marketplaceNetwork.Marketplace.Address);
var tokenAddress = marketplaceNetwork.Marketplace.TokenAddress;
foreach (var node in companionNodes)
{
@ -79,7 +79,7 @@ namespace DistTestCore
if (network == null)
{
var bootstrapInfo = bootstrapNodeStarter.StartGethBootstrapNode();
var marketplaceInfo = codexContractsStarter.Start(bootstrapInfo.RunningContainers.Containers[0]);
var marketplaceInfo = codexContractsStarter.Start(bootstrapInfo);
network = new MarketplaceNetwork(bootstrapInfo, marketplaceInfo );
}
return network;

View File

@ -1,6 +1,7 @@
using Newtonsoft.Json;
using NUnit.Framework;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Utils;
namespace DistTestCore
@ -37,6 +38,19 @@ namespace DistTestCore
return JsonConvert.DeserializeObject<T>(HttpGetString(route))!;
}
public TResponse HttpPostJson<TRequest, TResponse>(string route, TRequest body)
{
return Retry(() =>
{
using var client = GetClient();
var url = GetUrl() + route;
using var content = JsonContent.Create(body);
var result = Time.Wait(client.PostAsync(url, content));
var json = Time.Wait(result.Content.ReadAsStringAsync());
return JsonConvert.DeserializeObject<TResponse>(json)!;
});
}
public string HttpPostStream(string route, Stream stream)
{
return Retry(() =>

View File

@ -12,12 +12,12 @@ namespace DistTestCore.Marketplace
{
}
public MarketplaceInfo Start(RunningContainer bootstrapContainer)
public MarketplaceInfo Start(GethBootstrapNodeInfo bootstrapNode)
{
LogStart("Deploying Codex contracts...");
var workflow = workflowCreator.CreateWorkflow();
var startupConfig = CreateStartupConfig(bootstrapContainer);
var startupConfig = CreateStartupConfig(bootstrapNode.RunningContainers.Containers[0]);
var containers = workflow.Start(1, Location.Unspecified, new CodexContractsContainerRecipe(), startupConfig);
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Codex contracts container to be created. Test infra failure.");
@ -33,9 +33,12 @@ namespace DistTestCore.Marketplace
var extractor = new ContainerInfoExtractor(workflow, container);
var marketplaceAddress = extractor.ExtractMarketplaceAddress();
var interaction = bootstrapNode.StartInteraction(lifecycle.Log);
var tokenAddress = interaction.GetTokenAddress(marketplaceAddress);
LogEnd("Contracts deployed.");
return new MarketplaceInfo(marketplaceAddress);
return new MarketplaceInfo(marketplaceAddress, tokenAddress);
}
private void WaitUntil(Func<bool> predicate)
@ -54,12 +57,14 @@ namespace DistTestCore.Marketplace
public class MarketplaceInfo
{
public MarketplaceInfo(string address)
public MarketplaceInfo(string address, string tokenAddress)
{
Address = address;
TokenAddress = tokenAddress;
}
public string Address { get; }
public string TokenAddress { get; }
}
public class ContractsReadyLogHandler : LogHandler

View File

@ -1,4 +1,6 @@
using KubernetesWorkflow;
using Logging;
using NethereumWorkflow;
namespace DistTestCore.Marketplace
{
@ -20,5 +22,16 @@ namespace DistTestCore.Marketplace
public string PubKey { get; }
public string PrivateKey { get; }
public Port DiscoveryPort { get; }
public NethereumInteraction StartInteraction(TestLog log)
{
var ip = RunningContainers.RunningPod.Cluster.IP;
var port = RunningContainers.Containers[0].ServicePorts[0].Number;
var account = Account;
var privateKey = PrivateKey;
var creator = new NethereumInteractionCreator(log, ip, port, account, privateKey);
return creator.CreateWorkflow();
}
}
}

View File

@ -1,13 +1,15 @@
using Logging;
using DistTestCore.Codex;
using Logging;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using System.Numerics;
namespace DistTestCore.Marketplace
{
public interface IMarketplaceAccess
{
void MakeStorageAvailable(ByteSize size, int minPricePerBytePerSecond, float maxCollateral);
void RequestStorage(ContentId contentId, int pricePerBytePerSecond, float requiredCollateral, float minRequiredNumberOfNodes);
string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan maxDuration);
string RequestStorage(ContentId contentId, TestToken pricePerBytePerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration);
void AssertThatBalance(IResolveConstraint constraint, string message = "");
decimal GetBalance();
}
@ -17,22 +19,58 @@ namespace DistTestCore.Marketplace
private readonly TestLog log;
private readonly MarketplaceNetwork marketplaceNetwork;
private readonly GethCompanionNodeInfo companionNode;
private readonly CodexAccess codexAccess;
public MarketplaceAccess(TestLog log, MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode)
public MarketplaceAccess(TestLog log, MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode, CodexAccess codexAccess)
{
this.log = log;
this.marketplaceNetwork = marketplaceNetwork;
this.companionNode = companionNode;
this.codexAccess = codexAccess;
}
public void RequestStorage(ContentId contentId, int pricePerBytePerSecond, float requiredCollateral, float minRequiredNumberOfNodes)
public string RequestStorage(ContentId contentId, TestToken pricePerBytePerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
{
throw new NotImplementedException();
var request = new CodexSalesRequestStorageRequest
{
duration = ToHexBigInt(duration.TotalSeconds),
proofProbability = ToHexBigInt(proofProbability),
reward = ToHexBigInt(pricePerBytePerSecond),
collateral = ToHexBigInt(requiredCollateral),
expiry = null,
nodes = minRequiredNumberOfNodes,
tolerance = null,
};
var response = codexAccess.RequestStorage(request, contentId.Id);
return response.purchaseId;
}
public void MakeStorageAvailable(ByteSize size, int minPricePerBytePerSecond, float maxCollateral)
public string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan duration)
{
throw new NotImplementedException();
var request = new CodexSalesAvailabilityRequest
{
size = ToHexBigInt(size.SizeInBytes),
duration = ToHexBigInt(duration.TotalSeconds),
maxCollateral = ToHexBigInt(maxCollateral),
minPrice = ToHexBigInt(minPricePerBytePerSecond)
};
var response = codexAccess.SalesAvailability(request);
return response.id;
}
private string ToHexBigInt(double d)
{
return "0x" + string.Format("{0:X}", Convert.ToInt64(d));
}
public string ToHexBigInt(TestToken t)
{
var bigInt = new BigInteger(t.Amount);
return "0x" + bigInt.ToString("X");
}
public void AssertThatBalance(IResolveConstraint constraint, string message = "")
@ -43,20 +81,22 @@ namespace DistTestCore.Marketplace
public decimal GetBalance()
{
var interaction = marketplaceNetwork.StartInteraction(log);
return interaction.GetBalance(companionNode.Account);
return interaction.GetBalance(marketplaceNetwork.Marketplace.TokenAddress, companionNode.Account);
}
}
public class MarketplaceUnavailable : IMarketplaceAccess
{
public void RequestStorage(ContentId contentId, int pricePerBytePerSecond, float requiredCollateral, float minRequiredNumberOfNodes)
public string RequestStorage(ContentId contentId, TestToken pricePerBytePerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
{
Unavailable();
return string.Empty;
}
public void MakeStorageAvailable(ByteSize size, int minPricePerBytePerSecond, float maxCollateral)
public string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan duration)
{
Unavailable();
return string.Empty;
}
public void AssertThatBalance(IResolveConstraint constraint, string message = "")

View File

@ -30,7 +30,7 @@ namespace DistTestCore.Marketplace
public IMarketplaceAccess CreateMarketplaceAccess(CodexAccess access)
{
var companionNode = GetGethCompanionNode(access);
return new MarketplaceAccess(log, marketplaceNetwork, companionNode);
return new MarketplaceAccess(log, marketplaceNetwork, companionNode, access);
}
private GethCompanionNodeInfo GetGethCompanionNode(CodexAccess access)

View File

@ -16,13 +16,7 @@ namespace DistTestCore.Marketplace
public NethereumInteraction StartInteraction(TestLog log)
{
var ip = Bootstrap.RunningContainers.RunningPod.Cluster.IP;
var port = Bootstrap.RunningContainers.Containers[0].ServicePorts[0].Number;
var account = Bootstrap.Account;
var privateKey = Bootstrap.PrivateKey;
var creator = new NethereumInteractionCreator(log, ip, port, account, privateKey);
return creator.CreateWorkflow();
return Bootstrap.StartInteraction(log);
}
}
}

View File

@ -56,10 +56,16 @@ namespace NethereumWorkflow
Time.Wait(handler.SendRequestAndWaitForReceiptAsync(tokenAddress, function));
}
public decimal GetBalance(string account)
public decimal GetBalance(string tokenAddress, string account)
{
var bigInt = Time.Wait(web3.Eth.GetBalance.SendRequestAsync(account));
var result = ToDecimal(bigInt);
var function = new GetTokenBalanceFunction
{
Owner = account
};
var handler = web3.Eth.GetContractQueryHandler<GetTokenBalanceFunction>();
var result = ToDecimal(Time.Wait(handler.QueryAsync<BigInteger>(tokenAddress, function)));
Log($"Balance of {account} is {result}");
return result;
}
@ -81,6 +87,11 @@ namespace NethereumWorkflow
return (decimal)hexBigInteger.Value;
}
private decimal ToDecimal(BigInteger bigInteger)
{
return (decimal)bigInteger;
}
private void Log(string msg)
{
log.Log(msg);
@ -101,4 +112,11 @@ namespace NethereumWorkflow
[Parameter("uint256", "amount", 2)]
public BigInteger Amount { get; set; }
}
[Function("balanceOf", "uint256")]
public class GetTokenBalanceFunction :FunctionMessage
{
[Parameter("address", "owner", 1)]
public string Owner { get; set; }
}
}

View File

@ -1,6 +1,7 @@
using DistTestCore;
using DistTestCore.Codex;
using NUnit.Framework;
using Utils;
namespace Tests.BasicTests
{
@ -49,38 +50,44 @@ namespace Tests.BasicTests
[Test]
public void MarketplaceExample()
{
var group = SetupCodexNodes(2)
var primary = SetupCodexNodes(1)
.WithStorageQuota(10.GB())
.EnableMarketplace(20.TestTokens())
.BringOnline();
.EnableMarketplace(initialBalance: 234.TestTokens())
.BringOnline()[0];
foreach (var node in group)
{
Assert.That(node.Marketplace.GetBalance(), Is.EqualTo(20));
}
Assert.That(primary.Marketplace.GetBalance(), Is.EqualTo(234));
// WIP: Balance is now only ETH.
// todo: All nodes should have plenty of ETH to pay for transactions.
// todo: Upload our own token, use this exclusively. ETH should be invisibile to the tests.
var secondary = SetupCodexNodes(1)
.EnableMarketplace(initialBalance: 1000.TestTokens())
.BringOnline()[0];
primary.ConnectToPeer(secondary);
//var secondary = SetupCodexNodes(1)
// .EnableMarketplace(initialBalance: 1000)
// .BringOnline()[0];
// Gives 503 - Persistance not enabled in current codex image.
primary.Marketplace.MakeStorageAvailable(
size: 10.GB(),
minPricePerBytePerSecond: 1.TestTokens(),
maxCollateral: 20.TestTokens(),
maxDuration: TimeSpan.FromMinutes(3));
//primary.ConnectToPeer(secondary);
//primary.Marketplace.MakeStorageAvailable(10.GB(), minPricePerBytePerSecond: 1, maxCollateral: 20);
var testFile = GenerateTestFile(10.MB());
var contentId = secondary.UploadFile(testFile);
//var testFile = GenerateTestFile(10.MB());
//var contentId = secondary.UploadFile(testFile);
//secondary.Marketplace.RequestStorage(contentId, pricePerBytePerSecond: 2,
// requiredCollateral: 10, minRequiredNumberOfNodes: 1);
// Gives 500 - Persistance not enabled in current codex image.
secondary.Marketplace.RequestStorage(contentId,
pricePerBytePerSecond: 2.TestTokens(),
requiredCollateral: 10.TestTokens(),
minRequiredNumberOfNodes: 1,
proofProbability: 5,
duration: TimeSpan.FromMinutes(2));
//primary.Marketplace.AssertThatBalance(Is.LessThan(20), "Collateral was not placed.");
//var primaryBalance = primary.Marketplace.GetBalance();
Time.Sleep(TimeSpan.FromMinutes(3));
//secondary.Marketplace.AssertThatBalance(Is.LessThan(1000), "Contractor was not charged for storage.");
//primary.Marketplace.AssertThatBalance(Is.GreaterThan(primaryBalance), "Storer was not paid for storage.");
primary.Marketplace.AssertThatBalance(Is.LessThan(20), "Collateral was not placed.");
var primaryBalance = primary.Marketplace.GetBalance();
secondary.Marketplace.AssertThatBalance(Is.LessThan(1000), "Contractor was not charged for storage.");
primary.Marketplace.AssertThatBalance(Is.GreaterThan(primaryBalance), "Storer was not paid for storage.");
}
}
}