Wires up marketplace access

This commit is contained in:
benbierens 2023-09-20 08:45:55 +02:00
parent 6cbf363bb1
commit cedaf84740
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
9 changed files with 256 additions and 24 deletions

View File

@ -111,12 +111,6 @@ namespace CodexPlugin
return 8.GB().Multiply(1.2);
}
//private int GetAccountIndex(MarketplaceInitialConfig marketplaceConfig)
//{
// if (marketplaceConfig.AccountIndexOverride != null) return marketplaceConfig.AccountIndexOverride.Value;
// return Index;
//}
private string GetDockerImage()
{
var image = Environment.GetEnvironmentVariable("CODEXDOCKERIMAGE");

View File

@ -18,6 +18,7 @@ namespace CodexPlugin
TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "");
void ConnectToPeer(ICodexNode node);
CodexDebugVersionResponse Version { get; }
IMarketplaceAccess Marketplace { get; }
void Stop();
}
@ -28,18 +29,20 @@ namespace CodexPlugin
private readonly IPluginTools tools;
private readonly IEthAddress? ethAddress;
public CodexNode(IPluginTools tools, CodexAccess codexAccess, CodexNodeGroup group, IEthAddress? ethAddress)
public CodexNode(IPluginTools tools, CodexAccess codexAccess, CodexNodeGroup group, IMarketplaceAccess marketplaceAccess, IEthAddress? ethAddress)
{
this.tools = tools;
this.ethAddress = ethAddress;
CodexAccess = codexAccess;
Group = group;
Marketplace = marketplaceAccess;
Version = new CodexDebugVersionResponse();
}
public RunningContainer Container { get { return CodexAccess.Container; } }
public CodexAccess CodexAccess { get; }
public CodexNodeGroup Group { get; }
public IMarketplaceAccess Marketplace { get; }
public CodexDebugVersionResponse Version { get; private set; }
public IMetricsScrapeTarget MetricsScrapeTarget
{

View File

@ -20,7 +20,14 @@ namespace CodexPlugin
public CodexNode CreateOnlineCodexNode(CodexAccess access, CodexNodeGroup group)
{
var ethAddress = GetEthAddress(access);
return new CodexNode(tools, access, group, ethAddress);
var marketplaceAccess = GetMarketplaceAccess(access, ethAddress);
return new CodexNode(tools, access, group, marketplaceAccess, ethAddress);
}
private IMarketplaceAccess GetMarketplaceAccess(CodexAccess codexAccess, IEthAddress? ethAddress)
{
if (ethAddress == null) return new MarketplaceUnavailable();
return new MarketplaceAccess(tools.GetLog(), codexAccess);
}
private IEthAddress? GetEthAddress(CodexAccess access)

View File

@ -33,9 +33,7 @@ namespace CodexPlugin
public RunningContainers[] StartCodexNodes(int numberOfNodes, Action<ICodexSetup> setup)
{
var codexSetup = new CodexSetup(numberOfNodes);
codexSetup.LogLevel = defaultLogLevel;
setup(codexSetup);
var codexSetup = GetSetup(numberOfNodes, setup);
return codexStarter.BringOnline(codexSetup);
}
@ -43,5 +41,26 @@ namespace CodexPlugin
{
return codexStarter.WrapCodexContainers(coreInterface, containers);
}
public void WireUpMarketplace(ICodexNodeGroup result, Action<ICodexSetup> setup)
{
var codexSetup = GetSetup(1, setup);
if (codexSetup.MarketplaceConfig == null) return;
var mconfig = codexSetup.MarketplaceConfig;
foreach (var node in result)
{
mconfig.GethNode.SendEth(node, mconfig.InitialEth);
mconfig.CodexContracts.MintTestTokens(mconfig.GethNode, node, mconfig.InitialTokens);
}
}
private CodexSetup GetSetup(int numberOfNodes, Action<ICodexSetup> setup)
{
var codexSetup = new CodexSetup(numberOfNodes);
codexSetup.LogLevel = defaultLogLevel;
setup(codexSetup);
return codexSetup;
}
}
}

View File

@ -16,7 +16,7 @@ namespace CodexPlugin
ICodexSetup WithBlockMaintenanceInterval(TimeSpan duration);
ICodexSetup WithBlockMaintenanceNumber(int numberOfBlocks);
ICodexSetup EnableMetrics();
ICodexSetup EnableMarketplace(IGethNode gethNode, ICodexContracts codexContracts, bool isValidator = false);
ICodexSetup EnableMarketplace(IGethNode gethNode, ICodexContracts codexContracts, Ether initialEth, TestToken initialTokens, bool isValidator = false);
}
public class CodexSetup : CodexStartupConfig, ICodexSetup
@ -82,9 +82,9 @@ namespace CodexPlugin
return this;
}
public ICodexSetup EnableMarketplace(IGethNode gethNode, ICodexContracts codexContracts, bool isValidator = false)
public ICodexSetup EnableMarketplace(IGethNode gethNode, ICodexContracts codexContracts, Ether initialEth, TestToken initialTokens, bool isValidator = false)
{
MarketplaceConfig = new MarketplaceInitialConfig(gethNode, codexContracts, isValidator);
MarketplaceConfig = new MarketplaceInitialConfig(gethNode, codexContracts, initialEth, initialTokens, isValidator);
return this;
}

View File

@ -28,7 +28,9 @@ namespace CodexPlugin
public static ICodexNodeGroup SetupCodexNodes(this CoreInterface ci, int number, Action<ICodexSetup> setup)
{
var rc = ci.StartCodexNodes(number, setup);
return ci.WrapCodexContainers(rc);
var result = ci.WrapCodexContainers(rc);
Plugin(ci).WireUpMarketplace(result, setup);
return result;
}
public static ICodexNodeGroup SetupCodexNodes(this CoreInterface ci, int number)

View File

@ -0,0 +1,208 @@
using CodexContractsPlugin;
using Logging;
using Newtonsoft.Json;
using NUnit.Framework;
using Utils;
using System.Numerics;
namespace CodexPlugin
{
public interface IMarketplaceAccess
{
string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan maxDuration);
StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerSlotPerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration);
}
public class MarketplaceAccess : IMarketplaceAccess
{
private readonly ILog log;
private readonly CodexAccess codexAccess;
public MarketplaceAccess(ILog log, CodexAccess codexAccess)
{
this.log = log;
this.codexAccess = codexAccess;
}
public StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerSlotPerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
{
var request = new CodexSalesRequestStorageRequest
{
duration = ToDecInt(duration.TotalSeconds),
proofProbability = ToDecInt(proofProbability),
reward = ToDecInt(pricePerSlotPerSecond),
collateral = ToDecInt(requiredCollateral),
expiry = null,
nodes = minRequiredNumberOfNodes,
tolerance = null,
};
Log($"Requesting storage for: {contentId.Id}... (" +
$"pricePerSlotPerSecond: {pricePerSlotPerSecond}, " +
$"requiredCollateral: {requiredCollateral}, " +
$"minRequiredNumberOfNodes: {minRequiredNumberOfNodes}, " +
$"proofProbability: {proofProbability}, " +
$"duration: {Time.FormatDuration(duration)})");
var response = codexAccess.RequestStorage(request, contentId.Id);
if (response == "Purchasing not available")
{
throw new InvalidOperationException(response);
}
Log($"Storage requested successfully. PurchaseId: '{response}'.");
return new StoragePurchaseContract(log, codexAccess, response, duration);
}
public string MakeStorageAvailable(ByteSize totalSpace, TestToken minPriceForTotalSpace, TestToken maxCollateral, TimeSpan maxDuration)
{
var request = new CodexSalesAvailabilityRequest
{
size = ToDecInt(totalSpace.SizeInBytes),
duration = ToDecInt(maxDuration.TotalSeconds),
maxCollateral = ToDecInt(maxCollateral),
minPrice = ToDecInt(minPriceForTotalSpace)
};
Log($"Making storage available... (" +
$"size: {totalSpace}, " +
$"minPriceForTotalSpace: {minPriceForTotalSpace}, " +
$"maxCollateral: {maxCollateral}, " +
$"maxDuration: {Time.FormatDuration(maxDuration)})");
var response = codexAccess.SalesAvailability(request);
Log($"Storage successfully made available. Id: {response.id}");
return response.id;
}
private string ToDecInt(double d)
{
var i = new BigInteger(d);
return i.ToString("D");
}
public string ToDecInt(TestToken t)
{
var i = new BigInteger(t.Amount);
return i.ToString("D");
}
private void Log(string msg)
{
log.Log($"{codexAccess.Container.Name} {msg}");
}
}
public class MarketplaceUnavailable : IMarketplaceAccess
{
public StoragePurchaseContract RequestStorage(ContentId contentId, TestToken pricePerBytePerSecond, TestToken requiredCollateral, uint minRequiredNumberOfNodes, int proofProbability, TimeSpan duration)
{
Unavailable();
return null!;
}
public string MakeStorageAvailable(ByteSize size, TestToken minPricePerBytePerSecond, TestToken maxCollateral, TimeSpan duration)
{
Unavailable();
return string.Empty;
}
private void Unavailable()
{
Assert.Fail("Incorrect test setup: Marketplace was not enabled for this group of Codex nodes. Add 'EnableMarketplace(...)' after 'SetupCodexNodes()' to enable it.");
throw new InvalidOperationException();
}
}
public class StoragePurchaseContract
{
private readonly ILog log;
private readonly CodexAccess codexAccess;
private DateTime? contractStartUtc;
public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, TimeSpan contractDuration)
{
this.log = log;
this.codexAccess = codexAccess;
PurchaseId = purchaseId;
ContractDuration = contractDuration;
}
public string PurchaseId { get; }
public TimeSpan ContractDuration { get; }
public void WaitForStorageContractStarted()
{
WaitForStorageContractStarted(TimeSpan.FromSeconds(30));
}
public void WaitForStorageContractFinished()
{
if (!contractStartUtc.HasValue)
{
WaitForStorageContractStarted();
}
var gracePeriod = TimeSpan.FromSeconds(10);
var currentContractTime = DateTime.UtcNow - contractStartUtc!.Value;
var timeout = (ContractDuration - currentContractTime) + gracePeriod;
WaitForStorageContractState(timeout, "finished");
}
/// <summary>
/// Wait for contract to start. Max timeout depends on contract filesize. Allows more time for larger files.
/// </summary>
public void WaitForStorageContractStarted(ByteSize contractFileSize)
{
var filesizeInMb = contractFileSize.SizeInBytes / (1024 * 1024);
var maxWaitTime = TimeSpan.FromSeconds(filesizeInMb * 10.0);
WaitForStorageContractStarted(maxWaitTime);
}
public void WaitForStorageContractStarted(TimeSpan timeout)
{
WaitForStorageContractState(timeout, "started");
contractStartUtc = DateTime.UtcNow;
}
private void WaitForStorageContractState(TimeSpan timeout, string desiredState)
{
var lastState = "";
var waitStart = DateTime.UtcNow;
log.Log($"Waiting for {Time.FormatDuration(timeout)} for contract '{PurchaseId}' to reach state '{desiredState}'.");
while (lastState != desiredState)
{
var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId);
var statusJson = JsonConvert.SerializeObject(purchaseStatus);
if (purchaseStatus != null && purchaseStatus.state != lastState)
{
lastState = purchaseStatus.state;
log.Debug("Purchase status: " + statusJson);
}
Thread.Sleep(1000);
if (lastState == "errored")
{
Assert.Fail("Contract errored: " + statusJson);
}
if (DateTime.UtcNow - waitStart > timeout)
{
Assert.Fail($"Contract did not reach '{desiredState}' within timeout. {statusJson}");
}
}
log.Log($"Contract '{desiredState}'.");
}
public CodexStoragePurchase GetPurchaseStatus(string purchaseId)
{
return codexAccess.GetPurchaseStatus(purchaseId);
}
}
}

View File

@ -5,15 +5,19 @@ namespace CodexPlugin
{
public class MarketplaceInitialConfig
{
public MarketplaceInitialConfig(IGethNode gethNode, ICodexContracts codexContracts, bool isValidator)
public MarketplaceInitialConfig(IGethNode gethNode, ICodexContracts codexContracts, Ether initialEth, TestToken initialTokens, bool isValidator)
{
GethNode = gethNode;
CodexContracts = codexContracts;
InitialEth = initialEth;
InitialTokens = initialTokens;
IsValidator = isValidator;
}
public IGethNode GethNode { get; }
public ICodexContracts CodexContracts { get; }
public Ether InitialEth { get; }
public TestToken InitialTokens { get; }
public bool IsValidator { get; }
}
}

View File

@ -1,5 +1,4 @@
using CodexContractsPlugin;
using CodexPlugin;
using DistTestCore;
using GethPlugin;
using MetricsPlugin;
@ -57,9 +56,7 @@ namespace Tests.BasicTests
var seller = AddCodex(s => s
.WithStorageQuota(11.GB())
.EnableMarketplace(geth, contracts));
geth.SendEth(seller, 10.Eth());
contracts.MintTestTokens(geth, seller, sellerInitialBalance);
.EnableMarketplace(geth, contracts, initialEth: 10.Eth(), initialTokens: sellerInitialBalance));
AssertBalance(geth, contracts, seller, Is.EqualTo(sellerInitialBalance));
seller.Marketplace.MakeStorageAvailable(
@ -72,10 +69,8 @@ namespace Tests.BasicTests
var buyer = AddCodex(s => s
.WithBootstrapNode(seller)
.EnableMarketplace(geth, contracts));
geth.SendEth(seller, 10.Eth());
contracts.MintTestTokens(geth, seller, buyerInitialBalance);
.EnableMarketplace(geth, contracts, initialEth: 10.Eth(), initialTokens: buyerInitialBalance));
AssertBalance(geth, contracts, buyer, Is.EqualTo(buyerInitialBalance));
var contentId = buyer.UploadFile(testFile);