refactor: remove marketplace and make it work

This commit is contained in:
Adam Uhlíř 2026-02-09 16:58:16 +01:00
parent d7148ee147
commit 4932aea949
No known key found for this signature in database
GPG Key ID: 0CBD7AA7B5A72FED
91 changed files with 77 additions and 7949 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ bin
.vscode
Tools/AutoClient/datapath
.editorconfig
.DS_Store

View File

@ -9,6 +9,8 @@ namespace CodexClient
{
public class CodexAccess
{
public const string API_BASE_URL = "/api/storage/v1";
private readonly ILog log;
private readonly IHttpFactory httpFactory;
private readonly IProcessControl processControl;
@ -142,37 +144,12 @@ namespace CodexClient
return mapper.Map(OnCodex(api => api.ListDataAsync()));
}
public StorageAvailability SalesAvailability(CreateStorageAvailability request)
{
var body = mapper.Map(request);
var read = OnCodex(api => api.OfferStorageAsync(body));
return mapper.Map(read);
}
public StorageAvailability[] GetAvailabilities()
{
var collection = OnCodex(api => api.GetAvailabilitiesAsync());
return mapper.Map(collection);
}
public string RequestStorage(StoragePurchaseRequest request)
{
var body = mapper.Map(request);
return OnCodex(api => api.CreateStorageRequestAsync(request.ContentId.Id, body));
}
public CodexSpace Space()
{
var space = OnCodex(api => api.SpaceAsync());
return mapper.Map(space);
}
public StoragePurchase? GetPurchaseStatus(string purchaseId)
{
var purchase = OnCodex(api => api.GetPurchaseAsync(purchaseId));
return mapper.Map(purchase);
}
public string GetName()
{
return instance.Name;
@ -203,11 +180,6 @@ namespace CodexClient
return instance.MetricsEndpoint;
}
public EthAccount? GetEthAccount()
{
return instance.EthAccount;
}
public void DeleteDataDirFolder()
{
processControl.DeleteDataDirFolder();
@ -236,7 +208,7 @@ namespace CodexClient
{
var address = GetAddress();
var api = new CodexApiClient(client);
api.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
api.BaseUrl = $"{address.Host}:{address.Port}{API_BASE_URL}";
return CrashCheck(() => Time.Wait(action(api)));
}
@ -256,7 +228,7 @@ namespace CodexClient
{
return httpFactory
.CreateHttp(GetHttpId(), h => CheckContainerCrashed())
.CreateEndpoint(GetAddress(), "/api/codex/v1/", GetName());
.CreateEndpoint(GetAddress(), $"{API_BASE_URL}/", GetName());
}
private Address GetAddress()

View File

@ -5,7 +5,7 @@ using Utils;
namespace CodexClient
{
public partial interface ICodexNode : IHasEthAddress, IHasMetricsScrapeTarget
public partial interface ICodexNode : IHasMetricsScrapeTarget
{
string GetName();
string GetImageName();
@ -29,10 +29,7 @@ namespace CodexClient
CodexSpace Space();
void ConnectToPeer(ICodexNode node);
DebugInfoVersion Version { get; }
IMarketplaceAccess Marketplace { get; }
ITransferSpeeds TransferSpeeds { get; }
EthAccount EthAccount { get; }
StoragePurchase? GetPurchaseStatus(string purchaseId);
Address GetDiscoveryEndpoint();
Address GetApiEndpoint();
@ -59,11 +56,10 @@ namespace CodexClient
private readonly CodexAccess codexAccess;
private readonly IFileManager fileManager;
public CodexNode(ILog log, CodexAccess codexAccess, IFileManager fileManager, IMarketplaceAccess marketplaceAccess, ICodexNodeHooks hooks)
public CodexNode(ILog log, CodexAccess codexAccess, IFileManager fileManager, ICodexNodeHooks hooks)
{
this.codexAccess = codexAccess;
this.fileManager = fileManager;
Marketplace = marketplaceAccess;
this.hooks = hooks;
Version = new DebugInfoVersion();
transferSpeeds = new TransferSpeeds();
@ -73,7 +69,7 @@ namespace CodexClient
public void Awake()
{
hooks.OnNodeStarting(codexAccess.GetStartUtc(), codexAccess.GetImageName(), codexAccess.GetEthAccount());
hooks.OnNodeStarting(codexAccess.GetStartUtc(), codexAccess.GetImageName());
}
public void Initialize()
@ -93,33 +89,9 @@ namespace CodexClient
hooks.OnNodeStarted(this, peerId, nodeId);
}
public IMarketplaceAccess Marketplace { get; }
public DebugInfoVersion Version { get; private set; }
public ITransferSpeeds TransferSpeeds { get => transferSpeeds; }
public StoragePurchase? GetPurchaseStatus(string purchaseId)
{
return codexAccess.GetPurchaseStatus(purchaseId);
}
public EthAddress EthAddress
{
get
{
EnsureMarketplace();
return codexAccess.GetEthAccount()!.EthAddress;
}
}
public EthAccount EthAccount
{
get
{
EnsureMarketplace();
return codexAccess.GetEthAccount()!;
}
}
public string GetName()
{
return codexAccess.GetName();
@ -333,14 +305,6 @@ namespace CodexClient
log.AddStringReplace(CodexUtils.ToShortId(peerId), nodeName);
log.AddStringReplace(nodeId, nodeName);
log.AddStringReplace(CodexUtils.ToShortId(nodeId), nodeName);
var ethAccount = codexAccess.GetEthAccount();
if (ethAccount != null)
{
var addr = ethAccount.EthAddress.ToString();
log.AddStringReplace(addr, nodeName);
log.AddStringReplace(addr.ToLowerInvariant(), nodeName);
}
}
private string[] GetPeerMultiAddresses(CodexNode peer, DebugInfo peerInfo)
@ -421,11 +385,6 @@ namespace CodexClient
});
}
private void EnsureMarketplace()
{
if (codexAccess.GetEthAccount() == null) throw new Exception("Marketplace is not enabled for this Codex node. Please start it with the option '.EnableMarketplace(...)' to enable it.");
}
private void Log(string msg)
{
log.Log(msg);

View File

@ -37,16 +37,10 @@ namespace CodexClient
var processControl = processControlFactory.CreateProcessControl(instance);
var access = new CodexAccess(log, httpFactory, processControl, instance);
var hooks = hooksFactory.CreateHooks(access.GetName());
var marketplaceAccess = CreateMarketplaceAccess(instance, access, hooks);
var node = new CodexNode(log, access, fileManager, marketplaceAccess, hooks);
var node = new CodexNode(log, access, fileManager, hooks);
node.Initialize();
return node;
}
private IMarketplaceAccess CreateMarketplaceAccess(ICodexInstance instance, CodexAccess access, ICodexNodeHooks hooks)
{
// MARKETPLACE REMOVED: always return unavailable
return new MarketplaceUnavailable();
}
}
}

View File

@ -17,7 +17,6 @@ namespace CodexClient
{
public string Version { get; set; } = string.Empty;
public string Revision { get; set; } = string.Empty;
public string Contracts { get; set; } = string.Empty;
public bool IsValid()
{
@ -68,19 +67,8 @@ namespace CodexClient
public string RootHash { get; set; } = string.Empty;
public ByteSize DatasetSize { get; set; } = ByteSize.Zero;
public ByteSize BlockSize { get; set; } = ByteSize.Zero;
public bool Protected { get; set; }
}
public class SalesRequestStorageRequest
{
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 ContentId
{

View File

@ -50,24 +50,12 @@ namespace CodexClient.Hooks
{
}
public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount)
public void OnNodeStarting(DateTime startUtc, string image)
{
}
public void OnNodeStopping()
{
}
public void OnStorageAvailabilityCreated(StorageAvailability response)
{
}
public void OnStorageContractSubmitted(IStoragePurchaseContract storagePurchaseContract)
{
}
public void OnStorageContractUpdated(StoragePurchase purchaseStatus)
{
}
}
}

View File

@ -4,16 +4,13 @@ namespace CodexClient.Hooks
{
public interface ICodexNodeHooks
{
void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount);
void OnNodeStarting(DateTime startUtc, string image);
void OnNodeStarted(ICodexNode node, string peerId, string nodeId);
void OnNodeStopping();
void OnFileUploading(string uid, ByteSize size);
void OnFileUploaded(string uid, ByteSize size, ContentId cid);
void OnFileDownloading(ContentId cid);
void OnFileDownloaded(ByteSize size, ContentId cid);
void OnStorageContractSubmitted(IStoragePurchaseContract storagePurchaseContract);
void OnStorageContractUpdated(StoragePurchase purchaseStatus);
void OnStorageAvailabilityCreated(StorageAvailability response);
}
public class MuxingCodexNodeHooks : ICodexNodeHooks
@ -50,29 +47,14 @@ namespace CodexClient.Hooks
foreach (var h in backingHooks) h.OnNodeStarted(node, peerId, nodeId);
}
public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount)
public void OnNodeStarting(DateTime startUtc, string image)
{
foreach (var h in backingHooks) h.OnNodeStarting(startUtc, image, ethAccount);
foreach (var h in backingHooks) h.OnNodeStarting(startUtc, image);
}
public void OnNodeStopping()
{
foreach (var h in backingHooks) h.OnNodeStopping();
}
public void OnStorageAvailabilityCreated(StorageAvailability response)
{
foreach (var h in backingHooks) h.OnStorageAvailabilityCreated(response);
}
public void OnStorageContractSubmitted(IStoragePurchaseContract storagePurchaseContract)
{
foreach (var h in backingHooks) h.OnStorageContractSubmitted(storagePurchaseContract);
}
public void OnStorageContractUpdated(StoragePurchase purchaseStatus)
{
foreach (var h in backingHooks) h.OnStorageContractUpdated(purchaseStatus);
}
}
}

View File

@ -14,7 +14,7 @@ namespace CodexClient
Spr = debugInfo.Spr,
Addrs = debugInfo.Addrs.ToArray(),
AnnounceAddresses = debugInfo.AnnounceAddresses.ToArray(),
Version = Map(debugInfo.Codex),
Version = Map(debugInfo.Storage),
Table = Map(debugInfo.Table)
};
}
@ -35,121 +35,6 @@ namespace CodexClient
Manifest = MapManifest(dataItem.Manifest)
};
}
public CodexOpenApi.SalesAvailability Map(CreateStorageAvailability availability)
{
return new CodexOpenApi.SalesAvailability
{
Duration = ToLong(availability.MaxDuration.TotalSeconds),
MinPricePerBytePerSecond = ToDecInt(availability.MinPricePerBytePerSecond),
TotalCollateral = ToDecInt(availability.TotalCollateral),
TotalSize = availability.TotalSpace.SizeInBytes
};
}
public CodexOpenApi.StorageRequestCreation Map(StoragePurchaseRequest purchase)
{
return new CodexOpenApi.StorageRequestCreation
{
Duration = ToLong(purchase.Duration.TotalSeconds),
ProofProbability = ToDecInt(purchase.ProofProbability),
PricePerBytePerSecond = ToDecInt(purchase.PricePerBytePerSecond),
CollateralPerByte = ToDecInt(purchase.CollateralPerByte),
Expiry = ToLong(purchase.Expiry.TotalSeconds),
Nodes = Convert.ToInt32(purchase.MinRequiredNumberOfNodes),
Tolerance = Convert.ToInt32(purchase.NodeFailureTolerance)
};
}
public StorageAvailability[] Map(ICollection<CodexOpenApi.SalesAvailabilityREAD> availabilities)
{
return availabilities.Select(a => Map(a)).ToArray();
}
public StorageAvailability Map(CodexOpenApi.SalesAvailabilityREAD availability)
{
return new StorageAvailability
(
availability.Id,
ToByteSize(availability.TotalSize),
ToTimespan(availability.Duration),
new TestToken(ToBigInt(availability.MinPricePerBytePerSecond)),
new TestToken(ToBigInt(availability.TotalCollateral)),
ToByteSize(availability.FreeSize)
);
}
public StoragePurchase Map(CodexOpenApi.Purchase purchase)
{
return new StoragePurchase
{
Request = Map(purchase.Request),
State = Map(purchase.State),
Error = purchase.Error
};
}
public StoragePurchaseState Map(CodexOpenApi.PurchaseState purchaseState)
{
// Explicit mapping: If the API changes, we will get compile errors here.
// That's what we want.
switch (purchaseState)
{
case CodexOpenApi.PurchaseState.Cancelled:
return StoragePurchaseState.Cancelled;
case CodexOpenApi.PurchaseState.Errored:
return StoragePurchaseState.Errored;
case CodexOpenApi.PurchaseState.Failed:
return StoragePurchaseState.Failed;
case CodexOpenApi.PurchaseState.Finished:
return StoragePurchaseState.Finished;
case CodexOpenApi.PurchaseState.Pending:
return StoragePurchaseState.Pending;
case CodexOpenApi.PurchaseState.Started:
return StoragePurchaseState.Started;
case CodexOpenApi.PurchaseState.Submitted:
return StoragePurchaseState.Submitted;
case CodexOpenApi.PurchaseState.Unknown:
return StoragePurchaseState.Unknown;
}
throw new Exception("API incompatibility detected. Unknown purchaseState: " + purchaseState.ToString());
}
public StorageRequest Map(CodexOpenApi.StorageRequest request)
{
return new StorageRequest
{
Ask = Map(request.Ask),
Content = Map(request.Content),
Id = request.Id,
Client = request.Client,
Expiry = request.Expiry,
Nonce = request.Nonce
};
}
public StorageAsk Map(CodexOpenApi.StorageAsk ask)
{
return new StorageAsk
{
Duration = ask.Duration,
MaxSlotLoss = ask.MaxSlotLoss,
ProofProbability = ask.ProofProbability,
PricePerBytePerSecond = ask.PricePerBytePerSecond,
Slots = ask.Slots,
SlotSize = ask.SlotSize
};
}
public StorageContent Map(CodexOpenApi.Content content)
{
return new StorageContent
{
Cid = content.Cid
};
}
public CodexSpace Map(CodexOpenApi.Space space)
{
return new CodexSpace
@ -161,13 +46,12 @@ namespace CodexClient
};
}
private DebugInfoVersion Map(CodexOpenApi.CodexVersion obj)
private DebugInfoVersion Map(CodexOpenApi.StorageVersion obj)
{
return new DebugInfoVersion
{
Version = obj.Version,
Revision = obj.Revision,
Contracts = obj.Contracts
Revision = obj.Revision
};
}
@ -209,8 +93,7 @@ namespace CodexClient
{
BlockSize = new ByteSize(Convert.ToInt64(manifest.BlockSize)),
DatasetSize = new ByteSize(Convert.ToInt64(manifest.DatasetSize)),
RootHash = manifest.TreeCid,
Protected = manifest.Protected
RootHash = manifest.TreeCid
};
}

View File

@ -1,108 +0,0 @@
using CodexClient.Hooks;
using Logging;
using Utils;
namespace CodexClient
{
public interface IMarketplaceAccess
{
string MakeStorageAvailable(CreateStorageAvailability availability);
StorageAvailability[] GetAvailabilities();
IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase);
}
// MARKETPLACE REMOVED: MarketplaceAccess implementation commented out
// public class MarketplaceAccess : IMarketplaceAccess
// {
// private readonly ILog log;
// private readonly CodexAccess codexAccess;
// private readonly ICodexNodeHooks hooks;
//
// public MarketplaceAccess(ILog log, CodexAccess codexAccess, ICodexNodeHooks hooks)
// {
// this.log = log;
// this.codexAccess = codexAccess;
// this.hooks = hooks;
// }
//
// public IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase)
// {
// purchase.Log(log);
// var swResult = Stopwatch.Measure(log, nameof(RequestStorage), () =>
// {
// return codexAccess.RequestStorage(purchase);
// });
//
// var response = swResult.Value;
//
// if (string.IsNullOrEmpty(response) ||
// response == "Unable to encode manifest" ||
// response == "Purchasing not available" ||
// response == "Expiry required" ||
// response == "Expiry needs to be in future" ||
// response == "Expiry has to be before the request's end (now + duration)")
// {
// throw new InvalidOperationException(response);
// }
//
// Log($"Storage requested successfully. PurchaseId: '{response}'.");
//
// var logName = $"<Purchase-{response.Substring(0, 3)}>";
// log.AddStringReplace(response, logName);
// log.AddStringReplace(response.ToLowerInvariant(), logName);
// return new StoragePurchaseContract(log, codexAccess, response, purchase, hooks);
// }
//
// public string MakeStorageAvailable(CreateStorageAvailability availability)
// {
// availability.Log(log);
//
// var response = codexAccess.SalesAvailability(availability);
//
// Log($"Storage successfully made available. Id: {response.Id}");
// hooks.OnStorageAvailabilityCreated(response);
//
// return response.Id;
// }
//
// public StorageAvailability[] GetAvailabilities()
// {
// var result = codexAccess.GetAvailabilities();
// Log($"Got {result.Length} availabilities:");
// foreach (var a in result) a.Log(log);
// return result;
// }
//
// private void Log(string msg)
// {
// log.Log($"{codexAccess.GetName()} {msg}");
// }
// }
public class MarketplaceUnavailable : IMarketplaceAccess
{
public string MakeStorageAvailable(CreateStorageAvailability availability)
{
Unavailable();
throw new NotImplementedException();
}
public IStoragePurchaseContract RequestStorage(StoragePurchaseRequest purchase)
{
Unavailable();
throw new NotImplementedException();
}
public StorageAvailability[] GetAvailabilities()
{
Unavailable();
throw new NotImplementedException();
}
private void Unavailable()
{
FrameworkAssert.Fail("Incorrect test setup: Marketplace was not enabled for this group of Codex nodes. Add 'EnableMarketplace(...)' after 'SetupCodexNodes()' to enable it.");
throw new InvalidOperationException();
}
}
}

View File

@ -1,142 +0,0 @@
using Logging;
using Utils;
namespace CodexClient
{
public class StoragePurchaseRequest
{
public StoragePurchaseRequest(ContentId cid)
{
ContentId = cid;
}
public ContentId ContentId { get; }
public TestToken PricePerBytePerSecond { get; set; } = 1.TstWei();
public TestToken CollateralPerByte { get; set; } = 1.TstWei();
public uint MinRequiredNumberOfNodes { get; set; }
public uint NodeFailureTolerance { get; set; }
public int ProofProbability { get; set; }
public TimeSpan Duration { get; set; }
public TimeSpan Expiry { get; set; }
public void Log(ILog log)
{
log.Log($"Requesting storage for: {ContentId.Id}... (" +
$"pricePerBytePerSecond: {PricePerBytePerSecond}, " +
$"collateralPerByte: {CollateralPerByte}, " +
$"minRequiredNumberOfNodes: {MinRequiredNumberOfNodes}, " +
$"nodeFailureTolerance: {NodeFailureTolerance}, " +
$"proofProbability: {ProofProbability}, " +
$"expiry: {Time.FormatDuration(Expiry)}, " +
$"duration: {Time.FormatDuration(Duration)})");
}
}
public class StoragePurchase
{
public StoragePurchaseState State { get; set; } = StoragePurchaseState.Unknown;
public string Error { get; set; } = string.Empty;
public StorageRequest Request { get; set; } = null!;
public bool IsCancelled => State == StoragePurchaseState.Cancelled;
public bool IsError => State == StoragePurchaseState.Errored;
public bool IsFinished => State == StoragePurchaseState.Finished;
public bool IsStarted => State == StoragePurchaseState.Started;
public bool IsSubmitted => State == StoragePurchaseState.Submitted;
}
public enum StoragePurchaseState
{
Cancelled = 0,
Errored = 1,
Failed = 2,
Finished = 3,
Pending = 4,
Started = 5,
Submitted = 6,
Unknown = 7,
}
public class StorageRequest
{
public string Id { get; set; } = string.Empty;
public string Client { get; set; } = string.Empty;
public StorageAsk Ask { get; set; } = null!;
public StorageContent Content { get; set; } = null!;
public long Expiry { get; set; }
public string Nonce { get; set; } = string.Empty;
}
public class StorageAsk
{
public long Slots { get; set; }
public long SlotSize { get; set; }
public long Duration { get; set; }
public string ProofProbability { get; set; } = string.Empty;
public string PricePerBytePerSecond { get; set; } = string.Empty;
public long MaxSlotLoss { get; set; }
}
public class StorageContent
{
public string Cid { get; set; } = string.Empty;
//public ErasureParameters Erasure { get; set; }
//public PoRParameters Por { get; set; }
}
public class CreateStorageAvailability
{
public CreateStorageAvailability(ByteSize totalSpace, TimeSpan maxDuration, TestToken minPricePerBytePerSecond, TestToken totalCollateral)
{
TotalSpace = totalSpace;
MaxDuration = maxDuration;
MinPricePerBytePerSecond = minPricePerBytePerSecond;
TotalCollateral = totalCollateral;
}
public ByteSize TotalSpace { get; }
public TimeSpan MaxDuration { get; }
public TestToken MinPricePerBytePerSecond { get; }
public TestToken TotalCollateral { get; }
public void Log(ILog log)
{
log.Log($"Create storage Availability: (" +
$"totalSize: {TotalSpace}, " +
$"maxDuration: {Time.FormatDuration(MaxDuration)}, " +
$"minPricePerBytePerSecond: {MinPricePerBytePerSecond}, " +
$"totalCollateral: {TotalCollateral})");
}
}
public class StorageAvailability
{
public StorageAvailability(string id, ByteSize totalSpace, TimeSpan maxDuration, TestToken minPricePerBytePerSecond, TestToken totalCollateral, ByteSize freeSpace)
{
Id = id;
TotalSpace = totalSpace;
MaxDuration = maxDuration;
MinPricePerBytePerSecond = minPricePerBytePerSecond;
TotalCollateral = totalCollateral;
FreeSpace = freeSpace;
}
public string Id { get; }
public ByteSize TotalSpace { get; }
public TimeSpan MaxDuration { get; }
public TestToken MinPricePerBytePerSecond { get; }
public TestToken TotalCollateral { get; }
public ByteSize FreeSpace { get; }
public void Log(ILog log)
{
log.Log($"Storage Availability: (" +
$"id: {Id}, " +
$"totalSize: {TotalSpace}, " +
$"maxDuration: {Time.FormatDuration(MaxDuration)}, " +
$"minPricePerBytePerSecond: {MinPricePerBytePerSecond}, " +
$"totalCollateral: {TotalCollateral}, " +
$"freeSpace: {FreeSpace})");
}
}
}

View File

@ -1,214 +0,0 @@
using CodexClient.Hooks;
using Logging;
using Newtonsoft.Json;
using Utils;
namespace CodexClient
{
public interface IStoragePurchaseContract
{
string PurchaseId { get; }
StoragePurchaseRequest Purchase { get; }
ContentId ContentId { get; }
StoragePurchase? GetStatus();
void WaitForStorageContractSubmitted();
void WaitForStorageContractExpired();
void WaitForStorageContractStarted();
void WaitForStorageContractFinished();
void WaitForContractFailed(IMarketplaceConfigInput config);
}
public interface IMarketplaceConfigInput
{
int MaxNumberOfSlashes { get; }
TimeSpan PeriodDuration { get; }
}
// MARKETPLACE REMOVED: StoragePurchaseContract implementation commented out
// public class StoragePurchaseContract : IStoragePurchaseContract
// {
// private readonly ILog log;
// private readonly CodexAccess codexAccess;
// private readonly ICodexNodeHooks hooks;
// private readonly TimeSpan gracePeriod = TimeSpan.FromSeconds(60);
// private readonly DateTime contractPendingUtc = DateTime.UtcNow;
// private DateTime? contractSubmittedUtc = DateTime.UtcNow;
// private DateTime? contractStartedUtc;
// private DateTime? contractFinishedUtc;
// private StoragePurchaseState lastState = StoragePurchaseState.Unknown;
// private ContentId encodedContentId = new ContentId();
//
// public StoragePurchaseContract(ILog log, CodexAccess codexAccess, string purchaseId, StoragePurchaseRequest purchase, ICodexNodeHooks hooks)
// {
// this.log = log;
// this.codexAccess = codexAccess;
// PurchaseId = purchaseId;
// Purchase = purchase;
// this.hooks = hooks;
// }
//
// public string PurchaseId { get; }
// public StoragePurchaseRequest Purchase { get; }
// public ContentId ContentId
// {
// get
// {
// if (string.IsNullOrEmpty(encodedContentId.Id)) GetStatus();
// return encodedContentId;
// }
// }
//
// public TimeSpan? PendingToSubmitted => contractSubmittedUtc - contractPendingUtc;
// public TimeSpan? SubmittedToStarted => contractStartedUtc - contractSubmittedUtc;
// public TimeSpan? SubmittedToFinished => contractFinishedUtc - contractSubmittedUtc;
//
// public StoragePurchase? GetStatus()
// {
// var status = codexAccess.GetPurchaseStatus(PurchaseId);
// if (status != null)
// {
// encodedContentId = new ContentId(status.Request.Content.Cid);
// }
// return status;
// }
//
// public void WaitForStorageContractSubmitted()
// {
// var timeout = Purchase.Expiry + gracePeriod;
// var raiseHook = lastState != StoragePurchaseState.Submitted;
// WaitForStorageContractState(timeout, StoragePurchaseState.Submitted, sleep: 200);
// contractSubmittedUtc = DateTime.UtcNow;
// if (raiseHook) hooks.OnStorageContractSubmitted(this);
// LogSubmittedDuration();
// AssertDuration(PendingToSubmitted, timeout, nameof(PendingToSubmitted));
// }
//
// public void WaitForStorageContractExpired()
// {
// var timeout = Purchase.Expiry + gracePeriod + gracePeriod;
// WaitForStorageContractState(timeout, StoragePurchaseState.Cancelled);
// }
//
// public void WaitForStorageContractStarted()
// {
// var timeout = Purchase.Expiry + gracePeriod;
//
// WaitForStorageContractState(timeout, StoragePurchaseState.Started);
// contractStartedUtc = DateTime.UtcNow;
// LogStartedDuration();
// AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted));
// }
//
// public void WaitForStorageContractFinished()
// {
// if (!contractStartedUtc.HasValue)
// {
// WaitForStorageContractStarted();
// }
// var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value;
// var timeout = (Purchase.Duration - currentContractTime) + gracePeriod;
// WaitForStorageContractState(timeout, StoragePurchaseState.Finished);
// contractFinishedUtc = DateTime.UtcNow;
// LogFinishedDuration();
// AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished));
// }
//
// public void WaitForContractFailed(IMarketplaceConfigInput config)
// {
// if (!contractStartedUtc.HasValue)
// {
// WaitForStorageContractStarted();
// }
// var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value;
// var timeout = (Purchase.Duration - currentContractTime) + gracePeriod;
// var minTimeout = TimeNeededToFailEnoughProofsToFreeASlot(config);
//
// if (timeout < minTimeout)
// {
// throw new ArgumentOutOfRangeException(
// $"Test is misconfigured. Assuming a proof is required every period, it will take {Time.FormatDuration(minTimeout)} " +
// $"to fail enough proofs for a slot to be freed. But, the storage contract will complete in {Time.FormatDuration(timeout)}. " +
// $"Increase the duration."
// );
// }
//
// WaitForStorageContractState(timeout, StoragePurchaseState.Errored);
// }
//
// private TimeSpan TimeNeededToFailEnoughProofsToFreeASlot(IMarketplaceConfigInput config)
// {
// var numMissedProofsRequiredForFree = config.MaxNumberOfSlashes;
// var timePerProof = config.PeriodDuration;
// var result = timePerProof * (numMissedProofsRequiredForFree + 1);
//
// // Times 2!
// // Because of pointer-downtime it's possible that some periods even though there's a probability of 100%
// // will not require any proof. To be safe we take twice the required time.
// return result * 2;
// }
//
// private void WaitForStorageContractState(TimeSpan timeout, StoragePurchaseState desiredState, int sleep = 1000)
// {
// var waitStart = DateTime.UtcNow;
//
// Log($"Waiting for {Time.FormatDuration(timeout)} to reach state '{desiredState}'.");
// while (lastState != desiredState)
// {
// Thread.Sleep(sleep);
//
// var purchaseStatus = codexAccess.GetPurchaseStatus(PurchaseId);
// var statusJson = JsonConvert.SerializeObject(purchaseStatus);
// if (purchaseStatus != null && purchaseStatus.State != lastState)
// {
// lastState = purchaseStatus.State;
// log.Debug("Purchase status: " + statusJson);
// hooks.OnStorageContractUpdated(purchaseStatus);
// }
//
// if (desiredState != StoragePurchaseState.Errored && lastState == StoragePurchaseState.Errored)
// {
// FrameworkAssert.Fail("Contract errored: " + statusJson);
// }
//
// if (DateTime.UtcNow - waitStart > timeout)
// {
// FrameworkAssert.Fail($"Contract did not reach '{desiredState}' within {Time.FormatDuration(timeout)} timeout. {statusJson}");
// }
// }
// }
//
// private void LogSubmittedDuration()
// {
// Log($"Pending to Submitted in {Time.FormatDuration(PendingToSubmitted)} " +
// $"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})");
// }
//
// private void LogStartedDuration()
// {
// Log($"Submitted to Started in {Time.FormatDuration(SubmittedToStarted)} " +
// $"( < {Time.FormatDuration(Purchase.Expiry + gracePeriod)})");
// }
//
// private void LogFinishedDuration()
// {
// Log($"Submitted to Finished in {Time.FormatDuration(SubmittedToFinished)} " +
// $"( < {Time.FormatDuration(Purchase.Duration + gracePeriod)})");
// }
//
// private void AssertDuration(TimeSpan? span, TimeSpan max, string message)
// {
// if (span == null) throw new ArgumentNullException(nameof(MarketplaceAccess) + ": " + message + " (IsNull)");
// if (span.Value.TotalDays >= max.TotalSeconds)
// {
// throw new Exception(nameof(MarketplaceAccess) +
// $": Duration out of range. Max: {Time.FormatDuration(max)} but was: {Time.FormatDuration(span.Value)} " +
// message);
// }
// }
//
// private void Log(string msg)
// {
// log.Log($"[{PurchaseId}] {msg}");
// }
// }
}

View File

@ -2,8 +2,8 @@ openapi: 3.0.3
info:
version: 0.0.1
title: Codex API
description: "List of endpoints and interfaces available to Codex API users"
title: Logos Storage API
description: "List of endpoints and interfaces available to Logos Storage API users"
security:
- {}
@ -32,42 +32,11 @@ components:
description: Content Identifier as specified at https://github.com/multiformats/cid
example: QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N
SlotId:
type: string
description: Keccak hash of the abi encoded tuple (RequestId, slot index)
example: 268a781e0db3f7cf36b18e5f4fdb7f586ec9edd08e5500b17c0e518a769f114a
LogLevel:
type: string
description: "One of the log levels: TRACE, DEBUG, INFO, NOTICE, WARN, ERROR or FATAL"
example: DEBUG
EthereumAddress:
type: string
description: Address of Ethereum address
PricePerBytePerSecond:
type: string
description: The amount of tokens paid per byte per second per slot to hosts the client is willing to pay
CollateralPerByte:
type: string
description: Number as decimal string that represents how much collateral per byte is asked from hosts that wants to fill a slots
Duration:
type: integer
format: int64
description: The duration of the request in seconds
ProofProbability:
type: string
description: How often storage proofs are required as decimal string
Expiry:
type: integer
format: int64
description: A timestamp as seconds since unix epoch at which this request expires if the Request does not find requested amount of nodes to host the data.
SPR:
type: string
description: Signed Peer Record (libp2p)
@ -86,15 +55,6 @@ components:
id:
$ref: "#/components/schemas/PeerId"
Content:
type: object
required:
- cid
description: Parameters specifying the content
properties:
cid:
$ref: "#/components/schemas/Cid"
Node:
type: object
required:
@ -115,7 +75,7 @@ components:
seen:
type: boolean
CodexVersion:
StorageVersion:
type: object
properties:
version:
@ -124,9 +84,6 @@ components:
revision:
type: string
example: 0c647d8
contracts:
type: string
example: 0b537c7
PeersTable:
type: object
@ -150,7 +107,7 @@ components:
- spr
- announceAddresses
- table
- codex
- storage
properties:
id:
$ref: "#/components/schemas/PeerId"
@ -169,253 +126,8 @@ components:
$ref: "#/components/schemas/MultiAddress"
table:
$ref: "#/components/schemas/PeersTable"
codex:
$ref: "#/components/schemas/CodexVersion"
SalesAvailability:
type: object
required:
- totalSize
- duration
- minPricePerBytePerSecond
- totalCollateral
properties:
totalSize:
type: integer
format: int64
description: Total size of availability's storage in bytes
duration:
$ref: "#/components/schemas/Duration"
minPricePerBytePerSecond:
type: string
description: Minimal price per byte per second paid (in amount of tokens) for the hosted request's slot for the request's duration as decimal string
totalCollateral:
type: string
description: Total collateral (in amount of tokens) that can be used for matching requests
enabled:
type: boolean
description: Enable the ability to receive sales on this availability.
default: true
until:
type: integer
description: Specifies the latest timestamp, after which the availability will no longer host any slots. If set to 0, there will be no restrictions.
default: 0
SalesAvailabilityREAD:
required:
- id
- totalRemainingCollateral
- freeSize
allOf:
- $ref: "#/components/schemas/SalesAvailability"
- type: object
properties:
id:
$ref: "#/components/schemas/Id"
readonly: true
freeSize:
type: integer
format: int64
description: Unused size of availability's storage in bytes as decimal string
readOnly: true
totalRemainingCollateral:
type: string
description: Total collateral effective (in amount of tokens) that can be used for matching requests
readOnly: true
Slot:
type: object
required:
- id
- request
- slotIndex
properties:
id:
$ref: "#/components/schemas/SlotId"
request:
$ref: "#/components/schemas/StorageRequest"
slotIndex:
type: integer
format: int64
description: Slot Index number
SlotAgent:
type: object
required:
- state
- requestId
- slotIndex
properties:
slotIndex:
type: integer
format: int64
description: Slot Index number
requestId:
$ref: "#/components/schemas/Id"
request:
$ref: "#/components/schemas/StorageRequest"
reservation:
$ref: "#/components/schemas/Reservation"
state:
type: string
description: Description of the slot's
enum:
- SaleCancelled
- SaleDownloading
- SaleErrored
- SaleFailed
- SaleFilled
- SaleFilling
- SaleFinished
- SaleIgnored
- SaleInitialProving
- SalePayout
- SalePreparing
- SaleProving
- SaleUnknown
Reservation:
type: object
required:
- id
- availabilityId
- size
- requestId
- slotIndex
- validUntil
properties:
id:
$ref: "#/components/schemas/Id"
availabilityId:
$ref: "#/components/schemas/Id"
size:
type: integer
format: int64
description: Size of the slot in bytes
requestId:
$ref: "#/components/schemas/Id"
slotIndex:
type: integer
format: int64
description: Slot Index number
validUntil:
type: integer
description: Timestamp after which the reservation will no longer be valid.
StorageRequestCreation:
type: object
required:
- pricePerBytePerSecond
- duration
- proofProbability
- collateralPerByte
- expiry
properties:
duration:
$ref: "#/components/schemas/Duration"
pricePerBytePerSecond:
$ref: "#/components/schemas/PricePerBytePerSecond"
proofProbability:
$ref: "#/components/schemas/ProofProbability"
nodes:
description: Minimal number of nodes the content should be stored on
type: integer
default: 3
minimum: 3
tolerance:
description: Additional number of nodes on top of the `nodes` property that can be lost before pronouncing the content lost
type: integer
default: 1
minimum: 1
collateralPerByte:
$ref: "#/components/schemas/CollateralPerByte"
expiry:
type: integer
format: int64
description: Number that represents expiry threshold in seconds from when the Request is submitted. When the threshold is reached and the Request does not find requested amount of nodes to host the data, the Request is voided. The number of seconds can not be higher then the Request's duration itself.
StorageAsk:
type: object
required:
- slots
- slotSize
- duration
- proofProbability
- pricePerBytePerSecond
- collateralPerByte
- maxSlotLoss
properties:
slots:
description: Number of slots (eq. hosts) that the Request want to have the content spread over
type: integer
format: int64
slotSize:
type: integer
format: int64
description: Amount of storage per slot in bytes
duration:
$ref: "#/components/schemas/Duration"
proofProbability:
$ref: "#/components/schemas/ProofProbability"
pricePerBytePerSecond:
$ref: "#/components/schemas/PricePerBytePerSecond"
collateralPerByte:
$ref: "#/components/schemas/CollateralPerByte"
maxSlotLoss:
type: integer
format: int64
description: Max slots that can be lost without data considered to be lost
StorageRequest:
type: object
required:
- id
- client
- ask
- content
- expiry
- nonce
properties:
id:
type: string
description: Request ID
client:
$ref: "#/components/schemas/EthereumAddress"
ask:
$ref: "#/components/schemas/StorageAsk"
content:
$ref: "#/components/schemas/Content"
expiry:
$ref: "#/components/schemas/Expiry"
nonce:
type: string
description: Random data
Purchase:
type: object
required:
- state
- requestId
properties:
state:
type: string
description: Description of the Request's state
enum:
- cancelled
- errored
- failed
- finished
- pending
- started
- submitted
- unknown
error:
type: string
nullable: true
description: If Request failed, then here is presented the error message
request:
$ref: "#/components/schemas/StorageRequest"
requestId:
$ref: "#/components/schemas/Id"
storage:
$ref: "#/components/schemas/StorageVersion"
DataList:
type: object
@ -444,7 +156,6 @@ components:
- treeCid
- datasetSize
- blockSize
- protected
properties:
treeCid:
$ref: "#/components/schemas/Cid"
@ -456,9 +167,6 @@ components:
blockSize:
type: integer
description: "Size of blocks"
protected:
type: boolean
description: "Indicates if content is protected by erasure-coding"
filename:
type: string
nullable: true
@ -485,22 +193,20 @@ components:
quotaMaxBytes:
type: integer
format: int64
description: "Maximum storage space (in bytes) available for the node in Codex's local repository."
description: "Maximum storage space (in bytes) available for the node in Logos Storage's local repository."
quotaUsedBytes:
type: integer
format: int64
description: "Amount of storage space (in bytes) currently used for storing files in Codex's local repository."
description: "Amount of storage space (in bytes) currently used for storing files in Logos Storage's local repository."
quotaReservedBytes:
type: integer
format: int64
description: "Amount of storage reserved (in bytes) in the Codex's local repository for future use when storage requests will be picked up and hosted by the node using node's availabilities. This does not include the storage currently in use."
description: "Amount of storage reserved (in bytes) in the Logos Storage's local repository for future use when storage requests will be picked up and hosted by the node using node's availabilities. This does not include the storage currently in use."
servers:
- url: "http://localhost:8080/api/codex/v1"
- url: "http://localhost:8080/api/storage/v1"
tags:
- name: Marketplace
description: Marketplace information and operations
- name: Data
description: Data operations
- name: Node
@ -727,6 +433,34 @@ paths:
"500":
description: Well it was bad-bad
"/data/{cid}/exists":
get:
summary: "Check if a block identified by CID exists in the local node."
tags: [Data]
operationId: hasBlock
parameters:
- in: path
name: cid
required: true
schema:
$ref: "#/components/schemas/Cid"
description: "CID of the block to check."
responses:
"200":
description: Block existence information
content:
application/json:
schema:
type: object
properties:
has:
type: boolean
description: Indicates whether the block exists in the local node
"400":
description: Invalid CID is specified
"500":
description: Well it was bad-bad
"/space":
get:
summary: "Gets a summary of the storage space allocation of the node."
@ -743,237 +477,6 @@ paths:
"500":
description: "It's not working as planned"
"/sales/slots":
get:
summary: "Returns active slots"
tags: [Marketplace]
operationId: getActiveSlots
responses:
"200":
description: Retrieved active slots
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Slot"
"503":
description: Persistence is not enabled
"/sales/slots/{slotId}":
get:
summary: "Returns active slot with id {slotId} for the host"
tags: [Marketplace]
operationId: getActiveSlotById
parameters:
- in: path
name: slotId
required: true
schema:
$ref: "#/components/schemas/Cid"
description: File to be downloaded.
responses:
"200":
description: Retrieved active slot
content:
application/json:
schema:
$ref: "#/components/schemas/SlotAgent"
"400":
description: Invalid or missing SlotId
"404":
description: Host is not in an active sale for the slot
"503":
description: Persistence is not enabled
"/sales/availability":
get:
summary: "Returns storage that is for sale"
tags: [Marketplace]
operationId: getAvailabilities
responses:
"200":
description: Retrieved storage availabilities of the node
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/SalesAvailabilityREAD"
"500":
description: Error getting unused availabilities
"503":
description: Persistence is not enabled
post:
summary: "Offers storage for sale"
operationId: offerStorage
tags: [Marketplace]
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/SalesAvailability"
responses:
"201":
description: Created storage availability
content:
application/json:
schema:
$ref: "#/components/schemas/SalesAvailabilityREAD"
"400":
description: Invalid data input
"422":
description: Not enough node's storage quota available or the provided parameters did not pass validation
"500":
description: Error reserving availability
"503":
description: Persistence is not enabled
"/sales/availability/{id}":
patch:
summary: "Updates availability"
description: |
The new parameters will be only considered for new requests.
Existing Requests linked to this Availability will continue as is.
operationId: updateOfferedStorage
tags: [Marketplace]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: ID of Availability
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/SalesAvailability"
responses:
"204":
description: Availability successfully updated
"400":
description: Invalid data input
"404":
description: Availability not found
"422":
description: The provided parameters did not pass validation
"500":
description: Error reserving availability
"503":
description: Persistence is not enabled
"/sales/availability/{id}/reservations":
get:
summary: "Get availability's reservations"
description: Return's list of Reservations for ongoing Storage Requests that the node hosts.
operationId: getReservations
tags: [Marketplace]
parameters:
- in: path
name: id
required: true
schema:
type: string
description: ID of Availability
responses:
"200":
description: Retrieved storage availabilities of the node
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Reservation"
"400":
description: Invalid Availability ID
"404":
description: Availability not found
"500":
description: Error getting reservations
"503":
description: Persistence is not enabled
"/storage/request/{cid}":
post:
summary: "Creates a new Request for storage"
tags: [Marketplace]
operationId: createStorageRequest
parameters:
- in: path
name: cid
required: true
schema:
$ref: "#/components/schemas/Cid"
description: CID of the uploaded data that should be stored
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/StorageRequestCreation"
responses:
"200":
description: Returns the Request ID as decimal string
content:
text/plain:
schema:
type: string
"400":
description: Invalid or missing Request ID
"422":
description: The storage request parameters are not valid
"404":
description: Request ID not found
"503":
description: Persistence is not enabled
"/storage/purchases":
get:
summary: "Returns list of purchase IDs"
tags: [Marketplace]
operationId: getPurchases
responses:
"200":
description: Gets all purchase IDs stored in node
content:
application/json:
schema:
type: array
items:
type: string
"503":
description: Persistence is not enabled
"/storage/purchases/{id}":
get:
summary: "Returns purchase details"
tags: [Marketplace]
operationId: getPurchase
parameters:
- in: path
name: id
required: true
schema:
type: string
description: Hexadecimal ID of a Purchase
responses:
"200":
description: Purchase details
content:
application/json:
schema:
$ref: "#/components/schemas/Purchase"
"400":
description: Invalid or missing Purchase ID
"404":
description: Purchase not found
"503":
description: Persistence is not enabled
"/spr":
get:
summary: "Get Node's SPR"

View File

@ -1,78 +0,0 @@
using CodexContractsPlugin.Marketplace;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public class ChainEvents
{
private ChainEvents(
BlockInterval blockInterval,
StorageRequestedEventDTO[] requests,
RequestFulfilledEventDTO[] fulfilled,
RequestCancelledEventDTO[] cancelled,
RequestFailedEventDTO[] failed,
SlotFilledEventDTO[] slotFilled,
SlotFreedEventDTO[] slotFreed,
SlotReservationsFullEventDTO[] slotReservationsFull,
ProofSubmittedEventDTO[] proofSubmitted
)
{
BlockInterval = blockInterval;
Requests = requests;
Fulfilled = fulfilled;
Cancelled = cancelled;
Failed = failed;
SlotFilled = slotFilled;
SlotFreed = slotFreed;
SlotReservationsFull = slotReservationsFull;
ProofSubmitted = proofSubmitted;
All = ConcatAll<IHasBlock>(requests, fulfilled, cancelled, failed, slotFilled, SlotFreed, SlotReservationsFull, ProofSubmitted);
}
public BlockInterval BlockInterval { get; }
public StorageRequestedEventDTO[] Requests { get; }
public RequestFulfilledEventDTO[] Fulfilled { get; }
public RequestCancelledEventDTO[] Cancelled { get; }
public RequestFailedEventDTO[] Failed { get; }
public SlotFilledEventDTO[] SlotFilled { get; }
public SlotFreedEventDTO[] SlotFreed { get; }
public SlotReservationsFullEventDTO[] SlotReservationsFull { get; }
public ProofSubmittedEventDTO[] ProofSubmitted { get; }
public IHasBlock[] All { get; }
public static ChainEvents FromBlockInterval(ICodexContracts contracts, BlockInterval blockInterval)
{
return FromContractEvents(contracts.GetEvents(blockInterval));
}
public static ChainEvents FromTimeRange(ICodexContracts contracts, TimeRange timeRange)
{
return FromContractEvents(contracts.GetEvents(timeRange));
}
public static ChainEvents FromContractEvents(ICodexContractsEvents events)
{
return new ChainEvents(
events.BlockInterval,
events.GetStorageRequestedEvents(),
events.GetRequestFulfilledEvents(),
events.GetRequestCancelledEvents(),
events.GetRequestFailedEvents(),
events.GetSlotFilledEvents(),
events.GetSlotFreedEvents(),
events.GetSlotReservationsFullEvents(),
events.GetProofSubmittedEvents()
);
}
private T[] ConcatAll<T>(params T[][] arrays)
{
var result = Array.Empty<T>();
foreach (var array in arrays)
{
result = result.Concat(array).ToArray();
}
return result;
}
}
}

View File

@ -1,259 +0,0 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.Contracts;
using Nethereum.Hex.HexConvertors.Extensions;
using System.Numerics;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public interface IChainStateChangeHandler
{
void OnNewRequest(RequestEvent requestEvent);
void OnRequestFinished(RequestEvent requestEvent);
void OnRequestFulfilled(RequestEvent requestEvent);
void OnRequestCancelled(RequestEvent requestEvent);
void OnRequestFailed(RequestEvent requestEvent);
void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex, bool isRepair);
void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex);
void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex);
void OnProofSubmitted(BlockTimeEntry block, string id);
void OnError(string msg);
}
public class RequestEvent
{
public RequestEvent(BlockTimeEntry block, IChainStateRequest request)
{
Block = block;
Request = request;
}
public BlockTimeEntry Block { get; }
public IChainStateRequest Request { get; }
}
public class ChainState
{
private readonly List<ChainStateRequest> requests = new List<ChainStateRequest>();
private readonly ILog log;
private readonly ICodexContracts contracts;
private readonly IChainStateChangeHandler handler;
private readonly bool doProofPeriodMonitoring;
public ChainState(ILog log, IGethNode geth, ICodexContracts contracts, IChainStateChangeHandler changeHandler, DateTime startUtc, bool doProofPeriodMonitoring, IPeriodMonitorEventHandler periodEventHandler)
{
this.log = new LogPrefixer(log, "(ChainState) ");
this.contracts = contracts;
handler = changeHandler;
this.doProofPeriodMonitoring = doProofPeriodMonitoring;
TotalSpan = new TimeRange(startUtc, startUtc);
PeriodMonitor = new PeriodMonitor(log, contracts, geth, periodEventHandler);
}
public TimeRange TotalSpan { get; private set; }
public IChainStateRequest[] Requests => requests.ToArray();
public PeriodMonitor PeriodMonitor { get; }
public int Update()
{
return Update(DateTime.UtcNow);
}
public int Update(DateTime toUtc)
{
var span = new TimeRange(TotalSpan.To, toUtc);
var events = ChainEvents.FromTimeRange(contracts, span);
Apply(events);
TotalSpan = new TimeRange(TotalSpan.From, span.To);
return events.All.Length;
}
private void Apply(ChainEvents events)
{
if (events.BlockInterval.TimeRange.From < TotalSpan.From)
{
var msg = "Attempt to update ChainState with set of events from before its current record.";
handler.OnError(msg);
throw new Exception(msg);
}
log.Debug($"ChainState updating: {events.BlockInterval} = {events.All.Length} events.");
// Run through each block and apply the events to the state in order.
var span = events.BlockInterval.TimeRange.Duration;
var numBlocks = events.BlockInterval.NumberOfBlocks;
if (numBlocks == 0) return;
var spanPerBlock = span / numBlocks;
var eventUtc = events.BlockInterval.TimeRange.From;
for (var b = events.BlockInterval.From; b <= events.BlockInterval.To; b++)
{
var blockEvents = events.All.Where(e => e.Block.BlockNumber == b).ToArray();
ApplyEvents(b, blockEvents, eventUtc);
UpdatePeriodMonitor(eventUtc);
eventUtc += spanPerBlock;
}
}
private void UpdatePeriodMonitor(DateTime eventUtc)
{
if (!doProofPeriodMonitoring) return;
var activeRequests = requests.Where(r => r.State == RequestState.Started).ToArray();
PeriodMonitor.Update(eventUtc, activeRequests);
}
private void ApplyEvents(ulong blockNumber, IHasBlock[] blockEvents, DateTime eventsUtc)
{
foreach (var e in blockEvents)
{
dynamic d = e;
ApplyEvent(d);
}
ApplyTimeImplicitEvents(blockNumber, eventsUtc);
}
private void ApplyEvent(StorageRequestedEventDTO @event)
{
if (requests.Any(r => Equal(r.RequestId, @event.RequestId)))
{
var r = FindRequest(@event);
if (r == null) throw new Exception("ChainState is inconsistent. Received already-known requestId that's not known.");
if (@event.Block.BlockNumber != @event.Block.BlockNumber) throw new Exception("Same request found in different blocks.");
log.Log("Received the same request-creation event multiple times.");
return;
}
var request = contracts.GetRequest(@event.RequestId);
var newRequest = new ChainStateRequest(log, @event.RequestId, @event.Block, request, RequestState.New);
requests.Add(newRequest);
handler.OnNewRequest(new RequestEvent(@event.Block, newRequest));
}
private void ApplyEvent(RequestFulfilledEventDTO @event)
{
var r = FindRequest(@event);
if (r == null) return;
r.UpdateState(@event.Block.BlockNumber, RequestState.Started);
handler.OnRequestFulfilled(new RequestEvent(@event.Block, r));
}
private void ApplyEvent(RequestCancelledEventDTO @event)
{
var r = FindRequest(@event);
if (r == null) return;
r.UpdateState(@event.Block.BlockNumber, RequestState.Cancelled);
handler.OnRequestCancelled(new RequestEvent(@event.Block, r));
}
private void ApplyEvent(RequestFailedEventDTO @event)
{
var r = FindRequest(@event);
if (r == null) return;
r.UpdateState(@event.Block.BlockNumber, RequestState.Failed);
handler.OnRequestFailed(new RequestEvent(@event.Block, r));
}
private void ApplyEvent(SlotFilledEventDTO @event)
{
var r = FindRequest(@event);
if (r == null) return;
var slotIndex = (int)@event.SlotIndex;
var isRepair = !r.Hosts.IsFilled(slotIndex) && r.Hosts.WasPreviouslyFilled(slotIndex);
r.Hosts.HostFillsSlot(@event.Host, slotIndex);
r.Log($"[{@event.Block.BlockNumber}] SlotFilled (host:'{@event.Host}', slotIndex:{@event.SlotIndex})");
handler.OnSlotFilled(new RequestEvent(@event.Block, r), @event.Host, @event.SlotIndex, isRepair);
}
private void ApplyEvent(SlotFreedEventDTO @event)
{
var r = FindRequest(@event);
if (r == null) return;
r.Hosts.SlotFreed((int)@event.SlotIndex);
r.Log($"[{@event.Block.BlockNumber}] SlotFreed (slotIndex:{@event.SlotIndex})");
handler.OnSlotFreed(new RequestEvent(@event.Block, r), @event.SlotIndex);
}
private void ApplyEvent(SlotReservationsFullEventDTO @event)
{
var r = FindRequest(@event);
if (r == null) return;
r.Log($"[{@event.Block.BlockNumber}] SlotReservationsFull (slotIndex:{@event.SlotIndex})");
handler.OnSlotReservationsFull(new RequestEvent(@event.Block, r), @event.SlotIndex);
}
private void ApplyEvent(ProofSubmittedEventDTO @event)
{
var id = Base58.Encode(@event.Id);
var proofOrigin = SearchForProofOrigin(id);
log.Log($"[{@event.Block.BlockNumber}] Proof submitted (id:{id} {proofOrigin})");
handler.OnProofSubmitted(@event.Block, id);
}
private string SearchForProofOrigin(string slotId)
{
foreach (var r in requests)
{
for (decimal slotIndex = 0; slotIndex < r.Request.Ask.Slots; slotIndex++)
{
var thisSlotId = contracts.GetSlotId(r.RequestId, slotIndex);
var id = Base58.Encode(thisSlotId);
if (id.ToLowerInvariant() == slotId.ToLowerInvariant())
{
return $"({r.RequestId.ToHex()} slotIndex:{slotIndex})";
}
}
}
return "(Could not identify proof requestId + slot)";
}
private void ApplyTimeImplicitEvents(ulong blockNumber, DateTime eventsUtc)
{
foreach (var r in requests)
{
if (r.State == RequestState.Started
&& r.FinishedUtc < eventsUtc)
{
r.UpdateState(blockNumber, RequestState.Finished);
handler.OnRequestFinished(new RequestEvent(new BlockTimeEntry(blockNumber, eventsUtc), r));
}
}
}
private ChainStateRequest? FindRequest(IHasBlockAndRequestId hasBoth)
{
var r = requests.SingleOrDefault(r => Equal(r.RequestId, hasBoth.RequestId));
if (r != null) return r;
try
{
var req = contracts.GetRequest(hasBoth.RequestId);
var state = contracts.GetRequestState(hasBoth.RequestId);
var newRequest = new ChainStateRequest(log, hasBoth.RequestId, hasBoth.Block, req, state);
requests.Add(newRequest);
return newRequest;
}
catch (Exception ex)
{
var msg = $"Failed to get request with id '{hasBoth.RequestId.ToHex()}' from chain: {ex}";
log.Error(msg);
handler.OnError(msg);
return null;
}
}
private bool Equal(byte[] a, byte[] b)
{
return a.SequenceEqual(b);
}
}
}

View File

@ -1,66 +0,0 @@
using BlockchainUtils;
using System.Numerics;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public class ChainStateChangeHandlerMux : IChainStateChangeHandler
{
public ChainStateChangeHandlerMux(params IChainStateChangeHandler[] handlers)
{
Handlers = handlers.ToList();
}
public List<IChainStateChangeHandler> Handlers { get; } = new List<IChainStateChangeHandler>();
public void OnNewRequest(RequestEvent requestEvent)
{
foreach (var handler in Handlers) handler.OnNewRequest(requestEvent);
}
public void OnRequestCancelled(RequestEvent requestEvent)
{
foreach (var handler in Handlers) handler.OnRequestCancelled(requestEvent);
}
public void OnRequestFailed(RequestEvent requestEvent)
{
foreach (var handler in Handlers) handler.OnRequestFailed(requestEvent);
}
public void OnRequestFinished(RequestEvent requestEvent)
{
foreach (var handler in Handlers) handler.OnRequestFinished(requestEvent);
}
public void OnRequestFulfilled(RequestEvent requestEvent)
{
foreach (var handler in Handlers) handler.OnRequestFulfilled(requestEvent);
}
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex, bool isRepair)
{
foreach (var handler in Handlers) handler.OnSlotFilled(requestEvent, host, slotIndex, isRepair);
}
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
{
foreach (var handler in Handlers) handler.OnSlotFreed(requestEvent, slotIndex);
}
public void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex)
{
foreach (var handler in Handlers) handler.OnSlotReservationsFull(requestEvent, slotIndex);
}
public void OnError(string msg)
{
foreach (var handler in Handlers) handler.OnError(msg);
}
public void OnProofSubmitted(BlockTimeEntry block, string id)
{
foreach (var handler in Handlers) handler.OnProofSubmitted(block, id);
}
}
}

View File

@ -1,102 +0,0 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using Logging;
using Nethereum.Hex.HexConvertors.Extensions;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public interface IChainStateRequest
{
byte[] RequestId { get; }
public BlockTimeEntry Block { get; }
Request Request { get; }
RequestState State { get; }
DateTime ExpiryUtc { get; }
DateTime FinishedUtc { get; }
EthAddress Client { get; }
RequestHosts Hosts { get; }
}
public class ChainStateRequest : IChainStateRequest
{
private readonly ILog log;
public ChainStateRequest(ILog log, byte[] requestId, BlockTimeEntry block, Request request, RequestState state)
{
if (requestId == null || requestId.Length != 32) throw new ArgumentException(nameof(requestId));
this.log = log;
RequestId = requestId;
Block = block;
Request = request;
State = state;
ExpiryUtc = Block.Utc + TimeSpan.FromSeconds((double)request.Expiry);
FinishedUtc = Block.Utc + TimeSpan.FromSeconds((double)request.Ask.Duration);
Log($"[{Block.BlockNumber}] Created as {State}.");
Client = new EthAddress(request.Client);
Hosts = new RequestHosts();
}
public byte[] RequestId { get; }
public BlockTimeEntry Block { get; }
public Request Request { get; }
public RequestState State { get; private set; }
public DateTime ExpiryUtc { get; }
public DateTime FinishedUtc { get; }
public EthAddress Client { get; }
public RequestHosts Hosts { get; }
public void UpdateState(ulong blockNumber, RequestState newState)
{
Log($"[{blockNumber}] Transit: {State} -> {newState}");
State = newState;
}
public void Log(string msg)
{
log.Log($"Request '{RequestId.ToHex()}': {msg}");
}
}
public class RequestHosts
{
private readonly Dictionary<int, EthAddress> hosts = new Dictionary<int, EthAddress>();
private readonly List<int> filled = new List<int>();
public void HostFillsSlot(EthAddress host, int index)
{
hosts.Add(index, host);
filled.Add(index);
}
public bool IsFilled(int index)
{
return hosts.ContainsKey(index);
}
public bool WasPreviouslyFilled(int index)
{
return filled.Contains(index);
}
public void SlotFreed(int index)
{
hosts.Remove(index);
}
public EthAddress? GetHost(int index)
{
if (!hosts.ContainsKey(index)) return null;
return hosts[index];
}
public EthAddress[] GetHosts()
{
return hosts.Values.ToArray();
}
}
}

View File

@ -1,93 +0,0 @@
using BlockchainUtils;
using System.Numerics;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public class DoNothingChainEventHandler : IChainStateChangeHandler
{
public void OnNewRequest(RequestEvent requestEvent)
{
}
public void OnRequestCancelled(RequestEvent requestEvent)
{
}
public void OnRequestFailed(RequestEvent requestEvent)
{
}
public void OnRequestFinished(RequestEvent requestEvent)
{
}
public void OnRequestFulfilled(RequestEvent requestEvent)
{
}
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex, bool isRepair)
{
}
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
{
}
public void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex)
{
}
public void OnError(string msg)
{
}
public void OnProofSubmitted(BlockTimeEntry block, string id)
{
}
}
public class DoNothingThrowingChainEventHandler : IChainStateChangeHandler
{
public void OnNewRequest(RequestEvent requestEvent)
{
}
public void OnRequestCancelled(RequestEvent requestEvent)
{
}
public void OnRequestFailed(RequestEvent requestEvent)
{
}
public void OnRequestFinished(RequestEvent requestEvent)
{
}
public void OnRequestFulfilled(RequestEvent requestEvent)
{
}
public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex, bool isRepair)
{
}
public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex)
{
}
public void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex)
{
}
public void OnError(string msg)
{
throw new Exception(msg);
}
public void OnProofSubmitted(BlockTimeEntry block, string id)
{
}
}
}

View File

@ -1,200 +0,0 @@
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.Contracts;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Model;
using Nethereum.RPC.Eth.DTOs;
using Newtonsoft.Json;
using System.Reflection;
namespace CodexContractsPlugin.ChainMonitor
{
public interface IPeriodMonitorEventHandler
{
void OnPeriodReport(PeriodReport report);
}
public class PeriodMonitor
{
private readonly ILog log;
private readonly ICodexContracts contracts;
private readonly IGethNode geth;
private readonly IPeriodMonitorEventHandler eventHandler;
private readonly List<PeriodReport> reports = new List<PeriodReport>();
private CurrentPeriod? currentPeriod = null;
public PeriodMonitor(ILog log, ICodexContracts contracts, IGethNode geth, IPeriodMonitorEventHandler eventHandler)
{
this.log = log;
this.contracts = contracts;
this.geth = geth;
this.eventHandler = eventHandler;
}
public void Update(DateTime eventUtc, IChainStateRequest[] requests)
{
var periodNumber = contracts.GetPeriodNumber(eventUtc);
if (currentPeriod == null)
{
currentPeriod = CreateCurrentPeriod(periodNumber, requests);
return;
}
if (periodNumber == currentPeriod.PeriodNumber) return;
CreateReportForPeriod(currentPeriod, requests);
currentPeriod = CreateCurrentPeriod(periodNumber, requests);
}
public PeriodMonitorResult GetAndClearReports()
{
var result = reports.ToArray();
reports.Clear();
return new PeriodMonitorResult(result);
}
private CurrentPeriod CreateCurrentPeriod(ulong periodNumber, IChainStateRequest[] requests)
{
var result = new CurrentPeriod(periodNumber);
ForEachActiveSlot(requests, (request, slotIndex) =>
{
if (contracts.IsProofRequired(request.RequestId, slotIndex) ||
contracts.WillProofBeRequired(request.RequestId, slotIndex))
{
var idx = Convert.ToInt32(slotIndex);
var host = request.Hosts.GetHost(idx);
var slotId = contracts.GetSlotId(request.RequestId, slotIndex);
if (host != null)
{
result.RequiredProofs.Add(new PeriodRequiredProof(host, request, idx, slotId));
}
}
});
return result;
}
private void CreateReportForPeriod(CurrentPeriod currentPeriod, IChainStateRequest[] requests)
{
// Fetch function calls during period. Format report.
var timeRange = contracts.GetPeriodTimeRange(currentPeriod.PeriodNumber);
var blockRange = geth.ConvertTimeRangeToBlockRange(timeRange);
var callReports = new List<FunctionCallReport>();
geth.IterateTransactions(blockRange, (t, blkI, blkUtc) =>
{
var reporter = new CallReporter(callReports, t, blkUtc, blkI);
reporter.Run();
});
var report = new PeriodReport(
new ProofPeriod(currentPeriod.PeriodNumber, timeRange, blockRange),
currentPeriod.RequiredProofs.ToArray(),
callReports.ToArray());
report.Log(log);
reports.Add(report);
eventHandler.OnPeriodReport(report);
}
private void ForEachActiveSlot(IChainStateRequest[] requests, Action<IChainStateRequest, ulong> action)
{
foreach (var request in requests)
{
for (ulong slotIndex = 0; slotIndex < request.Request.Ask.Slots; slotIndex++)
{
action(request, slotIndex);
}
}
}
}
public class DoNothingPeriodMonitorEventHandler : IPeriodMonitorEventHandler
{
public void OnPeriodReport(PeriodReport report)
{
}
}
public class CallReporter
{
private readonly List<FunctionCallReport> reports;
private readonly Transaction t;
private readonly DateTime blockUtc;
private readonly ulong blockNumber;
public CallReporter(List<FunctionCallReport> reports, Transaction t, DateTime blockUtc, ulong blockNumber)
{
this.reports = reports;
this.t = t;
this.blockUtc = blockUtc;
this.blockNumber = blockNumber;
}
public void Run()
{
CreateFunctionCallReport<CanMarkProofAsMissingFunction>();
CreateFunctionCallReport<CanReserveSlotFunction>();
CreateFunctionCallReport<ConfigurationFunction>();
CreateFunctionCallReport<CurrentCollateralFunction>();
CreateFunctionCallReport<FillSlotFunction>();
CreateFunctionCallReport<FreeSlot1Function>();
CreateFunctionCallReport<FreeSlotFunction>();
CreateFunctionCallReport<GetActiveSlotFunction>();
CreateFunctionCallReport<GetChallengeFunction>();
CreateFunctionCallReport<GetHostFunction>();
CreateFunctionCallReport<GetPointerFunction>();
CreateFunctionCallReport<GetRequestFunction>();
CreateFunctionCallReport<IsProofRequiredFunction>();
CreateFunctionCallReport<MarkProofAsMissingFunction>();
CreateFunctionCallReport<MissingProofsFunction>();
CreateFunctionCallReport<MyRequestsFunction>();
CreateFunctionCallReport<MySlotsFunction>();
CreateFunctionCallReport<RequestEndFunction>();
CreateFunctionCallReport<RequestExpiryFunction>();
CreateFunctionCallReport<RequestStateFunction>();
CreateFunctionCallReport<RequestStorageFunction>();
CreateFunctionCallReport<ReserveSlotFunction>();
CreateFunctionCallReport<SlotProbabilityFunction>();
CreateFunctionCallReport<SlotStateFunction>();
CreateFunctionCallReport<SubmitProofFunction>();
CreateFunctionCallReport<TokenFunction>();
CreateFunctionCallReport<WillProofBeRequiredFunction>();
CreateFunctionCallReport<WithdrawFundsFunction>();
CreateFunctionCallReport<WithdrawFunds1Function>();
}
private void CreateFunctionCallReport<TFunc>() where TFunc : FunctionMessage, new()
{
if (t.IsTransactionForFunctionMessage<TFunc>())
{
var func = t.DecodeTransactionToFunctionMessage<TFunc>();
reports.Add(new FunctionCallReport(blockUtc, blockNumber, typeof(TFunc).Name, JsonConvert.SerializeObject(func)));
}
}
}
public class PeriodMonitorResult
{
public PeriodMonitorResult(PeriodReport[] reports)
{
Reports = reports;
}
public PeriodReport[] Reports { get; }
}
public class CurrentPeriod
{
public CurrentPeriod(ulong periodNumber)
{
PeriodNumber = periodNumber;
}
public ulong PeriodNumber { get; }
public List<PeriodRequiredProof> RequiredProofs { get; } = new List<PeriodRequiredProof>();
}
}

View File

@ -1,55 +0,0 @@
using Logging;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public class PeriodReport
{
public PeriodReport(ProofPeriod period, PeriodRequiredProof[] required, FunctionCallReport[] functionCalls)
{
Period = period;
Required = required;
FunctionCalls = functionCalls;
}
public ProofPeriod Period { get; }
public PeriodRequiredProof[] Required { get; }
public FunctionCallReport[] FunctionCalls { get; }
public void Log(ILog log)
{
log.Log($"Period report: {Period}");
log.Log($" - Proofs required: {Required.Length}");
foreach (var r in Required)
{
log.Log($" - {r.Describe()}");
}
log.Log($" - Calls: {FunctionCalls.Length}");
foreach (var f in FunctionCalls)
{
log.Log($" - {f.Describe()}");
}
}
}
public class FunctionCallReport
{
public FunctionCallReport(DateTime utc, ulong blockNumber, string name, string payload)
{
Utc = utc;
BlockNumber = blockNumber;
Name = name;
Payload = payload;
}
public DateTime Utc { get; }
public ulong BlockNumber { get; }
public string Name { get; }
public string Payload { get; }
public string Describe()
{
return $"[{Time.FormatTimestamp(Utc)}][{BlockNumber}] {Name} = \"{Payload}\"";
}
}
}

View File

@ -1,26 +0,0 @@
using Nethereum.Hex.HexConvertors.Extensions;
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public class PeriodRequiredProof
{
public PeriodRequiredProof(EthAddress host, IChainStateRequest request, int slotIndex, byte[] slotId)
{
Host = host;
Request = request;
SlotIndex = slotIndex;
SlotId = slotId;
}
public EthAddress Host { get; }
public IChainStateRequest Request { get; }
public int SlotIndex { get; }
public byte[] SlotId { get; }
public string Describe()
{
return $"{Request.RequestId.ToHex()} slotId:{SlotId.ToHex()} slotIndex:{SlotIndex} by {Host}";
}
}
}

View File

@ -1,23 +0,0 @@
using Utils;
namespace CodexContractsPlugin.ChainMonitor
{
public class ProofPeriod
{
public ProofPeriod(ulong periodNumber, TimeRange timeRange, BlockInterval blockRange)
{
PeriodNumber = periodNumber;
TimeRange = timeRange;
BlockRange = blockRange;
}
public ulong PeriodNumber { get; }
public TimeRange TimeRange { get; }
public BlockInterval BlockRange { get; }
public override string ToString()
{
return $"{{{PeriodNumber} - {TimeRange} {BlockRange}}}";
}
}
}

View File

@ -1,223 +0,0 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.ABI;
using Nethereum.Contracts;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Util;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Utils;
namespace CodexContractsPlugin
{
public interface ICodexContracts
{
CodexContractsDeployment Deployment { get; }
bool IsDeployed();
string MintTestTokens(IHasEthAddress owner, TestToken testTokens);
string MintTestTokens(EthAddress ethAddress, TestToken testTokens);
TestToken GetTestTokenBalance(IHasEthAddress owner);
TestToken GetTestTokenBalance(EthAddress ethAddress);
string TransferTestTokens(EthAddress to, TestToken amount);
ICodexContractsEvents GetEvents(TimeRange timeRange);
ICodexContractsEvents GetEvents(BlockInterval blockInterval);
EthAddress? GetSlotHost(byte[] requestId, decimal slotIndex);
RequestState GetRequestState(byte[] requestId);
Request GetRequest(byte[] requestId);
ulong GetPeriodNumber(DateTime utc);
TimeRange GetPeriodTimeRange(ulong periodNumber);
void WaitUntilNextPeriod();
bool IsProofRequired(byte[] requestId, decimal slotIndex);
bool WillProofBeRequired(byte[] requestId, decimal slotIndex);
byte[] GetSlotId(byte[] requestId, decimal slotIndex);
ICodexContracts WithDifferentGeth(IGethNode node);
}
[JsonConverter(typeof(StringEnumConverter))]
public enum RequestState
{
New,
Started,
Cancelled,
Finished,
Failed
}
public class CodexContractsAccess : ICodexContracts
{
private readonly ILog log;
private readonly IGethNode gethNode;
public CodexContractsAccess(ILog log, IGethNode gethNode, CodexContractsDeployment deployment)
{
this.log = log;
this.gethNode = gethNode;
Deployment = deployment;
}
public CodexContractsDeployment Deployment { get; }
public bool IsDeployed()
{
return !string.IsNullOrEmpty(StartInteraction().GetTokenName(Deployment.TokenAddress));
}
public string MintTestTokens(IHasEthAddress owner, TestToken testTokens)
{
return MintTestTokens(owner.EthAddress, testTokens);
}
public string MintTestTokens(EthAddress ethAddress, TestToken testTokens)
{
return StartInteraction().MintTestTokens(ethAddress, testTokens.TstWei, Deployment.TokenAddress);
}
public TestToken GetTestTokenBalance(IHasEthAddress owner)
{
return GetTestTokenBalance(owner.EthAddress);
}
public TestToken GetTestTokenBalance(EthAddress ethAddress)
{
var balance = StartInteraction().GetBalance(Deployment.TokenAddress, ethAddress.Address);
return balance.TstWei();
}
public string TransferTestTokens(EthAddress to, TestToken amount)
{
return StartInteraction().TransferTestTokens(Deployment.TokenAddress, to.Address, amount.TstWei);
}
public ICodexContractsEvents GetEvents(TimeRange timeRange)
{
return GetEvents(gethNode.ConvertTimeRangeToBlockRange(timeRange));
}
public ICodexContractsEvents GetEvents(BlockInterval blockInterval)
{
return new CodexContractsEvents(log, gethNode, Deployment, blockInterval);
}
public EthAddress? GetSlotHost(byte[] requestId, decimal slotIndex)
{
var slotId = GetSlotId(requestId, slotIndex);
var func = new GetHostFunction
{
SlotId = slotId
};
var address = gethNode.Call<GetHostFunction, string>(Deployment.MarketplaceAddress, func);
if (string.IsNullOrEmpty(address)) return null;
return new EthAddress(address);
}
public RequestState GetRequestState(byte[] requestId)
{
if (requestId == null) throw new ArgumentNullException(nameof(requestId));
if (requestId.Length != 32) throw new InvalidDataException(nameof(requestId) + $"{nameof(requestId)} length should be 32 bytes, but was: {requestId.Length}" + requestId.Length);
var func = new RequestStateFunction
{
RequestId = requestId
};
return gethNode.Call<RequestStateFunction, RequestState>(Deployment.MarketplaceAddress, func);
}
public Request GetRequest(byte[] requestId)
{
if (requestId == null) throw new ArgumentNullException(nameof(requestId));
if (requestId.Length != 32) throw new InvalidDataException(nameof(requestId) + $"{nameof(requestId)} length should be 32 bytes, but was: {requestId.Length}" + requestId.Length);
var func = new GetRequestFunction
{
RequestId = requestId
};
var request = gethNode.Call<GetRequestFunction, GetRequestOutputDTO>(Deployment.MarketplaceAddress, func);
return request.ReturnValue1;
}
public ulong GetPeriodNumber(DateTime utc)
{
DateTimeOffset utco = DateTime.SpecifyKind(utc, DateTimeKind.Utc);
var now = utco.ToUnixTimeSeconds();
var periodSeconds = (int)Deployment.Config.Proofs.Period;
var result = now / periodSeconds;
return Convert.ToUInt64(result);
}
public TimeRange GetPeriodTimeRange(ulong periodNumber)
{
var periodSeconds = (ulong)Deployment.Config.Proofs.Period;
var startUtco = Convert.ToInt64(periodSeconds * periodNumber);
var endUtco = Convert.ToInt64(periodSeconds * (periodNumber + 1));
var start = DateTimeOffset.FromUnixTimeSeconds(startUtco).UtcDateTime;
var end = DateTimeOffset.FromUnixTimeSeconds(endUtco).UtcDateTime;
return new TimeRange(start, end);
}
public void WaitUntilNextPeriod()
{
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var periodSeconds = (int)Deployment.Config.Proofs.Period;
var secondsLeft = now % periodSeconds;
Thread.Sleep(TimeSpan.FromSeconds(secondsLeft + 1));
}
public bool IsProofRequired(byte[] requestId, decimal slotIndex)
{
var slotId = GetSlotId(requestId, slotIndex);
return IsProofRequired(slotId);
}
public bool WillProofBeRequired(byte[] requestId, decimal slotIndex)
{
var slotId = GetSlotId(requestId, slotIndex);
return WillProofBeRequired(slotId);
}
public ICodexContracts WithDifferentGeth(IGethNode node)
{
return new CodexContractsAccess(log, node, Deployment);
}
public byte[] GetSlotId(byte[] requestId, decimal slotIndex)
{
var encoder = new ABIEncode();
var encoded = encoder.GetABIEncoded(
new ABIValue("bytes32", requestId),
new ABIValue("uint256", slotIndex.ToBig())
);
return Sha3Keccack.Current.CalculateHash(encoded);
}
private bool IsProofRequired(byte[] slotId)
{
var func = new IsProofRequiredFunction
{
Id = slotId
};
var result = gethNode.Call<IsProofRequiredFunction, IsProofRequiredOutputDTO>(Deployment.MarketplaceAddress, func);
return result.ReturnValue1;
}
private bool WillProofBeRequired(byte[] slotId)
{
var func = new WillProofBeRequiredFunction
{
Id = slotId
};
var result = gethNode.Call<WillProofBeRequiredFunction, WillProofBeRequiredOutputDTO>(Deployment.MarketplaceAddress, func);
return result.ReturnValue1;
}
private ContractInteractions StartInteraction()
{
return new ContractInteractions(log, gethNode);
}
}
}

View File

@ -1,14 +0,0 @@
using GethPlugin;
namespace CodexContractsPlugin
{
public class CodexContractsContainerConfig
{
public CodexContractsContainerConfig(IGethNode gethNode)
{
GethNode = gethNode;
}
public IGethNode GethNode { get; }
}
}

View File

@ -1,62 +0,0 @@
using CodexClient;
using GethPlugin;
using KubernetesWorkflow;
using KubernetesWorkflow.Recipe;
namespace CodexContractsPlugin
{
public class CodexContractsContainerRecipe : ContainerRecipeFactory
{
public const string DeployedAddressesFilename = "/hardhat/ignition/deployments/chain-789988/deployed_addresses.json";
public const string MarketplaceArtifactFilename = "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json";
public const int PeriodSeconds = 60;
public const int TimeoutSeconds = 30;
public const int DowntimeSeconds = 128;
private readonly DebugInfoVersion versionInfo;
public override string AppName => "codex-contracts";
public override string Image => GetContractsDockerImage();
public CodexContractsContainerRecipe(DebugInfoVersion versionInfo)
{
this.versionInfo = versionInfo;
}
protected override void Initialize(StartupConfig startupConfig)
{
var config = startupConfig.Get<CodexContractsContainerConfig>();
var address = config.GethNode.StartResult.Container.GetAddress(GethContainerRecipe.HttpPortTag);
SetSchedulingAffinity(notIn: "false");
AddEnvVar("DISTTEST_NETWORK_URL", address.ToString());
// Default values:
AddEnvVar("DISTTEST_REPAIRREWARD", 10);
AddEnvVar("DISTTEST_MAXSLASHES", 2);
AddEnvVar("DISTTEST_SLASHPERCENTAGE", 20);
AddEnvVar("DISTTEST_VALIDATORREWARD", 20);
AddEnvVar("DISTTEST_DOWNTIMEPRODUCT", 67);
AddEnvVar("DISTTEST_MAXRESERVATIONS", 3);
AddEnvVar("DISTTEST_MAXDURATION", Convert.ToInt32(TimeSpan.FromDays(30).TotalSeconds));
// Customized values, required to operate in a network with
// block frequency of 1.
AddEnvVar("DISTTEST_PERIOD", PeriodSeconds);
AddEnvVar("DISTTEST_TIMEOUT", TimeoutSeconds);
AddEnvVar("DISTTEST_DOWNTIME", DowntimeSeconds);
AddEnvVar("HARDHAT_NETWORK", "codexdisttestnetwork");
AddEnvVar("HARDHAT_IGNITION_CONFIRM_DEPLOYMENT", "false");
AddEnvVar("KEEP_ALIVE", "1");
}
private string GetContractsDockerImage()
{
return $"codexstorage/codex-contracts-eth:sha-{versionInfo.Contracts}-dist-tests";
}
}
}

View File

@ -1,20 +0,0 @@
using CodexContractsPlugin.Marketplace;
namespace CodexContractsPlugin
{
public class CodexContractsDeployment
{
public CodexContractsDeployment(MarketplaceConfig config, string marketplaceAddress, string abi, string tokenAddress)
{
Config = config;
MarketplaceAddress = marketplaceAddress;
Abi = abi;
TokenAddress = tokenAddress;
}
public MarketplaceConfig Config { get; }
public string MarketplaceAddress { get; }
public string Abi { get; }
public string TokenAddress { get; }
}
}

View File

@ -1,122 +0,0 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.Contracts;
using Nethereum.Hex.HexTypes;
using Utils;
namespace CodexContractsPlugin
{
public interface ICodexContractsEvents
{
BlockInterval BlockInterval { get; }
StorageRequestedEventDTO[] GetStorageRequestedEvents();
RequestFulfilledEventDTO[] GetRequestFulfilledEvents();
RequestCancelledEventDTO[] GetRequestCancelledEvents();
RequestFailedEventDTO[] GetRequestFailedEvents();
SlotFilledEventDTO[] GetSlotFilledEvents();
SlotFreedEventDTO[] GetSlotFreedEvents();
SlotReservationsFullEventDTO[] GetSlotReservationsFullEvents();
ProofSubmittedEventDTO[] GetProofSubmittedEvents();
void GetReserveSlotCalls(Action<ReserveSlotFunction> onFunction);
}
public class CodexContractsEvents : ICodexContractsEvents
{
private readonly ILog log;
private readonly IGethNode gethNode;
private readonly CodexContractsDeployment deployment;
public CodexContractsEvents(ILog log, IGethNode gethNode, CodexContractsDeployment deployment, BlockInterval blockInterval)
{
this.log = log;
this.gethNode = gethNode;
this.deployment = deployment;
BlockInterval = blockInterval;
}
public BlockInterval BlockInterval { get; }
public StorageRequestedEventDTO[] GetStorageRequestedEvents()
{
var events = gethNode.GetEvents<StorageRequestedEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(SetBlockOnEvent).ToArray();
}
public RequestFulfilledEventDTO[] GetRequestFulfilledEvents()
{
var events = gethNode.GetEvents<RequestFulfilledEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(SetBlockOnEvent).ToArray();
}
public RequestCancelledEventDTO[] GetRequestCancelledEvents()
{
var events = gethNode.GetEvents<RequestCancelledEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(SetBlockOnEvent).ToArray();
}
public RequestFailedEventDTO[] GetRequestFailedEvents()
{
var events = gethNode.GetEvents<RequestFailedEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(SetBlockOnEvent).ToArray();
}
public SlotFilledEventDTO[] GetSlotFilledEvents()
{
var events = gethNode.GetEvents<SlotFilledEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(e =>
{
var result = e.Event;
result.Block = GetBlock(e.Log.BlockNumber.ToUlong());
result.Host = GetEthAddressFromTransaction(e.Log.TransactionHash);
return result;
}).ToArray();
}
public SlotFreedEventDTO[] GetSlotFreedEvents()
{
var events = gethNode.GetEvents<SlotFreedEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(SetBlockOnEvent).ToArray();
}
public SlotReservationsFullEventDTO[] GetSlotReservationsFullEvents()
{
var events = gethNode.GetEvents<SlotReservationsFullEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(SetBlockOnEvent).ToArray();
}
public ProofSubmittedEventDTO[] GetProofSubmittedEvents()
{
var events = gethNode.GetEvents<ProofSubmittedEventDTO>(deployment.MarketplaceAddress, BlockInterval);
return events.Select(SetBlockOnEvent).ToArray();
}
public void GetReserveSlotCalls(Action<ReserveSlotFunction> onFunction)
{
gethNode.IterateFunctionCalls<ReserveSlotFunction>(BlockInterval, (b, fn) =>
{
fn.Block = b;
onFunction(fn);
});
}
private T SetBlockOnEvent<T>(EventLog<T> e) where T : IHasBlock
{
var result = e.Event;
result.Block = GetBlock(e.Log.BlockNumber.ToUlong());
return result;
}
private BlockTimeEntry GetBlock(ulong number)
{
return gethNode.GetBlockForNumber(number);
}
private EthAddress GetEthAddressFromTransaction(string transactionHash)
{
var transaction = gethNode.GetTransaction(transactionHash);
return new EthAddress(transaction.From);
}
}
}

View File

@ -1,48 +0,0 @@
using Core;
using GethPlugin;
namespace CodexContractsPlugin
{
public class CodexContractsPlugin : IProjectPlugin, IHasLogPrefix, IHasMetadata
{
private readonly IPluginTools tools;
private readonly CodexContractsStarter starter;
public CodexContractsPlugin(IPluginTools tools)
{
this.tools = tools;
starter = new CodexContractsStarter(tools);
}
public string LogPrefix => "(CodexContracts) ";
public void Awake(IPluginAccess access)
{
}
public void Announce()
{
tools.GetLog().Log($"Loaded Codex-Marketplace SmartContracts");
}
public void AddMetadata(IAddMetadata metadata)
{
metadata.Add("codexcontractsid", "dynamic");
}
public void Decommission()
{
}
public CodexContractsDeployment DeployContracts(CoreInterface ci, IGethNode gethNode, CodexClient.DebugInfoVersion versionInfo)
{
return starter.Deploy(ci, gethNode, versionInfo);
}
public ICodexContracts WrapDeploy(IGethNode gethNode, CodexContractsDeployment deployment)
{
deployment = SerializeGate.Gate(deployment);
return starter.Wrap(gethNode, deployment);
}
}
}

View File

@ -1,20 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nethereum.Generators" Version="4.21.4" />
<PackageReference Include="Nethereum.Generators.Net" Version="4.21.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Framework\Core\Core.csproj" />
<ProjectReference Include="..\CodexClient\CodexClient.csproj" />
<ProjectReference Include="..\GethPlugin\GethPlugin.csproj" />
</ItemGroup>
</Project>

View File

@ -1,189 +0,0 @@
using CodexClient;
using CodexContractsPlugin.Marketplace;
using Core;
using GethPlugin;
using KubernetesWorkflow;
using KubernetesWorkflow.Types;
using Logging;
using Newtonsoft.Json;
using Utils;
namespace CodexContractsPlugin
{
public class CodexContractsStarter
{
private readonly IPluginTools tools;
public CodexContractsStarter(IPluginTools tools)
{
this.tools = tools;
}
public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo)
{
Log("Starting Codex SmartContracts container...");
var workflow = tools.CreateWorkflow();
var startupConfig = CreateStartupConfig(gethNode);
startupConfig.NameOverride = "codex-contracts";
var recipe = new CodexContractsContainerRecipe(versionInfo);
Log($"Using image: {recipe.Image}");
var containers = workflow.Start(1, recipe, startupConfig).WaitForOnline();
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Codex contracts container to be created. Test infra failure.");
var container = containers.Containers[0];
Log("Container started.");
var watcher = workflow.CreateCrashWatcher(container);
watcher.Start();
try
{
var result = DeployContract(container, workflow, gethNode);
workflow.Stop(containers, waitTillStopped: false);
watcher.Stop();
Log("Container stopped.");
return result;
}
catch (Exception ex)
{
Log("Failed to deploy contract: " + ex);
Log("Downloading Codex SmartContracts container log...");
ci.DownloadLog(container);
throw;
}
}
public ICodexContracts Wrap(IGethNode gethNode, CodexContractsDeployment deployment)
{
return new CodexContractsAccess(tools.GetLog(), gethNode, deployment);
}
private CodexContractsDeployment DeployContract(RunningContainer container, IStartupWorkflow workflow, IGethNode gethNode)
{
Log("Deploying SmartContract...");
WaitUntil(() =>
{
var logHandler = new ContractsReadyLogHandler(tools.GetLog());
workflow.DownloadContainerLog(container, logHandler, 100);
return logHandler.Found;
}, nameof(DeployContract));
Log("Contracts deployed. Extracting addresses...");
var extractor = new ContractsContainerInfoExtractor(tools.GetLog(), workflow, container);
var marketplaceAddress = extractor.ExtractMarketplaceAddress();
if (string.IsNullOrEmpty(marketplaceAddress)) throw new Exception("Marketplace address not received.");
var (abi, bytecode) = extractor.ExtractMarketplaceAbiAndByteCode();
if (string.IsNullOrEmpty(abi)) throw new Exception("ABI not received.");
if (string.IsNullOrEmpty(bytecode)) throw new Exception("bytecode not received.");
EnsureCompatbility(abi, bytecode);
var interaction = new ContractInteractions(tools.GetLog(), gethNode);
var tokenAddress = interaction.GetTokenAddress(marketplaceAddress);
if (string.IsNullOrEmpty(tokenAddress)) throw new Exception("Token address not received.");
Log("TokenAddress: " + tokenAddress);
Log("Extract completed. Checking sync...");
Time.WaitUntil(() => interaction.IsSynced(marketplaceAddress, abi), nameof(DeployContract));
Log("Synced. Codex SmartContracts deployed. Getting configuration...");
var config = GetMarketplaceConfiguration(marketplaceAddress, gethNode);
Log("Got config: " + JsonConvert.SerializeObject(config));
ConfigShouldEqual(config.Proofs.Period, CodexContractsContainerRecipe.PeriodSeconds, "Period");
ConfigShouldEqual(config.Proofs.Timeout, CodexContractsContainerRecipe.TimeoutSeconds, "Timeout");
ConfigShouldEqual(config.Proofs.Downtime, CodexContractsContainerRecipe.DowntimeSeconds, "Downtime");
return new CodexContractsDeployment(config, marketplaceAddress, abi, tokenAddress);
}
private void ConfigShouldEqual(ulong value, int expected, string name)
{
if (Convert.ToInt32(value) != expected)
{
// Merge todo: https://github.com/codex-storage/nim-codex/pull/1303
// Once this is merged, the contract config values are settable via env-vars.
// This plugin is already updated to set the config vars to values compatible with a
// 1-second block frequency. AND it will read back the config and assert it is deployed correctly.
// This is waiting for that merge.
// Replace log with assert WHEN MERGED:
// throw new Exception($"Config value '{name}' should be deployed as '{expected}' but was '{value}'");
Log($"MERGE TODO. Config value '{name}' should be deployed as '{expected}' but was '{value}'");
}
}
private MarketplaceConfig GetMarketplaceConfiguration(string marketplaceAddress, IGethNode gethNode)
{
var func = new ConfigurationFunctionBase();
var response = gethNode.Call<ConfigurationFunctionBase, ConfigurationOutputDTO>(marketplaceAddress, func);
return response.ReturnValue1;
}
private void EnsureCompatbility(string abi, string bytecode)
{
var expectedByteCode = MarketplaceDeploymentBase.BYTECODE.ToLowerInvariant();
if (bytecode != expectedByteCode)
{
Log("Deployed contract is incompatible with current build of CodexContracts plugin. Running self-updater...");
var selfUpdater = new SelfUpdater();
selfUpdater.Update(abi, bytecode);
}
}
private void Log(string msg)
{
tools.GetLog().Log(msg);
}
private void WaitUntil(Func<bool> predicate, string msg)
{
Time.WaitUntil(predicate, TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(2), msg);
}
private StartupConfig CreateStartupConfig(IGethNode gethNode)
{
var startupConfig = new StartupConfig();
var contractsConfig = new CodexContractsContainerConfig(gethNode);
startupConfig.Add(contractsConfig);
return startupConfig;
}
}
public class ContractsReadyLogHandler : LogHandler
{
// Log should contain 'Compiled 15 Solidity files successfully' at some point.
private const string RequiredCompiledString = "Solidity files successfully";
// When script is done, it prints the ready-string.
private const string ReadyString = "Done! Sleeping indefinitely...";
private readonly ILog log;
public ContractsReadyLogHandler(ILog log)
{
this.log = log;
log.Debug($"Looking for '{RequiredCompiledString}' and '{ReadyString}' in container logs...");
}
public bool SeenCompileString { get; private set; }
public bool Found { get; private set; }
protected override void ProcessLine(string line)
{
log.Debug(line);
if (line.Contains(RequiredCompiledString)) SeenCompileString = true;
if (line.Contains(ReadyString))
{
if (!SeenCompileString) throw new Exception("CodexContracts deployment failed. " +
"Solidity files not compiled before process exited.");
Found = true;
}
}
}
}

View File

@ -1,123 +0,0 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.Hex.HexConvertors.Extensions;
using System.Numerics;
using Utils;
namespace CodexContractsPlugin
{
public class ContractInteractions
{
private readonly ILog log;
private readonly IGethNode gethNode;
public ContractInteractions(ILog log, IGethNode gethNode)
{
this.log = log;
this.gethNode = gethNode;
}
public string GetTokenAddress(string marketplaceAddress)
{
log.Debug(marketplaceAddress);
var function = new TokenFunctionBase();
return gethNode.Call<TokenFunctionBase, string>(marketplaceAddress, function);
}
public string GetTokenName(string tokenAddress)
{
try
{
log.Debug(tokenAddress);
var function = new NameFunction();
return gethNode.Call<NameFunction, string>(tokenAddress, function);
}
catch (Exception ex)
{
log.Log("Failed to get token name: " + ex);
return string.Empty;
}
}
public string MintTestTokens(EthAddress address, BigInteger amount, string tokenAddress)
{
log.Debug($"{amount} -> {address} (token: {tokenAddress})");
return MintTokens(address.Address, amount, tokenAddress);
}
public decimal GetBalance(string tokenAddress, string account)
{
log.Debug($"({tokenAddress}) {account}");
var function = new BalanceOfFunction
{
Account = account
};
return gethNode.Call<BalanceOfFunction, BigInteger>(tokenAddress, function).ToDecimal();
}
public string TransferTestTokens(string tokenAddress, string toAccount, BigInteger amount)
{
log.Debug($"({tokenAddress}) {toAccount} {amount}");
var function = new TransferFunction
{
To = toAccount,
Value = amount
};
return gethNode.SendTransaction(tokenAddress, function);
}
public GetRequestOutputDTO GetRequest(string marketplaceAddress, byte[] requestId)
{
log.Debug($"({marketplaceAddress}) {requestId.ToHex(true)}");
var func = new GetRequestFunction
{
RequestId = requestId
};
return gethNode.Call<GetRequestFunction, GetRequestOutputDTO>(marketplaceAddress, func);
}
public bool IsSynced(string marketplaceAddress, string marketplaceAbi)
{
log.Debug();
try
{
return IsBlockNumberOK() && IsContractAvailable(marketplaceAddress, marketplaceAbi);
}
catch
{
return false;
}
}
private string MintTokens(string account, BigInteger amount, string tokenAddress)
{
log.Debug($"({tokenAddress}) {amount} --> {account}");
if (string.IsNullOrEmpty(account)) throw new ArgumentException("Invalid arguments for MintTestTokens");
var function = new MintFunction
{
Holder = account,
Amount = amount
};
return gethNode.SendTransaction(tokenAddress, function);
}
private bool IsBlockNumberOK()
{
var n = gethNode.GetSyncedBlockNumber();
return n != null && n > 256;
}
private bool IsContractAvailable(string marketplaceAddress, string marketplaceAbi)
{
return gethNode.IsContractAvailable(marketplaceAbi, marketplaceAddress);
}
}
}

View File

@ -1,77 +0,0 @@
using CodexContractsPlugin.Marketplace;
using KubernetesWorkflow;
using KubernetesWorkflow.Types;
using Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Utils;
namespace CodexContractsPlugin
{
public class ContractsContainerInfoExtractor
{
private readonly ILog log;
private readonly IStartupWorkflow workflow;
private readonly RunningContainer container;
public ContractsContainerInfoExtractor(ILog log, IStartupWorkflow workflow, RunningContainer container)
{
this.log = log;
this.workflow = workflow;
this.container = container;
}
public string ExtractMarketplaceAddress()
{
log.Debug();
var marketplaceAddress = Retry(FetchMarketplaceAddress);
if (string.IsNullOrEmpty(marketplaceAddress)) throw new InvalidOperationException("Unable to fetch marketplace account from codex-contracts node. Test infra failure.");
log.Log("MarketplaceAddress: " + marketplaceAddress);
return marketplaceAddress;
}
public (string, string) ExtractMarketplaceAbiAndByteCode()
{
log.Debug();
var (abi, bytecode) = Retry(FetchMarketplaceAbiAndByteCode);
if (string.IsNullOrEmpty(abi)) throw new InvalidOperationException("Unable to fetch marketplace artifacts from codex-contracts node. Test infra failure.");
log.Debug("Got Marketplace ABI: " + abi);
return (abi, bytecode);
}
private string FetchMarketplaceAddress()
{
var json = workflow.ExecuteCommand(container, "cat", CodexContractsContainerRecipe.DeployedAddressesFilename);
json = json.Replace("#", "_");
var addresses = JsonConvert.DeserializeObject<DeployedAddressesJson>(json);
return addresses!.Marketplace_Marketplace;
}
private (string, string) FetchMarketplaceAbiAndByteCode()
{
var json = workflow.ExecuteCommand(container, "cat", CodexContractsContainerRecipe.MarketplaceArtifactFilename);
var artifact = JObject.Parse(json);
var abi = artifact["abi"];
var byteCode = artifact["bytecode"];
var abiResult = abi!.ToString(Formatting.None);
var byteCodeResult = byteCode!.ToString(Formatting.None).ToLowerInvariant().Replace("\"", "");
return (abiResult, byteCodeResult);
}
private static T Retry<T>(Func<T> fetch)
{
return Time.Retry(fetch, nameof(ContractsContainerInfoExtractor));
}
}
public class DeployedAddressesJson
{
public string Token_TestToken { get; set; } = string.Empty;
public string Verifier_Groth16Verifier { get; set; } = string.Empty;
public string Marketplace_Marketplace { get; set; } = string.Empty;
}
}

View File

@ -1,30 +0,0 @@
using CodexClient;
using Core;
using GethPlugin;
namespace CodexContractsPlugin
{
public static class CoreInterfaceExtensions
{
public static CodexContractsDeployment DeployCodexContracts(this CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo)
{
return Plugin(ci).DeployContracts(ci, gethNode, versionInfo);
}
public static ICodexContracts WrapCodexContractsDeployment(this CoreInterface ci, IGethNode gethNode, CodexContractsDeployment deployment)
{
return Plugin(ci).WrapDeploy(gethNode, deployment);
}
public static ICodexContracts StartCodexContracts(this CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo)
{
var deployment = DeployCodexContracts(ci, gethNode, versionInfo);
return WrapCodexContractsDeployment(ci, gethNode, deployment);
}
private static CodexContractsPlugin Plugin(CoreInterface ci)
{
return ci.GetPlugin<CodexContractsPlugin>();
}
}
}

View File

@ -1,115 +0,0 @@
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
using BlockchainUtils;
using Nethereum.Hex.HexConvertors.Extensions;
using Newtonsoft.Json;
using CodexClient;
using Utils;
namespace CodexContractsPlugin.Marketplace
{
public interface IHasBlock
{
BlockTimeEntry Block { get; set; }
}
public interface IHasRequestId
{
byte[] RequestId { get; set; }
}
public interface IHasBlockAndRequestId : IHasBlock, IHasRequestId
{
}
public interface IHasSlotIndex
{
ulong SlotIndex { get; set; }
}
public partial class Request
{
public EthAddress ClientAddress { get { return new EthAddress(Client); } }
}
public partial class StorageRequestedEventDTO : IHasBlockAndRequestId
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class RequestFulfilledEventDTO : IHasBlockAndRequestId
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class RequestCancelledEventDTO : IHasBlockAndRequestId
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class RequestFailedEventDTO : IHasBlockAndRequestId
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class SlotFilledEventDTO : IHasBlockAndRequestId, IHasSlotIndex
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
public EthAddress Host { get; set; }
public override string ToString()
{
return $"SlotFilled:[host:{Host} request:{RequestId.ToHex()} slotIndex:{SlotIndex}]";
}
}
public partial class SlotFreedEventDTO : IHasBlockAndRequestId, IHasSlotIndex
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class SlotReservationsFullEventDTO : IHasBlockAndRequestId, IHasSlotIndex
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class ProofSubmittedEventDTO : IHasBlock
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class ReserveSlotFunction : IHasBlockAndRequestId, IHasSlotIndex
{
[JsonIgnore]
public BlockTimeEntry Block { get; set; }
}
public partial class MarketplaceConfig : IMarketplaceConfigInput
{
public int MaxNumberOfSlashes
{
get
{
if (Collateral == null) return -1;
return Collateral.MaxNumberOfSlashes;
}
}
public TimeSpan PeriodDuration
{
get
{
if (Proofs == null) return TimeSpan.MinValue;
return TimeSpan.FromSeconds(this.Proofs.Period);
}
}
}
}
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

File diff suppressed because one or more lines are too long

View File

@ -1,14 +0,0 @@
This code was generated using the Nethereum code generator, here: http://playground.nethereum.com
1. Go to site -> Abi Code Gen.
1. Contract name = "Marketplace".
1. In container, get "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json".
1. Save only ABI section as new JSON. (top-level is a json array.)
1. From original JSON get byte code.
1. Put ABI JSON and byte code into site.
1. Generate.
1. From site generated code, copy `public partial class MarketplaceDeployment` and everything after it. (be considerate of namespace brackets!)
1. In Marketplace/Marketplace.cs, replace content of 'namespace CodexContractsPlugin.Marketplace'.

View File

@ -1,96 +0,0 @@
using Utils;
namespace CodexContractsPlugin
{
public class SelfUpdater
{
public void Update(string abi, string bytecode)
{
var filePath = GetMarketplaceFilePath();
var content = GenerateContent(abi, bytecode);
var contentLines = content.Split("\r\n");
var beginWith = new string[]
{
"using Nethereum.ABI.FunctionEncoding.Attributes;",
"using Nethereum.Contracts;",
"using System.Numerics;",
"",
"// Generated code, do not modify.",
"",
"#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.",
"namespace CodexContractsPlugin.Marketplace",
"{"
};
var endWith = new string[]
{
"}",
"#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable."
};
File.Delete(filePath);
File.WriteAllLines(filePath,
beginWith.Concat(
contentLines.Concat(
endWith))
);
throw new Exception("Oh no! CodexContracts were updated. Current build of CodexContractsPlugin is incompatible. " +
"But fear not! SelfUpdater.cs has automatically updated the plugin. Just rebuild and rerun and it should work. " +
"Just in case, manual update instructions are found here: 'CodexContractsPlugin/Marketplace/README.md'.");
}
private string GetMarketplaceFilePath()
{
var projectPluginDir = PluginPathUtils.ProjectPluginsDir;
var path = Path.Combine(projectPluginDir, "CodexContractsPlugin", "Marketplace", "Marketplace.cs");
if (!File.Exists(path)) throw new Exception("Marketplace file not found. Expected: " + path);
return path;
}
private string GenerateContent(string abi, string bytecode)
{
var deserializer = new Nethereum.Generators.Net.GeneratorModelABIDeserialiser();
var abiModel = deserializer.DeserialiseABI(abi);
var abiCtor = abiModel.Constructor;
var c = new Nethereum.Generators.CQS.ContractDeploymentCQSMessageGenerator(abiCtor, "namespace", bytecode, "Marketplace", Nethereum.Generators.Core.CodeGenLanguage.CSharp);
var lines = "";
lines += c.GenerateClass();
lines += "\r\n";
foreach (var eventAbi in abiModel.Events)
{
var d = new Nethereum.Generators.DTOs.EventDTOGenerator(eventAbi, "namespace", Nethereum.Generators.Core.CodeGenLanguage.CSharp);
lines += d.GenerateClass();
lines += "\r\n";
}
foreach (var errorAbi in abiModel.Errors)
{
var e = new Nethereum.Generators.DTOs.ErrorDTOGenerator(errorAbi, "namespace", Nethereum.Generators.Core.CodeGenLanguage.CSharp);
lines += e.GenerateClass();
lines += "\r\n";
}
foreach (var funcAbi in abiModel.Functions)
{
var f = new Nethereum.Generators.DTOs.FunctionOutputDTOGenerator(funcAbi, "namespace", Nethereum.Generators.Core.CodeGenLanguage.CSharp);
var ff = new Nethereum.Generators.CQS.FunctionCQSMessageGenerator(funcAbi, "namespace", "funcoutput", Nethereum.Generators.Core.CodeGenLanguage.CSharp);
lines += f.GenerateClass();
lines += "\r\n";
lines += ff.GenerateClass();
lines += "\r\n";
}
foreach (var structAbi in abiModel.Structs)
{
var g = new Nethereum.Generators.DTOs.StructTypeGenerator(structAbi, "namespace", Nethereum.Generators.Core.CodeGenLanguage.CSharp);
lines += g.GenerateClass();
lines += "\r\n";
}
return lines;
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -10,8 +10,8 @@ namespace CodexPlugin
public class ApiChecker
{
// <INSERT-OPENAPI-YAML-HASH>
private const string OpenApiYamlHash = "2F-9D-82-3C-F0-2F-D3-C9-72-C3-F2-6E-BD-C3-63-F5-67-62-D1-03-B6-60-75-31-22-DF-3F-63-A2-8D-AA-4B";
private const string OpenApiFilePath = "/codex/openapi.yaml";
private const string OpenApiYamlHash = "58-CA-45-23-0C-BD-26-65-BA-BB-AF-9F-09-04-9D-98-73-71-D1-3A-F2-B9-A6-A6-3B-73-38-CF-DA-CA-A2-3C";
private const string OpenApiFilePath = "/logosstorage/openapi.yaml";
private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK";
private const bool Disable = false;

View File

@ -45,7 +45,7 @@ namespace CodexPlugin
try
{
var dataDirVar = container.Recipe.EnvVars.Single(e => e.Name == "CODEX_DATA_DIR");
var dataDirVar = container.Recipe.EnvVars.Single(e => e.Name == "STORAGE_DATA_DIR");
var dataDir = dataDirVar.Value;
var workflow = tools.CreateWorkflow();
workflow.ExecuteCommand(container, "rm", "-Rfv", $"/codex/{dataDir}/repo");

View File

@ -1,4 +1,3 @@
// MARKETPLACE REMOVED: using GethPlugin;
using KubernetesWorkflow;
using KubernetesWorkflow.Recipe;
using Utils;
@ -36,16 +35,16 @@ namespace CodexPlugin
var config = startupConfig.Get<CodexStartupConfig>();
var apiPort = CreateApiPort(config, ApiPortTag);
AddEnvVar("CODEX_API_PORT", apiPort);
AddEnvVar("CODEX_API_BINDADDR", "0.0.0.0");
AddEnvVar("STORAGE_API_PORT", apiPort);
AddEnvVar("STORAGE_API_BINDADDR", "0.0.0.0");
var dataDir = $"datadir{ContainerNumber}";
AddEnvVar("CODEX_DATA_DIR", dataDir);
AddEnvVar("STORAGE_DATA_DIR", dataDir);
AddVolume($"codex/{dataDir}", GetVolumeCapacity(config));
var discPort = CreateDiscoveryPort(config);
AddEnvVar("CODEX_DISC_PORT", discPort);
AddEnvVar("CODEX_LOG_LEVEL", config.LogLevelWithTopics());
AddEnvVar("STORAGE_DISC_PORT", discPort);
AddEnvVar("STORAGE_LOG_LEVEL", config.LogLevelWithTopics());
if (config.PublicTestNet != null)
{
@ -60,88 +59,44 @@ namespace CodexPlugin
}
var listenPort = CreateListenPort(config);
AddEnvVar("CODEX_LISTEN_ADDRS", $"/ip4/0.0.0.0/tcp/{listenPort.Number}");
AddEnvVar("STORAGE_LISTEN_ADDRS", $"/ip4/0.0.0.0/tcp/{listenPort.Number}");
if (!string.IsNullOrEmpty(config.BootstrapSpr))
{
AddEnvVar("CODEX_BOOTSTRAP_NODE", config.BootstrapSpr);
AddEnvVar("STORAGE_BOOTSTRAP_NODE", config.BootstrapSpr);
}
if (config.StorageQuota != null)
{
AddEnvVar("CODEX_STORAGE_QUOTA", config.StorageQuota.SizeInBytes.ToString()!);
AddEnvVar("STORAGE_STORAGE_QUOTA", config.StorageQuota.SizeInBytes.ToString()!);
}
if (config.BlockTTL != null)
{
AddEnvVar("CODEX_BLOCK_TTL", config.BlockTTL.ToString()!);
AddEnvVar("STORAGE_BLOCK_TTL", config.BlockTTL.ToString()!);
}
if (config.BlockMaintenanceInterval != null)
{
AddEnvVar("CODEX_BLOCK_MI", Convert.ToInt32(config.BlockMaintenanceInterval.Value.TotalSeconds).ToString());
AddEnvVar("STORAGE_BLOCK_MI", Convert.ToInt32(config.BlockMaintenanceInterval.Value.TotalSeconds).ToString());
}
if (config.BlockMaintenanceNumber != null)
{
AddEnvVar("CODEX_BLOCK_MN", config.BlockMaintenanceNumber.ToString()!);
AddEnvVar("STORAGE_BLOCK_MN", config.BlockMaintenanceNumber.ToString()!);
}
if (config.MetricsEnabled)
{
var metricsPort = CreateApiPort(config, MetricsPortTag);
AddEnvVar("CODEX_METRICS", "true");
AddEnvVar("CODEX_METRICS_ADDRESS", "0.0.0.0");
AddEnvVar("CODEX_METRICS_PORT", metricsPort);
AddEnvVar("STORAGE_METRICS", "true");
AddEnvVar("STORAGE_METRICS_ADDRESS", "0.0.0.0");
AddEnvVar("STORAGE_METRICS_PORT", metricsPort);
AddPodAnnotation("prometheus.io/scrape", "true");
AddPodAnnotation("prometheus.io/port", metricsPort.Number.ToString());
}
if (config.SimulateProofFailures != null)
{
AddEnvVar("CODEX_SIMULATE_PROOF_FAILURES", config.SimulateProofFailures.ToString()!);
}
// MARKETPLACE REMOVED: MarketplaceConfig configuration block
// if (config.MarketplaceConfig != null)
// {
// var mconfig = config.MarketplaceConfig;
// var gethStart = mconfig.GethNode.StartResult;
// var wsAddress = gethStart.Container.GetInternalAddress(GethContainerRecipe.WsPortTag);
// var marketplaceAddress = mconfig.CodexContracts.Deployment.MarketplaceAddress;
//
// AddEnvVar("CODEX_ETH_PROVIDER", $"{wsAddress.Host.Replace("http://", "ws://")}:{wsAddress.Port}");
// AddEnvVar("CODEX_MARKETPLACE_ADDRESS", marketplaceAddress);
//
// var marketplaceSetup = config.MarketplaceConfig.MarketplaceSetup;
//
// // Custom scripting in the Codex test image will write this variable to a private-key file,
// // and pass the correct filename to Codex.
// var account = marketplaceSetup.EthAccountSetup.GetNew();
// AddEnvVar("ETH_PRIVATE_KEY", account.PrivateKey);
// Additional(account);
//
// SetCommandOverride(marketplaceSetup);
// if (marketplaceSetup.IsValidator)
// {
// AddEnvVar("CODEX_VALIDATOR", "true");
// }
// }
if (!string.IsNullOrEmpty(config.NameOverride))
{
AddEnvVar("CODEX_NODENAME", config.NameOverride);
}
}
// MARKETPLACE REMOVED: SetCommandOverride method
// private void SetCommandOverride(MarketplaceSetup ms)
// {
// if (ms.IsStorageNode)
// {
// OverrideCommand("bash", "/docker-entrypoint.sh", "codex", "persistence", "prover");
// }
// else
// {
// OverrideCommand("bash", "/docker-entrypoint.sh", "codex", "persistence");
// }
// }
private Port CreateApiPort(CodexStartupConfig config, string tag)
{
if (config.PublicTestNet == null) return AddExposedPort(tag);

View File

@ -1,6 +1,4 @@
using CodexClient;
// MARKETPLACE REMOVED: using CodexContractsPlugin;
// MARKETPLACE REMOVED: using GethPlugin;
using KubernetesWorkflow.Types;
namespace CodexPlugin
@ -14,7 +12,6 @@ namespace CodexPlugin
{
Id = id;
CodexInstances = codexInstances;
// MARKETPLACE REMOVED: GethDeployment and CodexContractsDeployment
PrometheusContainer = prometheusContainer;
DiscordBotContainer = discordBotContainer;
Metadata = metadata;
@ -22,8 +19,6 @@ namespace CodexPlugin
public string Id { get; }
public CodexInstance[] CodexInstances { get; }
// MARKETPLACE REMOVED: public GethDeployment GethDeployment { get; }
// MARKETPLACE REMOVED: public CodexContractsDeployment CodexContractsDeployment { get; }
public RunningPod? PrometheusContainer { get; }
public RunningPod? DiscordBotContainer { get; }
public DeploymentMetadata Metadata { get; }

View File

@ -74,24 +74,6 @@ namespace CodexPlugin
return codexWrapper.WrapCodexInstances(instances);
}
// MARKETPLACE REMOVED: WireUpMarketplace method
// 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.MarketplaceSetup.InitialEth);
// mconfig.CodexContracts.MintTestTokens(node, mconfig.MarketplaceSetup.InitialTestTokens);
//
// Log($"Send {mconfig.MarketplaceSetup.InitialEth} and " +
// $"minted {mconfig.MarketplaceSetup.InitialTestTokens} for " +
// $"{node.GetName()} (address: {node.EthAddress})");
// }
// }
public void AddCodexHooksProvider(ICodexHooksProvider hooksProvider)
{
if (hooksFactory.Providers.Contains(hooksProvider)) return;

View File

@ -1,6 +1,5 @@
using System.Net.Sockets;
using System.Net;
// MARKETPLACE REMOVED: using Nethereum.Util;
namespace CodexPlugin
{
@ -98,19 +97,6 @@ namespace CodexPlugin
//AddPodAnnotation("prometheus.io/port", metricsPort.Number.ToString());
}
if (config.SimulateProofFailures != null)
{
throw new Exception("Not supported");
//AddEnvVar("CODEX_SIMULATE_PROOF_FAILURES", config.SimulateProofFailures.ToString()!);
}
// MARKETPLACE REMOVED: MarketplaceConfig block
// if (config.MarketplaceConfig != null) { ... }
//if (!string.IsNullOrEmpty(config.NameOverride))
//{
// AddEnvVar("CODEX_NODENAME", config.NameOverride);
//}
return Create();
}

View File

@ -1,6 +1,4 @@
using CodexClient;
// MARKETPLACE REMOVED: using CodexContractsPlugin;
// MARKETPLACE REMOVED: using GethPlugin;
using KubernetesWorkflow;
using Utils;
@ -18,23 +16,9 @@ namespace CodexPlugin
ICodexSetup WithBlockMaintenanceInterval(TimeSpan duration);
ICodexSetup WithBlockMaintenanceNumber(int numberOfBlocks);
ICodexSetup EnableMetrics();
// MARKETPLACE REMOVED: ICodexSetup EnableMarketplace(IGethNode gethNode, ICodexContracts codexContracts, Action<IMarketplaceSetup> marketplaceSetup);
/// <summary>
/// Provides an invalid proof every N proofs
/// </summary>
ICodexSetup WithSimulateProofFailures(uint failEveryNProofs);
ICodexSetup AsPublicTestNet(CodexTestNetConfig testNetConfig);
}
// MARKETPLACE REMOVED: IMarketplaceSetup interface
// public interface IMarketplaceSetup
// {
// IMarketplaceSetup WithInitial(Ether eth, TestToken tokens);
// IMarketplaceSetup WithAccount(EthAccount account);
// IMarketplaceSetup AsStorageNode();
// IMarketplaceSetup AsValidator();
// }
public class CodexLogCustomTopics
{
public CodexLogCustomTopics(CodexLogLevel discV5, CodexLogLevel libp2p, CodexLogLevel blockExchange)
@ -128,22 +112,6 @@ namespace CodexPlugin
return this;
}
// MARKETPLACE REMOVED: EnableMarketplace implementation
// public ICodexSetup EnableMarketplace(IGethNode gethNode, ICodexContracts codexContracts, Action<IMarketplaceSetup> marketplaceSetup)
// {
// var ms = new MarketplaceSetup();
// marketplaceSetup(ms);
//
// MarketplaceConfig = new MarketplaceInitialConfig(ms, gethNode, codexContracts);
// return this;
// }
public ICodexSetup WithSimulateProofFailures(uint failEveryNProofs)
{
SimulateProofFailures = failEveryNProofs;
return this;
}
public ICodexSetup AsPublicTestNet(CodexTestNetConfig testNetConfig)
{
PublicTestNet = testNetConfig;
@ -162,87 +130,6 @@ namespace CodexPlugin
yield return $"LogLevel={LogLevelWithTopics()}";
if (BootstrapSpr != null) yield return $"BootstrapNode={BootstrapSpr}";
if (StorageQuota != null) yield return $"StorageQuota={StorageQuota}";
if (SimulateProofFailures != null) yield return $"SimulateProofFailures={SimulateProofFailures}";
// MARKETPLACE REMOVED: if (MarketplaceConfig != null) yield return $"MarketplaceSetup={MarketplaceConfig.MarketplaceSetup}";
}
}
// MARKETPLACE REMOVED: MarketplaceSetup class
// public class MarketplaceSetup : IMarketplaceSetup
// {
// public bool IsStorageNode { get; private set; }
// public bool IsValidator { get; private set; }
// public Ether InitialEth { get; private set; } = 0.Eth();
// public TestToken InitialTestTokens { get; private set; } = 0.Tst();
// public EthAccountSetup EthAccountSetup { get; } = new EthAccountSetup();
//
// public IMarketplaceSetup AsStorageNode()
// {
// IsStorageNode = true;
// return this;
// }
//
// public IMarketplaceSetup AsValidator()
// {
// IsValidator = true;
// return this;
// }
//
// public IMarketplaceSetup WithAccount(EthAccount account)
// {
// EthAccountSetup.Pin(account);
// return this;
// }
//
// public IMarketplaceSetup WithInitial(Ether eth, TestToken tokens)
// {
// InitialEth = eth;
// InitialTestTokens = tokens;
// return this;
// }
//
// public override string ToString()
// {
// var result = "[(clientNode)"; // When marketplace is enabled, being a clientNode is implicit.
// result += IsStorageNode ? "(storageNode)" : "()";
// result += IsValidator ? "(validator)" : "() ";
// result += $"Pinned address: '{EthAccountSetup}' ";
// result += $"{InitialEth} / {InitialTestTokens}";
// result += "] ";
// return result;
// }
// }
// MARKETPLACE REMOVED: EthAccountSetup class
// public class EthAccountSetup
// {
// private readonly List<EthAccount> accounts = new List<EthAccount>();
// private bool pinned = false;
//
// public void Pin(EthAccount account)
// {
// accounts.Add(account);
// pinned = true;
// }
//
// public EthAccount GetNew()
// {
// if (pinned) return accounts.Last();
//
// var a = EthAccountGenerator.GenerateNew();
// accounts.Add(a);
// return a;
// }
//
// public EthAccount[] GetAll()
// {
// return accounts.ToArray();
// }
//
// public override string ToString()
// {
// if (!accounts.Any()) return "NoEthAccounts";
// return string.Join(",", accounts.Select(a => a.ToString()).ToArray());
// }
// }
}

View File

@ -12,10 +12,8 @@ namespace CodexPlugin
public CodexLogCustomTopics? CustomTopics { get; set; } = new CodexLogCustomTopics(CodexLogLevel.Info, CodexLogLevel.Warn);
public ByteSize? StorageQuota { get; set; }
public bool MetricsEnabled { get; set; }
// MARKETPLACE REMOVED: public MarketplaceInitialConfig? MarketplaceConfig { get; set; }
public string? BootstrapSpr { get; set; }
public int? BlockTTL { get; set; }
public uint? SimulateProofFailures { get; set; }
public bool? EnableValidator { get; set; }
public TimeSpan? BlockMaintenanceInterval { get; set; }
public int? BlockMaintenanceNumber { get; set; }
@ -70,22 +68,12 @@ namespace CodexPlugin
"blockexcnetwork",
"blockexcnetworkpeer"
};
var contractClockTopics = new[]
{
"contracts",
"clock"
};
var jsonSerializeTopics = new[]
{
"serde",
"json",
"serialization"
};
var marketplaceInfraTopics = new[]
{
"JSONRPC-WS-CLIENT",
"JSONRPC-HTTP-CLIENT",
};
var alwaysIgnoreTopics = new []
{
@ -95,9 +83,7 @@ namespace CodexPlugin
level = $"{level};" +
$"{CustomTopics.DiscV5.ToString()!.ToLowerInvariant()}:{string.Join(",", discV5Topics)};" +
$"{CustomTopics.Libp2p.ToString()!.ToLowerInvariant()}:{string.Join(",", libp2pTopics)};" +
$"{CustomTopics.ContractClock.ToString().ToLowerInvariant()}:{string.Join(",", contractClockTopics)};" +
$"{CustomTopics.JsonSerialize.ToString().ToLowerInvariant()}:{string.Join(",", jsonSerializeTopics)};" +
$"{CustomTopics.MarketplaceInfra.ToString().ToLowerInvariant()}:{string.Join(",", marketplaceInfraTopics)};" +
$"{CodexLogLevel.Error.ToString()}:{string.Join(",", alwaysIgnoreTopics)}";
if (CustomTopics.BlockExchange != null)

View File

@ -30,7 +30,6 @@ namespace CodexPlugin
{
var rc = ci.DeployCodexNodes(number, setup);
var result = ci.WrapCodexContainers(rc);
// MARKETPLACE REMOVED: Plugin(ci).WireUpMarketplace(result, setup);
return result;
}

View File

@ -54,7 +54,7 @@ namespace CodexNetDeployer
var customImage = GenerateImageName();
Docker($"build", "-t", customImage, "-f", "./codex.Dockerfile",
"--build-arg=\"MAKE_PARALLEL=4\"",
"--build-arg=\"NIMFLAGS=-d:disableMarchNative -d:codex_enable_api_debug_peers=true -d:codex_enable_api_debug_fetch=true -d:codex_enable_simulated_proof_failures\"",
"--build-arg=\"NIMFLAGS=-d:disableMarchNative -d:storage_enable_api_debug_peers=true -d:storage_enable_api_debug_fetch=true\"",
"--build-arg=\"NAT_IP_AUTO=true\"",
"..");

View File

@ -1,20 +0,0 @@
// MARKETPLACE REMOVED: Entire file commented out
// using CodexContractsPlugin;
// using GethPlugin;
//
// namespace CodexPlugin
// {
// public class MarketplaceInitialConfig
// {
// public MarketplaceInitialConfig(MarketplaceSetup marketplaceSetup, IGethNode gethNode, ICodexContracts codexContracts)
// {
// MarketplaceSetup = marketplaceSetup;
// GethNode = gethNode;
// CodexContracts = codexContracts;
// }
//
// public MarketplaceSetup MarketplaceSetup { get; }
// public IGethNode GethNode { get; }
// public ICodexContracts CodexContracts { get; }
// }
// }

View File

@ -20,14 +20,13 @@ namespace CodexPlugin.OverwatchSupport
this.name = name;
}
public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount)
public void OnNodeStarting(DateTime startUtc, string image)
{
WriteCodexEvent(startUtc, e =>
{
e.NodeStarting = new NodeStartingEvent
{
Image = image,
EthAddress = ethAccount != null ? ethAccount.ToString() : ""
};
});
}
@ -108,40 +107,6 @@ namespace CodexPlugin.OverwatchSupport
});
}
public void OnStorageContractSubmitted(IStoragePurchaseContract storagePurchaseContract)
{
WriteCodexEvent(e =>
{
e.StorageContractSubmitted = new StorageContractSubmittedEvent
{
PurchaseId = storagePurchaseContract.PurchaseId,
PurchaseRequest = storagePurchaseContract.Purchase
};
});
}
public void OnStorageContractUpdated(StoragePurchase purchaseStatus)
{
WriteCodexEvent(e =>
{
e.StorageContractUpdated = new StorageContractUpdatedEvent
{
StoragePurchase = purchaseStatus
};
});
}
public void OnStorageAvailabilityCreated(StorageAvailability response)
{
WriteCodexEvent(e =>
{
e.StorageAvailabilityCreated = new StorageAvailabilityCreatedEvent
{
StorageAvailability = response
};
});
}
private void WriteCodexEvent(Action<OverwatchCodexEvent> action)
{
WriteCodexEvent(DateTime.UtcNow, action);

View File

@ -25,9 +25,6 @@ namespace CodexPlugin.OverwatchSupport
public BlockReceivedEvent? BlockReceived { get; set; }
public PeerDialSuccessfulEvent? DialSuccessful { get; set; }
public PeerDroppedEvent? PeerDropped { get; set; }
public StorageContractSubmittedEvent? StorageContractSubmitted { get; set; }
public StorageContractUpdatedEvent? StorageContractUpdated { get; set; }
public StorageAvailabilityCreatedEvent? StorageAvailabilityCreated { get; set; }
public void Write(DateTime utc, ITranscriptWriter writer)
{
@ -72,7 +69,6 @@ namespace CodexPlugin.OverwatchSupport
public class NodeStartingEvent
{
public string Image { get; set; } = string.Empty;
public string EthAddress { get; set; } = string.Empty;
}
[Serializable]
@ -119,25 +115,6 @@ namespace CodexPlugin.OverwatchSupport
public long ByteSize { get; set; }
}
[Serializable]
public class StorageAvailabilityCreatedEvent
{
public StorageAvailability StorageAvailability { get; set; } = null!;
}
[Serializable]
public class StorageContractUpdatedEvent
{
public StoragePurchase StoragePurchase { get; set; } = null!;
}
[Serializable]
public class StorageContractSubmittedEvent
{
public string PurchaseId { get; set; } = string.Empty;
public StoragePurchaseRequest PurchaseRequest { get; set; } = null!;
}
#endregion
#region Codex Generated Events

View File

@ -1,29 +0,0 @@
using BlockchainUtils;
using Core;
namespace GethPlugin
{
public static class CoreInterfaceExtensions
{
public static GethDeployment DeployGeth(this CoreInterface ci, Action<IGethSetup> setup)
{
return Plugin(ci).DeployGeth(setup);
}
public static IGethNode WrapGethDeployment(this CoreInterface ci, GethDeployment deployment, BlockCache blockCache)
{
return Plugin(ci).WrapGethDeployment(deployment, blockCache);
}
public static IGethNode StartGethNode(this CoreInterface ci, BlockCache blockCache, Action<IGethSetup> setup)
{
var deploy = DeployGeth(ci, setup);
return WrapGethDeployment(ci, deploy, blockCache);
}
private static GethPlugin Plugin(CoreInterface ci)
{
return ci.GetPlugin<GethPlugin>();
}
}
}

View File

@ -1,19 +0,0 @@
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Web3.Accounts;
using Utils;
namespace GethPlugin
{
public static class EthAccountGenerator
{
public static EthAccount GenerateNew()
{
var ecKey = Nethereum.Signer.EthECKey.GenerateKey();
var privateKey = ecKey.GetPrivateKeyAsBytes().ToHex();
var account = new Account(privateKey);
var ethAddress = new EthAddress(account.Address);
return new EthAccount(ethAddress, account.PrivateKey);
}
}
}

View File

@ -1,24 +0,0 @@
namespace GethPlugin
{
public class GethAccount
{
public GethAccount(string account, string privateKey)
{
Account = account;
PrivateKey = privateKey;
}
public string Account { get; }
public string PrivateKey { get; }
}
public class AllGethAccounts
{
public GethAccount[] Accounts { get; }
public AllGethAccounts(GethAccount[] accounts)
{
Accounts = accounts;
}
}
}

View File

@ -1,118 +0,0 @@
using KubernetesWorkflow;
using KubernetesWorkflow.Types;
using Logging;
using Utils;
namespace GethPlugin
{
public class GethContainerInfoExtractor
{
private readonly ILog log;
private readonly IStartupWorkflow workflow;
private readonly RunningContainer container;
public GethContainerInfoExtractor(ILog log, IStartupWorkflow workflow, RunningContainer container)
{
this.log = log;
this.workflow = workflow;
this.container = container;
}
public AllGethAccounts ExtractAccounts()
{
log.Debug();
var accountsCsv = Retry(() => FetchAccountsCsv());
if (string.IsNullOrEmpty(accountsCsv)) throw new InvalidOperationException("Unable to fetch accounts.csv for geth node. Test infra failure.");
var lines = accountsCsv.Split('\n');
return new AllGethAccounts(lines.Select(ParseLineToAccount).ToArray());
}
public string ExtractPubKey()
{
log.Debug();
var pubKey = Retry(FetchPubKey);
if (string.IsNullOrEmpty(pubKey)) throw new InvalidOperationException("Unable to fetch enode from geth node. Test infra failure.");
return pubKey;
}
private string FetchAccountsCsv()
{
return workflow.ExecuteCommand(container, "cat", GethContainerRecipe.AccountsFilename);
}
private string FetchPubKey()
{
var enodeFinder = new PubKeyFinder(s => log.Debug(s));
workflow.DownloadContainerLog(container, enodeFinder, null);
return enodeFinder.GetPubKey();
}
private GethAccount ParseLineToAccount(string l)
{
var tokens = l.Replace("\r", "").Split(',');
if (tokens.Length != 2) throw new InvalidOperationException();
var account = tokens[0];
var privateKey = tokens[1];
return new GethAccount(account, privateKey);
}
private static string Retry(Func<string> fetch)
{
// This class is the first moment where we interact with our new geth container.
// K8s might be moving pods and/or setting up new VMs.
// So we apply a generous retry timeout.
var retry = new Retry(nameof(GethContainerInfoExtractor),
maxTimeout: TimeSpan.FromMinutes(15.0),
sleepAfterFail: TimeSpan.FromSeconds(20.0),
onFail: f => { },
failFast: false);
return retry.Run(fetch);
}
}
public class PubKeyFinder : LogHandler, ILogHandler
{
private const string openTag = "self=enode://";
private const string openTagQuote = "self=\"enode://";
private readonly Action<string> debug;
private string pubKey = string.Empty;
public PubKeyFinder(Action<string> debug)
{
this.debug = debug;
debug($"Looking for '{openTag}' in container logs...");
}
public string GetPubKey()
{
if (string.IsNullOrEmpty(pubKey)) throw new Exception("Not found yet exception.");
return pubKey;
}
protected override void ProcessLine(string line)
{
debug(line);
if (line.Contains(openTag))
{
ExtractPubKey(openTag, line);
}
else if (line.Contains(openTagQuote))
{
ExtractPubKey(openTagQuote, line);
}
}
private void ExtractPubKey(string tag, string line)
{
var openIndex = line.IndexOf(tag) + tag.Length;
var closeIndex = line.IndexOf("@");
pubKey = line.Substring(
startIndex: openIndex,
length: closeIndex - openIndex);
}
}
}

View File

@ -1,108 +0,0 @@
using KubernetesWorkflow;
using KubernetesWorkflow.Recipe;
namespace GethPlugin
{
public class GethContainerRecipe : ContainerRecipeFactory
{
public static string DockerImage { get; } = "codexstorage/dist-tests-geth:latest";
public static TimeSpan BlockInterval { get; } = TimeSpan.FromSeconds(1.0);
private const string defaultArgs = "--ipcdisable --syncmode full";
public const string HttpPortTag = "http_port";
public const string DiscoveryPortTag = "disc_port";
public const string ListenPortTag = "listen_port";
public const string WsPortTag = "ws_port";
public const string AuthRpcPortTag = "auth_rpc_port";
public const string AccountsFilename = "accounts.csv";
public override string AppName => "geth";
public override string Image => DockerImage;
protected override void Initialize(StartupConfig startupConfig)
{
var config = startupConfig.Get<GethStartupConfig>();
var args = CreateArgs(config);
SetSchedulingAffinity(notIn: "false");
SetSystemCriticalPriority();
AddEnvVar("GETH_ARGS", args);
}
private string CreateArgs(GethStartupConfig config)
{
if (config.IsMiner)
{
AddEnvVar("ENABLE_MINER", "1");
UnlockAccounts(0, 1);
}
var httpPort = CreateApiPort(config, tag: HttpPortTag);
var discovery = CreateDiscoveryPort(config);
var listen = CreateListenPort(config);
var authRpc = CreateP2pPort(config, tag: AuthRpcPortTag);
var wsPort = CreateP2pPort(config, tag: WsPortTag);
var args = $"--http.addr 0.0.0.0 --http.port {httpPort.Number} --port {listen.Number} --discovery.port {discovery.Number} {defaultArgs}";
if (config.BootstrapNode != null)
{
var bootPubKey = config.BootstrapNode.PublicKey;
var bootIp = config.BootstrapNode.IpAddress;
var bootPort = config.BootstrapNode.Port;
var bootstrapArg = $" --bootnodes enode://{bootPubKey}@{bootIp}:{bootPort}";
args += bootstrapArg;
}
if (config.IsPublicTestNet != null)
{
AddEnvVar("NAT_PUBLIC_IP_AUTO", PublicIpService.Address);
}
return args + $" --authrpc.port {authRpc.Number} --ws --ws.addr 0.0.0.0 --ws.port {wsPort.Number}";
}
private void UnlockAccounts(int startIndex, int numberOfAccounts)
{
if (startIndex < 0) throw new ArgumentException();
if (numberOfAccounts < 0) throw new ArgumentException();
if (startIndex + numberOfAccounts > 1000) throw new ArgumentException("Out of accounts!");
AddEnvVar("UNLOCK_START_INDEX", startIndex.ToString());
AddEnvVar("UNLOCK_NUMBER", numberOfAccounts.ToString());
}
private Port CreateDiscoveryPort(GethStartupConfig config)
{
if (config.IsPublicTestNet == null) return AddInternalPort(DiscoveryPortTag);
return AddExposedPort(config.IsPublicTestNet.DiscoveryPort, DiscoveryPortTag, PortProtocol.UDP);
}
private Port CreateListenPort(GethStartupConfig config)
{
if (config.IsPublicTestNet == null) return AddInternalPort(ListenPortTag);
return AddExposedPort(config.IsPublicTestNet.ListenPort, ListenPortTag);
}
private Port CreateP2pPort(GethStartupConfig config, string tag)
{
if (config.IsPublicTestNet != null)
{
return AddExposedPort(tag);
}
return AddInternalPort(tag);
}
private Port CreateApiPort(GethStartupConfig config, string tag)
{
if (config.IsPublicTestNet != null)
{
return AddInternalPort(tag);
}
return AddExposedPort(tag);
}
}
}

View File

@ -1,29 +0,0 @@
using Core;
using KubernetesWorkflow.Recipe;
using KubernetesWorkflow.Types;
using Newtonsoft.Json;
namespace GethPlugin
{
public class GethDeployment : IHasContainer
{
public GethDeployment(RunningPod pod, Port discoveryPort, Port httpPort, Port wsPort, GethAccount account, string pubKey)
{
Pod = pod;
DiscoveryPort = discoveryPort;
HttpPort = httpPort;
WsPort = wsPort;
Account = account;
PubKey = pubKey;
}
public RunningPod Pod { get; }
[JsonIgnore]
public RunningContainer Container { get { return Pod.Containers.Single(); } }
public Port DiscoveryPort { get; }
public Port HttpPort { get; }
public Port WsPort { get; }
public GethAccount Account { get; }
public string PubKey { get; }
}
}

View File

@ -1,261 +0,0 @@
using BlockchainUtils;
using Core;
using KubernetesWorkflow.Types;
using Logging;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.BlockchainProcessing.BlockStorage.Entities.Mapping;
using Nethereum.Contracts;
using Nethereum.RPC.Eth.DTOs;
using NethereumWorkflow;
using Utils;
namespace GethPlugin
{
public interface IGethNode : IHasContainer
{
GethDeployment StartResult { get; }
EthAddress CurrentAddress { get; }
Ether GetEthBalance();
Ether GetEthBalance(IHasEthAddress address);
Ether GetEthBalance(EthAddress address);
string SendEth(IHasEthAddress account, Ether eth);
string SendEth(EthAddress account, Ether eth);
TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
TResult Call<TFunction, TResult>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new();
void Call<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
void Call<TFunction>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new();
string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
Transaction GetTransaction(string transactionHash);
decimal? GetSyncedBlockNumber();
bool IsContractAvailable(string abi, string contractAddress);
GethBootstrapNode GetBootstrapRecord();
List<EventLog<TEvent>> GetEvents<TEvent>(string address, BlockInterval blockRange) where TEvent : IEventDTO, new();
List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new();
BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange);
BlockTimeEntry GetBlockForNumber(ulong number);
void IterateTransactions(BlockInterval blockRange, Action<Transaction, ulong, DateTime> action);
void IterateFunctionCalls<TFunc>(BlockInterval blockInterval, Action<BlockTimeEntry, TFunc> onCall) where TFunc : FunctionMessage, new();
IGethNode WithDifferentAccount(EthAccount account);
}
public class DeploymentGethNode : BaseGethNode, IGethNode
{
private readonly ILog log;
private readonly BlockCache blockCache;
public DeploymentGethNode(ILog log, BlockCache blockCache, GethDeployment startResult)
{
this.log = log;
this.blockCache = blockCache;
StartResult = startResult;
CurrentAddress = new EthAddress(startResult.Account.Account);
}
public GethDeployment StartResult { get; }
public RunningContainer Container => StartResult.Container;
public EthAddress CurrentAddress { get; }
public GethBootstrapNode GetBootstrapRecord()
{
var address = StartResult.Container.GetInternalAddress(GethContainerRecipe.ListenPortTag);
return new GethBootstrapNode(
publicKey: StartResult.PubKey,
ipAddress: address.Host.Replace("http://", ""),
port: address.Port
);
}
protected override NethereumInteraction StartInteraction()
{
var address = StartResult.Container.GetAddress(GethContainerRecipe.HttpPortTag);
var account = StartResult.Account;
var creator = new NethereumInteractionCreator(log, blockCache, address.Host, address.Port, account.PrivateKey);
return creator.CreateWorkflow();
}
public IGethNode WithDifferentAccount(EthAccount account)
{
return new DeploymentGethNode(log, blockCache,
new GethDeployment(
StartResult.Pod,
StartResult.DiscoveryPort,
StartResult.HttpPort,
StartResult.WsPort,
new GethAccount(
account.EthAddress.Address,
account.PrivateKey
),
account.PrivateKey));
}
}
public class CustomGethNode : BaseGethNode, IGethNode
{
private readonly ILog log;
private readonly BlockCache blockCache;
private readonly string gethHost;
private readonly int gethPort;
private readonly string privateKey;
public GethDeployment StartResult => throw new NotImplementedException();
public RunningContainer Container => throw new NotImplementedException();
public EthAddress CurrentAddress { get; }
public CustomGethNode(ILog log, BlockCache blockCache, string gethHost, int gethPort, string privateKey)
{
this.log = log;
this.blockCache = blockCache;
this.gethHost = gethHost;
this.gethPort = gethPort;
this.privateKey = privateKey;
var creator = new NethereumInteractionCreator(log, blockCache, gethHost, gethPort, privateKey);
CurrentAddress = creator.GetEthAddress();
}
public GethBootstrapNode GetBootstrapRecord()
{
throw new NotImplementedException();
}
public IGethNode WithDifferentAccount(EthAccount account)
{
return new CustomGethNode(log, blockCache, gethHost, gethPort, account.PrivateKey);
}
protected override NethereumInteraction StartInteraction()
{
var creator = new NethereumInteractionCreator(log, blockCache, gethHost, gethPort, privateKey);
return creator.CreateWorkflow();
}
}
public abstract class BaseGethNode
{
public Ether GetEthBalance()
{
return StartInteraction().GetEthBalance().Eth();
}
public Ether GetEthBalance(IHasEthAddress owner)
{
return GetEthBalance(owner.EthAddress);
}
public Ether GetEthBalance(EthAddress address)
{
return StartInteraction().GetEthBalance(address.Address).Wei();
}
public string SendEth(IHasEthAddress owner, Ether eth)
{
return SendEth(owner.EthAddress, eth);
}
public string SendEth(EthAddress account, Ether eth)
{
return StartInteraction().SendEth(account.Address, eth);
}
public TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
{
return StartInteraction().Call<TFunction, TResult>(contractAddress, function);
}
public TResult Call<TFunction, TResult>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
{
return StartInteraction().Call<TFunction, TResult>(contractAddress, function, blockNumber);
}
public void Call<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
{
StartInteraction().Call(contractAddress, function);
}
public void Call<TFunction>(string contractAddress, TFunction function, ulong blockNumber) where TFunction : FunctionMessage, new()
{
StartInteraction().Call(contractAddress, function, blockNumber);
}
public string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
{
return StartInteraction().SendTransaction(contractAddress, function);
}
public Transaction GetTransaction(string transactionHash)
{
return StartInteraction().GetTransaction(transactionHash);
}
public decimal? GetSyncedBlockNumber()
{
return StartInteraction().GetSyncedBlockNumber();
}
public bool IsContractAvailable(string abi, string contractAddress)
{
return StartInteraction().IsContractAvailable(abi, contractAddress);
}
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, BlockInterval blockRange) where TEvent : IEventDTO, new()
{
return StartInteraction().GetEvents<TEvent>(address, blockRange);
}
public List<EventLog<TEvent>> GetEvents<TEvent>(string address, TimeRange timeRange) where TEvent : IEventDTO, new()
{
return StartInteraction().GetEvents<TEvent>(address, ConvertTimeRangeToBlockRange(timeRange));
}
public BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange)
{
return StartInteraction().ConvertTimeRangeToBlockRange(timeRange);
}
public BlockTimeEntry GetBlockForNumber(ulong number)
{
return StartInteraction().GetBlockForNumber(number);
}
public BlockWithTransactions GetBlk(ulong number)
{
return StartInteraction().GetBlockWithTransactions(number);
}
public void IterateTransactions(BlockInterval blockRange, Action<Transaction, ulong, DateTime> action)
{
var i = StartInteraction();
for (var blkI = blockRange.From; blkI <= blockRange.To; blkI++)
{
var blk = i.GetBlockWithTransactions(blkI);
var blkUtc = DateTimeOffset.FromUnixTimeSeconds(blk.Timestamp.ToLong()).UtcDateTime;
foreach (var t in blk.Transactions)
{
action(t, blkI, blkUtc);
}
}
}
public void IterateFunctionCalls<TFunc>(BlockInterval blockRange, Action<BlockTimeEntry, TFunc> onCall) where TFunc : FunctionMessage, new()
{
IterateTransactions(blockRange, (t, blkI, blkUtc) =>
{
if (t.IsTransactionForFunctionMessage<TFunc>())
{
var func = t.DecodeTransactionToFunctionMessage<TFunc>();
if (func != null)
{
var b = GetBlockForNumber(blkI);
onCall(b, func);
}
}
});
}
protected abstract NethereumInteraction StartInteraction();
}
}

View File

@ -1,50 +0,0 @@
using BlockchainUtils;
using Core;
namespace GethPlugin
{
public class GethPlugin : IProjectPlugin, IHasLogPrefix, IHasMetadata
{
private readonly GethStarter starter;
private readonly IPluginTools tools;
public GethPlugin(IPluginTools tools)
{
starter = new GethStarter(tools);
this.tools = tools;
}
public string LogPrefix => "(Geth) ";
public void Awake(IPluginAccess access)
{
}
public void Announce()
{
tools.GetLog().Log($"Loaded Geth plugin.");
}
public void AddMetadata(IAddMetadata metadata)
{
metadata.Add("gethid", GethContainerRecipe.DockerImage);
}
public void Decommission()
{
}
public GethDeployment DeployGeth(Action<IGethSetup> setup)
{
var startupConfig = new GethStartupConfig();
setup(startupConfig);
return starter.StartGeth(startupConfig);
}
public IGethNode WrapGethDeployment(GethDeployment startResult, BlockCache blockCache)
{
startResult = SerializeGate.Gate(startResult);
return starter.WrapGethContainer(startResult, blockCache);
}
}
}

View File

@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Framework\Core\Core.csproj" />
<ProjectReference Include="..\..\Framework\KubernetesWorkflow\KubernetesWorkflow.csproj" />
<ProjectReference Include="..\..\Framework\NethereumWorkflow\NethereumWorkflow.csproj" />
</ItemGroup>
</Project>

View File

@ -1,61 +0,0 @@
using BlockchainUtils;
using Core;
using KubernetesWorkflow;
using Logging;
namespace GethPlugin
{
public class GethStarter
{
private readonly IPluginTools tools;
private readonly ILog log;
public GethStarter(IPluginTools tools)
{
this.tools = tools;
log = new LogPrefixer(tools.GetLog(), $"({nameof(GethStarter)}) ");
}
public GethDeployment StartGeth(GethStartupConfig gethStartupConfig)
{
Log("Starting Geth node...");
var startupConfig = new StartupConfig();
startupConfig.Add(gethStartupConfig);
startupConfig.NameOverride = gethStartupConfig.NameOverride;
var workflow = tools.CreateWorkflow();
var containers = workflow.Start(1, new GethContainerRecipe(), startupConfig).WaitForOnline();
if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure.");
var container = containers.Containers[0];
var extractor = new GethContainerInfoExtractor(log, workflow, container);
var account = extractor.ExtractAccounts().Accounts.First();
var pubKey = extractor.ExtractPubKey();
var discoveryPort = container.Recipe.GetPortByTag(GethContainerRecipe.DiscoveryPortTag);
if (discoveryPort == null) throw new Exception("Expected discovery port to be created.");
var httpPort = container.Recipe.GetPortByTag(GethContainerRecipe.HttpPortTag);
if (httpPort == null) throw new Exception("Expected http port to be created.");
var wsPort = container.Recipe.GetPortByTag(GethContainerRecipe.WsPortTag);
if (wsPort == null) throw new Exception("Expected ws port to be created.");
Log($"Geth node started.");
return new GethDeployment(containers, discoveryPort, httpPort, wsPort, account, pubKey);
}
public IGethNode WrapGethContainer(GethDeployment startResult, BlockCache blockCache)
{
startResult = SerializeGate.Gate(startResult);
var node = new DeploymentGethNode(tools.GetLog(), blockCache, startResult);
Log($"EthAddress: {node.CurrentAddress}");
return node;
}
private void Log(string msg)
{
log.Log(msg);
}
}
}

View File

@ -1,74 +0,0 @@
namespace GethPlugin
{
public interface IGethSetup
{
IGethSetup IsMiner();
IGethSetup WithBootstrapNode(IGethNode node);
IGethSetup WithBootstrapNode(GethBootstrapNode node);
IGethSetup WithName(string name);
IGethSetup AsPublicTestNet(GethTestNetConfig gethTestNetConfig);
}
public class GethStartupConfig : IGethSetup
{
public bool IsMiner { get; private set; }
public GethBootstrapNode? BootstrapNode { get; private set; }
public string? NameOverride { get; private set; }
public GethTestNetConfig? IsPublicTestNet { get; private set; }
public IGethSetup WithBootstrapNode(IGethNode node)
{
return WithBootstrapNode(node.GetBootstrapRecord());
}
public IGethSetup WithBootstrapNode(GethBootstrapNode node)
{
BootstrapNode = node;
return this;
}
public IGethSetup WithName(string name)
{
NameOverride = name;
return this;
}
IGethSetup IGethSetup.IsMiner()
{
IsMiner = true;
return this;
}
public IGethSetup AsPublicTestNet(GethTestNetConfig gethTestNetConfig)
{
IsPublicTestNet = gethTestNetConfig;
return this;
}
}
public class GethTestNetConfig
{
public GethTestNetConfig(int discoveryPort, int listenPort)
{
DiscoveryPort = discoveryPort;
ListenPort = listenPort;
}
public int DiscoveryPort { get; }
public int ListenPort { get; }
}
public class GethBootstrapNode
{
public GethBootstrapNode(string publicKey, string ipAddress, int port)
{
PublicKey = publicKey;
IpAddress = ipAddress;
Port = port;
}
public string PublicKey { get; }
public string IpAddress { get; }
public int Port { get; }
}
}

View File

@ -8,8 +8,6 @@ namespace ContinuousTests
public EntryPointFactory()
{
ProjectPlugin.Load<CodexPlugin.CodexPlugin>();
// MARKETPLACE REMOVED: ProjectPlugin.Load<CodexContractsPlugin.CodexContractsPlugin>();
// MARKETPLACE REMOVED: ProjectPlugin.Load<GethPlugin.GethPlugin>();
ProjectPlugin.Load<MetricsPlugin.MetricsPlugin>();
}

View File

@ -1,5 +1,3 @@
/* MARKETPLACE REMOVED
using CodexContractsPlugin;
using CodexPlugin;
using CodexTests;
using NUnit.Framework;
@ -50,101 +48,5 @@ namespace CodexReleaseTests.DataTests
Assert.That(cleanupSpace.QuotaUsedBytes, Is.EqualTo(startSpace.QuotaUsedBytes));
Assert.That(cleanupSpace.FreeBytes, Is.EqualTo(startSpace.FreeBytes));
}
[Test]
public void DeletesExpiredDataUsedByStorageRequests()
{
var fileSize = 3.MB();
var bootstrapNode = StartCodex();
var geth = StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth, bootstrapNode.Version);
var node = StartCodex(s => WithFastBlockExpiry(s)
.EnableMarketplace(geth, contracts, m => m.WithInitial(100.Eth(), 100.Tst()))
);
var startSpace = node.Space();
Assert.That(startSpace.QuotaUsedBytes, Is.EqualTo(0));
var cid = node.UploadFile(GenerateTestFile(fileSize));
var purchase = node.Marketplace.RequestStorage(new CodexClient.StoragePurchaseRequest(cid)
{
Duration = TimeSpan.FromHours(1.0),
Expiry = blockTtl,
MinRequiredNumberOfNodes = 3,
NodeFailureTolerance = 1,
PricePerBytePerSecond = 1000.TstWei(),
ProofProbability = 20,
CollateralPerByte = 100.TstWei()
});
var usedSpace = node.Space();
var usedFiles = node.LocalFiles();
Assert.That(usedSpace.QuotaUsedBytes, Is.GreaterThanOrEqualTo(fileSize.SizeInBytes));
Assert.That(usedSpace.FreeBytes, Is.LessThanOrEqualTo(startSpace.FreeBytes - fileSize.SizeInBytes));
Assert.That(usedFiles.Content.Length, Is.EqualTo(2));
Thread.Sleep(blockTtl * 2);
var cleanupSpace = node.Space();
var cleanupFiles = node.LocalFiles();
Assert.That(cleanupSpace.QuotaUsedBytes, Is.LessThan(usedSpace.QuotaUsedBytes));
Assert.That(cleanupSpace.FreeBytes, Is.GreaterThan(usedSpace.FreeBytes));
Assert.That(cleanupFiles.Content.Length, Is.EqualTo(0));
Assert.That(cleanupSpace.QuotaUsedBytes, Is.EqualTo(startSpace.QuotaUsedBytes));
Assert.That(cleanupSpace.FreeBytes, Is.EqualTo(startSpace.FreeBytes));
}
[Test]
[Ignore("Issue not fixed. Ticket: https://github.com/codex-storage/nim-codex/issues/1291")]
public void StorageRequestsKeepManifests()
{
var bootstrapNode = StartCodex(s => s.WithName("Bootstrap"));
var geth = StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth, bootstrapNode.Version);
var client = StartCodex(s => WithFastBlockExpiry(s)
.WithName("client")
.WithBootstrapNode(bootstrapNode)
.EnableMarketplace(geth, contracts, m => m.WithInitial(100.Eth(), 100.Tst()))
);
var hosts = StartCodex(3, s => WithFastBlockExpiry(s)
.WithName("host")
.WithBootstrapNode(bootstrapNode)
.EnableMarketplace(geth, contracts, m => m.AsStorageNode().WithInitial(100.Eth(), 100.Tst()))
);
foreach (var host in hosts) host.Marketplace.MakeStorageAvailable(new CodexClient.CreateStorageAvailability(
totalSpace: 2.GB(),
maxDuration: TimeSpan.FromDays(2.0),
minPricePerBytePerSecond: 1.TstWei(),
totalCollateral: 10.Tst()));
var uploadCid = client.UploadFile(GenerateTestFile(5.MB()));
var request = client.Marketplace.RequestStorage(new CodexClient.StoragePurchaseRequest(uploadCid)
{
CollateralPerByte = 1.TstWei(),
Duration = TimeSpan.FromDays(1.0),
Expiry = TimeSpan.FromHours(1.0),
MinRequiredNumberOfNodes = 3,
NodeFailureTolerance = 1,
PricePerBytePerSecond = 10.TstWei(),
ProofProbability = 99999
});
request.WaitForStorageContractSubmitted();
request.WaitForStorageContractStarted();
var storeCid = request.ContentId;
var clientManifest = client.DownloadManifestOnly(storeCid);
Assert.That(clientManifest.Manifest.Protected, Is.True);
client.Stop(waitTillStopped: true);
Thread.Sleep(blockTtl * 2.0);
var checker = StartCodex(s => s.WithName("checker").WithBootstrapNode(bootstrapNode));
var manifest = checker.DownloadManifestOnly(storeCid);
Assert.That(manifest.Manifest.Protected, Is.True);
}
}
}
*/

View File

@ -1,96 +0,0 @@
/* MARKETPLACE REMOVED
using CodexReleaseTests.Utils;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.DataTests
{
public class DecodeTest : MarketplaceAutoBootstrapDistTest
{
protected override int NumberOfHosts => 0;
protected override int NumberOfClients => 2;
protected override ByteSize HostAvailabilitySize => 0.Bytes();
protected override TimeSpan HostAvailabilityMaxDuration => TimeSpan.FromSeconds(0.0);
[Test]
public void DecodeDataset()
{
var clients = StartClients();
var file = GenerateTestFile(10.MB());
var bCid = clients[0].UploadFile(file);
var request = clients[0].Marketplace.RequestStorage(new CodexClient.StoragePurchaseRequest(bCid)
{
Expiry = TimeSpan.FromMinutes(5.0),
Duration = TimeSpan.FromMinutes(100.0),
CollateralPerByte = 100.Tst(),
MinRequiredNumberOfNodes = 6,
NodeFailureTolerance = 3,
PricePerBytePerSecond = 100.Tst(),
ProofProbability = 20
});
var eCid = request.ContentId;
Assert.That(bCid.Id, Is.Not.EqualTo(eCid.Id));
var basic = clients[0].DownloadManifestOnly(bCid);
var encoded = clients[0].DownloadManifestOnly(eCid);
Assert.That(basic.Manifest.Protected, Is.False);
Assert.That(encoded.Manifest.Protected, Is.True);
var decoded = clients[1].DownloadContent(eCid);
file.AssertIsEqual(decoded);
}
[Test]
[Ignore("Crashes node attempting encoding. Issue: https://github.com/codex-storage/nim-codex/issues/1185")]
public void PartiallyDeletedDatasets()
{
var clients = StartClients(s => s
.WithBlockMaintenanceNumber(1)
.WithBlockMaintenanceInterval(TimeSpan.FromSeconds(10.0))
.WithBlockTTL(TimeSpan.FromSeconds(30.0)));
var file = GenerateTestFile(2.MB());
var bCid = clients[0].UploadFile(file);
var space = clients[0].Space();
var update = space;
while (space.QuotaUsedBytes == update.QuotaUsedBytes)
{
Thread.Sleep(TimeSpan.FromSeconds(3.0));
update = clients[0].Space();
}
Assert.That(update.QuotaUsedBytes, Is.LessThan(space.QuotaUsedBytes));
// The dataset is partially deleted.
// What happens when we request storage for it?
var request = clients[0].Marketplace.RequestStorage(new CodexClient.StoragePurchaseRequest(bCid)
{
Expiry = TimeSpan.FromMinutes(5.0),
Duration = TimeSpan.FromMinutes(100.0),
CollateralPerByte = 100.Tst(),
MinRequiredNumberOfNodes = 6,
NodeFailureTolerance = 3,
PricePerBytePerSecond = 100.Tst(),
ProofProbability = 20
});
var eCid = request.ContentId;
Assert.That(bCid.Id, Is.Not.EqualTo(eCid.Id));
var basic = clients[0].DownloadManifestOnly(bCid);
var encoded = clients[0].DownloadManifestOnly(eCid);
Assert.That(basic.Manifest.Protected, Is.False);
Assert.That(encoded.Manifest.Protected, Is.True);
var decoded = clients[1].DownloadContent(eCid);
file.AssertIsEqual(decoded);
}
}
}
*/

View File

@ -39,7 +39,7 @@ namespace CodexReleaseTests.DataTests
private Process StartCurlUploadProcess(ICodexNode node, TrackedFile file)
{
var apiAddress = node.GetApiEndpoint();
var codexUrl = $"{apiAddress}/api/codex/v1/data";
var codexUrl = $"{apiAddress}/api/storage/v1/data";
var filePath = file.Filename;
return Process.Start("curl", $"-X POST {codexUrl} -H \"Content-Type: application/octet-stream\" -T {filePath}");
}

View File

@ -32,9 +32,7 @@ namespace CodexReleaseTests.DataTests
var local1 = localFiles.Content.Single(f => f.Cid == cid1);
var local2 = localFiles.Content.Single(f => f.Cid == cid2);
Assert.That(local1.Manifest.Protected, Is.False);
Assert.That(local1.Manifest.DatasetSize, Is.EqualTo(size1));
Assert.That(local2.Manifest.Protected, Is.False);
Assert.That(local2.Manifest.DatasetSize, Is.EqualTo(size2));
}
}

View File

@ -1,11 +1,5 @@
using CodexTests;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utils;
namespace CodexReleaseTests.DataTests

View File

@ -1,86 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexReleaseTests.Utils;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
public class FailTest : MarketplaceAutoBootstrapDistTest
{
protected override int NumberOfHosts => 4;
private readonly int SlotTolerance;
protected override int NumberOfClients => 1;
protected override ByteSize HostAvailabilitySize => 1.GB();
protected override TimeSpan HostAvailabilityMaxDuration => TimeSpan.FromDays(1.0);
public FailTest()
{
SlotTolerance = NumberOfHosts / 2;
}
[Test]
[Combinatorial]
public void Fail(
[Rerun] int rerun
)
{
var hosts = StartHosts();
var client = StartClients().Single();
StartValidator();
var request = CreateStorageRequest(client);
request.WaitForStorageContractSubmitted();
AssertContractIsOnChain(request);
request.WaitForStorageContractStarted();
AssertContractSlotsAreFilledByHosts(request, hosts);
hosts.Stop(waitTillStopped: true);
WaitForSlotFreedEvents();
var config = GetContracts().Deployment.Config;
request.WaitForContractFailed(config);
}
private void WaitForSlotFreedEvents()
{
var start = DateTime.UtcNow;
var timeout = CalculateContractFailTimespan();
Log($"{nameof(WaitForSlotFreedEvents)} timeout: {Time.FormatDuration(timeout)}");
while (DateTime.UtcNow < start + timeout)
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
var slotFreed = events.GetSlotFreedEvents();
Log($"SlotFreed events: {slotFreed.Length} - Expected: {SlotTolerance}");
if (slotFreed.Length > SlotTolerance)
{
Log($"{nameof(WaitForSlotFreedEvents)} took {Time.FormatDuration(DateTime.UtcNow - start)}");
return;
}
GetContracts().WaitUntilNextPeriod();
}
Assert.Fail($"{nameof(WaitForSlotFreedEvents)} failed after {Time.FormatDuration(timeout)}");
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client)
{
var cid = client.UploadFile(GenerateTestFile(3.MB()));
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = HostAvailabilityMaxDuration / 2,
Expiry = TimeSpan.FromMinutes(5.0),
MinRequiredNumberOfNodes = (uint)NumberOfHosts,
NodeFailureTolerance = (uint)SlotTolerance,
PricePerBytePerSecond = 100.TstWei(),
ProofProbability = 1, // Require a proof every period
CollateralPerByte = 1.TstWei()
});
}
}
}
*/

View File

@ -1,86 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexReleaseTests.Utils;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
[TestFixture(5, 3, 1)]
[TestFixture(10, 8, 4)]
public class FinishTest : MarketplaceAutoBootstrapDistTest
{
public FinishTest(int hosts, int slots, int tolerance)
{
this.hosts = hosts;
purchaseParams = new PurchaseParams(slots, tolerance, uploadFilesize: 3.MB());
}
private readonly TestToken pricePerBytePerSecond = 10.TstWei();
private readonly int hosts;
private readonly PurchaseParams purchaseParams;
protected override int NumberOfHosts => hosts;
protected override int NumberOfClients => 1;
protected override ByteSize HostAvailabilitySize => purchaseParams.SlotSize.Multiply(5.1);
protected override TimeSpan HostAvailabilityMaxDuration => GetContractDuration() * 2;
[Test]
[Combinatorial]
public void Finish(
[Rerun] int rerun
)
{
var hosts = StartHosts();
var client = StartClients().Single();
AssertHostAvailabilitiesAreEmpty(hosts);
var request = CreateStorageRequest(client);
request.WaitForStorageContractSubmitted();
AssertContractIsOnChain(request);
WaitForContractStarted(request);
AssertContractSlotsAreFilledByHosts(request, hosts);
request.WaitForStorageContractFinished();
AssertClientHasPaidForContract(pricePerBytePerSecond, client, request, hosts);
AssertHostsWerePaidForContract(pricePerBytePerSecond, request, hosts);
AssertHostsCollateralsAreUnchanged(hosts);
AssertHostAvailabilitiesAreEmpty(hosts);
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client)
{
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
var config = GetContracts().Deployment.Config;
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = GetContractDuration(),
Expiry = GetContractExpiry(),
MinRequiredNumberOfNodes = (uint)purchaseParams.Nodes,
NodeFailureTolerance = (uint)purchaseParams.Tolerance,
PricePerBytePerSecond = pricePerBytePerSecond,
ProofProbability = 20,
CollateralPerByte = 100.TstWei()
});
}
private TimeSpan GetContractExpiry()
{
return GetContractDuration() / 2;
}
private TimeSpan GetContractDuration()
{
return Get8TimesConfiguredPeriodDuration();
}
private TimeSpan Get8TimesConfiguredPeriodDuration()
{
return GetPeriodDuration() * 8.0;
}
}
}
*/

View File

@ -1,101 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexContractsPlugin.ChainMonitor;
using CodexReleaseTests.Utils;
using Nethereum.Hex.HexConvertors.Extensions;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
[TestFixture]
public class IsProofRequiredTest : MarketplaceAutoBootstrapDistTest
{
#region Setup
private readonly PurchaseParams purchaseParams = new PurchaseParams(
nodes: 4,
tolerance: 2,
uploadFilesize: 32.MB()
);
public IsProofRequiredTest()
{
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
[Test]
public void IsProofRequired()
{
var mins = TimeSpan.FromMinutes(10.0);
StartHosts();
StartValidator();
var client = StartClients().Single();
var purchase = CreateStorageRequest(client, mins);
purchase.WaitForStorageContractStarted();
var requestId = purchase.PurchaseId.HexToByteArray();
var numSlots = purchaseParams.Nodes;
//var map = new Dictionary<ulong, List<int>>();
Log($"Checking IsProofRequired every second for {Time.FormatDuration(mins)}.");
var endUtc = DateTime.UtcNow + mins;
while (DateTime.UtcNow < endUtc)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
var requiredSlotIndices = new List<int>();
var willRequireSlotIndices = new List<int>();
for (var i = 0; i < numSlots; i++)
{
if (GetContracts().IsProofRequired(requestId, i)) requiredSlotIndices.Add(i);
if (GetContracts().WillProofBeRequired(requestId, i)) willRequireSlotIndices.Add(i);
}
var periodNumber = GetContracts().GetPeriodNumber(DateTime.UtcNow);
var blockNumber = GetGeth().GetSyncedBlockNumber();
Log($"[{blockNumber?.ToString().PadLeft(4, '0')}]" +
$"{periodNumber.ToString().PadLeft(12, '0')} => Required now: " +
$"[{string.Join(",", requiredSlotIndices.Select(i => i.ToString()))}] " +
$"Will be required: " +
$"[{string.Join(",", willRequireSlotIndices.Select(i => i.ToString()))}]");
//var num = currentPeriod.PeriodNumber;
//if (!map.ContainsKey(num))
//{
// map.Add(num, requiredSlotIndices);
// Log($"Period {num} = required proof for slot indices {string.Join(",", requiredSlotIndices.Select(i => i.ToString()))}");
//}
//else
//{
// var a = map[num];
// CollectionAssert.AreEquivalent(a, requiredSlotIndices);
//}
}
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client, TimeSpan minutes)
{
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
var config = GetContracts().Deployment.Config;
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = minutes * 1.1,
Expiry = TimeSpan.FromMinutes(8.0),
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()
});
}
}
}
*/

View File

@ -1,92 +0,0 @@
/* MARKETPLACE REMOVED
using BlockchainUtils;
using CodexContractsPlugin;
using CodexPlugin;
using DistTestCore;
using GethPlugin;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
[TestFixture]
public class TestTokenTransferTest : DistTest
{
private readonly EthAccount user1 = EthAccountGenerator.GenerateNew();
private readonly EthAccount user2 = EthAccountGenerator.GenerateNew();
[Test]
public void CanTransferTestTokens()
{
var node = Ci.StartCodexNode();
var blockCache = new BlockCache();
var geth = Ci.StartGethNode(blockCache, s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth, node.Version);
geth.SendEth(user1.EthAddress, 1.Eth());
geth.SendEth(user2.EthAddress, 1.Eth());
contracts.MintTestTokens(user1.EthAddress, 10.Tst());
Balances(contracts, 10.Tst(), 0.Tst());
var geth1 = geth.WithDifferentAccount(user1);
var geth2 = geth.WithDifferentAccount(user2);
var contracts1 = contracts.WithDifferentGeth(geth1);
var contracts2 = contracts.WithDifferentGeth(geth2);
contracts1.TransferTestTokens(user2.EthAddress, (0.5m).Tst());
Balances(contracts, (9.5m).Tst(), (0.5m).Tst());
contracts2.TransferTestTokens(user1.EthAddress, (0.2m).Tst());
Balances(contracts, (9.7m).Tst(), (0.3m).Tst());
}
[Test]
public void CanTransferEth()
{
var blockCache = new BlockCache();
var geth = Ci.StartGethNode(blockCache, s => s.IsMiner());
geth.SendEth(user1.EthAddress, 1.Eth());
geth.SendEth(user2.EthAddress, 1.Eth());
Balances(geth, 1.Eth(), 1.Eth());
var geth1 = geth.WithDifferentAccount(user1);
var geth2 = geth.WithDifferentAccount(user2);
geth1.SendEth(user2.EthAddress, (0.5m).Eth());
Balances(geth, (0.5m).Eth(), (1.5m).Eth());
geth2.SendEth(user1.EthAddress, (0.2m).Eth());
Balances(geth, (0.7m).Eth(), (1.3m).Eth());
}
private void Balances(ICodexContracts contracts, TestToken one, TestToken two)
{
var balance1 = contracts.GetTestTokenBalance(user1.EthAddress);
var balance2 = contracts.GetTestTokenBalance(user2.EthAddress);
Assert.That(balance1, Is.EqualTo(one));
Assert.That(balance2, Is.EqualTo(two));
}
private void Balances(IGethNode geth, Ether one, Ether two)
{
var balance1 = geth.GetEthBalance(user1.EthAddress);
var balance2 = geth.GetEthBalance(user2.EthAddress);
InRange(balance1, one);
InRange(balance2, two);
}
private void InRange(Ether balance, Ether expected)
{
var gasTolerance = (0.001m).Eth();
var max = expected + gasTolerance;
var min = expected - gasTolerance;
Assert.That(balance, Is.LessThanOrEqualTo(max).And.GreaterThanOrEqualTo(min));
}
}
}
*/

View File

@ -1,74 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexReleaseTests.Utils;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
public class MaxCapacityTest : MarketplaceAutoBootstrapDistTest
{
private readonly TestToken pricePerBytePerSecond = 10.TstWei();
private readonly PurchaseParams purchaseParams = new PurchaseParams(
nodes: 10,
tolerance: 5,
uploadFilesize: 10.MB()
);
protected override int NumberOfHosts => purchaseParams.Nodes / 2;
protected override int NumberOfClients => 1;
protected override ByteSize HostAvailabilitySize => purchaseParams.SlotSize.Multiply(2.1);
protected override TimeSpan HostAvailabilityMaxDuration => GetContractDuration() * 2;
[Test]
[Combinatorial]
public void TwoSlotsEach(
[Rerun] int rerun
)
{
var hosts = StartHosts();
var client = StartClients().Single();
AssertHostAvailabilitiesAreEmpty(hosts);
var request = CreateStorageRequest(client);
request.WaitForStorageContractSubmitted();
AssertContractIsOnChain(request);
WaitForContractStarted(request);
AssertContractSlotsAreFilledByHosts(request, hosts);
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client)
{
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
var config = GetContracts().Deployment.Config;
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = GetContractDuration(),
Expiry = GetContractExpiry(),
MinRequiredNumberOfNodes = (uint)purchaseParams.Nodes,
NodeFailureTolerance = (uint)purchaseParams.Tolerance,
PricePerBytePerSecond = pricePerBytePerSecond,
ProofProbability = 20,
CollateralPerByte = 100.TstWei()
});
}
private TimeSpan GetContractExpiry()
{
return GetContractDuration() / 2;
}
private TimeSpan GetContractDuration()
{
return Get8TimesConfiguredPeriodDuration();
}
private TimeSpan Get8TimesConfiguredPeriodDuration()
{
return GetPeriodDuration() * 8.0;
}
}
}
*/

View File

@ -1,196 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexContractsPlugin;
using CodexReleaseTests.Utils;
using Nethereum.Hex.HexConvertors.Extensions;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
[TestFixture]
public class RepairTest : MarketplaceAutoBootstrapDistTest
{
#region Setup
private readonly PurchaseParams purchaseParams = new PurchaseParams(
nodes: 4,
tolerance: 2,
uploadFilesize: 32.MB()
);
public RepairTest()
{
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
[Test]
[Combinatorial]
public void RollingRepairSingleFailure(
[Rerun] int rerun,
[Values(10)] int numFailures)
{
Assert.That(numFailures, Is.GreaterThan(NumberOfHosts));
var hosts = StartHosts().ToList();
var client = StartClients().Single();
StartValidator();
var contract = CreateStorageRequest(client);
contract.WaitForStorageContractStarted();
// All slots are filled.
client.Stop(waitTillStopped: true);
// Hold this situation
Log("Holding initial situation to ensure contract is stable...");
var config = GetContracts().Deployment.Config;
WaitAndCheckNodesStaysAlive(config.PeriodDuration * 5, hosts);
var requestState = GetContracts().GetRequestState(contract.PurchaseId.HexToByteArray());
Assert.That(requestState, Is.Not.EqualTo(RequestState.Failed));
for (var i = 0; i < numFailures; i++)
{
Log($"Failure step: {i}");
Log($"Running hosts: [{string.Join(", ", hosts.Select(h => h.GetName()))}]");
// Start a new host. Add it to the back of the list:
hosts.Add(StartOneHost());
var fill = GetSlotFillByOldestHost(hosts);
Log($"Causing failure for host: {fill.Host.GetName()} slotIndex: {fill.SlotFilledEvent.SlotIndex}");
hosts.Remove(fill.Host);
fill.Host.Stop(waitTillStopped: true);
// The slot should become free.
WaitForSlotFreedEvent(contract, fill.SlotFilledEvent.SlotIndex);
// One of the other hosts should pick up the free slot.
WaitForNewSlotFilledEvent(contract, fill.SlotFilledEvent.SlotIndex);
}
}
private void WaitForSlotFreedEvent(IStoragePurchaseContract contract, ulong slotIndex)
{
var start = DateTime.UtcNow;
var timeout = CalculateContractFailTimespan();
Log($"{nameof(WaitForSlotFreedEvent)} {Time.FormatDuration(timeout)} requestId: '{contract.PurchaseId.ToLowerInvariant()}' slotIndex: {slotIndex}");
while (DateTime.UtcNow < start + timeout)
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
var slotsFreed = events.GetSlotFreedEvents();
Log($"Slots freed this period: {slotsFreed.Length}");
foreach (var free in slotsFreed)
{
var freedId = free.RequestId.ToHex().ToLowerInvariant();
Log($"Free for requestId '{freedId}' slotIndex: {free.SlotIndex}");
if (freedId == contract.PurchaseId.ToLowerInvariant())
{
if (free.SlotIndex == slotIndex)
{
Log("Found the correct slotFree event");
return;
}
}
}
GetContracts().WaitUntilNextPeriod();
}
Assert.Fail($"{nameof(WaitForSlotFreedEvent)} for contract {contract.PurchaseId} and slotIndex {slotIndex} failed after {Time.FormatDuration(timeout)}");
}
private void WaitForNewSlotFilledEvent(IStoragePurchaseContract contract, ulong slotIndex)
{
Log(nameof(WaitForNewSlotFilledEvent));
var start = DateTime.UtcNow - TimeSpan.FromSeconds(10.0);
var timeout = contract.Purchase.Expiry;
while (DateTime.UtcNow < start + timeout)
{
var newTimeRange = new TimeRange(start, DateTime.UtcNow); // We only want to see new fill events.
var events = GetContracts().GetEvents(newTimeRange);
var slotFillEvents = events.GetSlotFilledEvents();
var matches = slotFillEvents.Where(f =>
{
return
f.RequestId.ToHex().ToLowerInvariant() == contract.PurchaseId.ToLowerInvariant() &&
f.SlotIndex == slotIndex;
}).ToArray();
if (matches.Length > 1)
{
var msg = string.Join(",", matches.Select(f => f.ToString()));
Assert.Fail($"Somehow, the slot got filled multiple times: {msg}");
}
if (matches.Length == 1)
{
Log($"Found the correct new slotFilled event: {matches[0].ToString()}");
return;
}
Thread.Sleep(TimeSpan.FromSeconds(15));
}
Assert.Fail($"{nameof(WaitForSlotFreedEvent)} for contract {contract.PurchaseId} and slotIndex {slotIndex} failed after {Time.FormatDuration(timeout)}");
}
private SlotFill GetSlotFillByOldestHost(List<ICodexNode> hosts)
{
var fills = GetOnChainSlotFills(hosts);
var copy = hosts.ToArray();
foreach (var host in copy)
{
var fill = GetFillByHost(host, fills);
if (fill == null)
{
// This host didn't fill anything.
// Move this one to the back of the list.
hosts.Remove(host);
hosts.Add(host);
}
else
{
return fill;
}
}
throw new Exception("None of the hosts seem to have filled a slot.");
}
private SlotFill? GetFillByHost(ICodexNode host, SlotFill[] fills)
{
// If these is more than 1 fill by this host, the test is misconfigured.
// The availability size of the host should guarantee it can fill 1 slot maximum.
return fills.SingleOrDefault(f => f.Host.EthAddress == host.EthAddress);
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client)
{
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
var config = GetContracts().Deployment.Config;
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = HostAvailabilityMaxDuration / 2,
Expiry = TimeSpan.FromMinutes(10.0),
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()
});
}
}
}
*/

View File

@ -1,119 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexPlugin;
using CodexReleaseTests.Utils;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
[TestFixture(10, 20, 5)]
public class SequentialContracts : MarketplaceAutoBootstrapDistTest
{
public SequentialContracts(int hosts, int slots, int tolerance)
{
this.hosts = hosts;
purchaseParams = new PurchaseParams(slots, tolerance, 10.MB());
}
private readonly int hosts;
private readonly PurchaseParams purchaseParams;
protected override int NumberOfHosts => hosts;
protected override int NumberOfClients => 6;
protected override ByteSize HostAvailabilitySize => purchaseParams.SlotSize.Multiply(100.0);
protected override TimeSpan HostAvailabilityMaxDuration => GetContractDuration() * 2;
private readonly TestToken pricePerBytePerSecond = 10.TstWei();
[Test]
[Combinatorial]
public void Sequential(
[Values(5)] int numGenerations)
{
var hosts = StartHosts();
var clients = StartClients();
for (var i = 0; i < numGenerations; i++)
{
Log("Generation: " + i);
try
{
Generation(clients, hosts);
}
catch (Exception ex)
{
Assert.Fail($"Failed at generation {i} with exception {ex}");
}
}
Thread.Sleep(TimeSpan.FromSeconds(12.0));
}
private void Generation(ICodexNodeGroup clients, ICodexNodeGroup hosts)
{
var requests = All(clients.ToArray(), CreateStorageRequest);
All(requests, r =>
{
r.WaitForStorageContractSubmitted();
AssertContractIsOnChain(r);
});
All(requests, WaitForContractStarted);
}
private void All<T>(T[] items, Action<T> action)
{
var tasks = items.Select(r => Task.Run(() => action(r))).ToArray();
Task.WaitAll(tasks);
foreach(var t in tasks)
{
if (t.Exception != null) throw t.Exception;
}
}
private TResult[] All<T, TResult>(T[] items, Func<T, TResult> action)
{
var tasks = items.Select(r => Task.Run(() => action(r))).ToArray();
Task.WaitAll(tasks);
foreach (var t in tasks)
{
if (t.Exception != null) throw t.Exception;
}
return tasks.Select(t => t.Result).ToArray();
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client)
{
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
var config = GetContracts().Deployment.Config;
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = GetContractDuration(),
Expiry = GetContractExpiry(),
MinRequiredNumberOfNodes = (uint)purchaseParams.Nodes,
NodeFailureTolerance = (uint)purchaseParams.Tolerance,
PricePerBytePerSecond = pricePerBytePerSecond,
ProofProbability = 100000,
CollateralPerByte = 1.TstWei()
});
}
private TimeSpan GetContractExpiry()
{
return GetContractDuration() / 2;
}
private TimeSpan GetContractDuration()
{
return Get8TimesConfiguredPeriodDuration() * 4;
}
private TimeSpan Get8TimesConfiguredPeriodDuration()
{
var config = GetContracts().Deployment.Config;
return TimeSpan.FromSeconds(config.Proofs.Period * 8.0);
}
}
}
*/

View File

@ -1,150 +0,0 @@
/* MARKETPLACE REMOVED
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]
public class StabilityTest : MarketplaceAutoBootstrapDistTest
{
#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
private int numPeriods = 0;
private bool proofWasMissed = false;
[Test]
[Combinatorial]
public void Stability(
[Values(10, 120)] int minutes)
{
var mins = TimeSpan.FromMinutes(minutes);
var periodDuration = GetContracts().Deployment.Config.PeriodDuration;
Assert.That(HostAvailabilityMaxDuration, Is.GreaterThan(mins * 1.1));
numPeriods = 0;
proofWasMissed = false;
StartHosts();
StartValidator();
var client = StartClients().Single();
var purchase = CreateStorageRequest(client, mins);
purchase.WaitForStorageContractStarted();
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));
var status = client.GetPurchaseStatus(purchase.PurchaseId);
if (status == null) throw new Exception("Purchase status not found");
Assert.That(status.IsStarted || status.IsFinished);
}
protected override void OnPeriod(PeriodReport report)
{
numPeriods++;
// For each required proof, there should be a submit call.
var calls = GetSubmitProofCalls(report);
foreach (var required in report.Required)
{
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;
}
}
// 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));
}
}
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)
{
var submitCall = nameof(SubmitProofFunction);
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!;
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client, TimeSpan minutes)
{
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
var config = GetContracts().Deployment.Config;
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = minutes * 1.1,
Expiry = TimeSpan.FromMinutes(8.0),
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()
});
}
}
}
*/

View File

@ -1,74 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexReleaseTests.Utils;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
[TestFixture]
public class StartTest : MarketplaceAutoBootstrapDistTest
{
private readonly PurchaseParams purchaseParams = new PurchaseParams(
nodes: 3,
tolerance: 1,
uploadFilesize: 3.MB()
);
private readonly TestToken pricePerBytePerSecond = 10.TstWei();
protected override int NumberOfHosts => 5;
protected override int NumberOfClients => 1;
protected override ByteSize HostAvailabilitySize => purchaseParams.SlotSize.Multiply(10.0);
protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration() * 12;
[Test]
[Combinatorial]
public void Start(
[Rerun] int rerun
)
{
var hosts = StartHosts();
var client = StartClients().Single();
var request = CreateStorageRequest(client);
request.WaitForStorageContractSubmitted();
AssertContractIsOnChain(request);
WaitForContractStarted(request);
AssertContractSlotsAreFilledByHosts(request, hosts);
}
private IStoragePurchaseContract CreateStorageRequest(ICodexNode client)
{
var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize));
var config = GetContracts().Deployment.Config;
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = GetContractDuration(),
Expiry = GetContractExpiry(),
MinRequiredNumberOfNodes = (uint)purchaseParams.Nodes,
NodeFailureTolerance = (uint)purchaseParams.Tolerance,
PricePerBytePerSecond = pricePerBytePerSecond,
ProofProbability = 20,
CollateralPerByte = 100.TstWei()
});
}
private TimeSpan GetContractExpiry()
{
return GetContractDuration() / 2;
}
private TimeSpan GetContractDuration()
{
return Get8TimesConfiguredPeriodDuration();
}
private TimeSpan Get8TimesConfiguredPeriodDuration()
{
return GetPeriodDuration() * 8.0;
}
}
}
*/

View File

@ -1,78 +0,0 @@
/* MARKETPLACE REMOVED
using CodexContractsPlugin;
using CodexContractsPlugin.ChainMonitor;
using GethPlugin;
using Logging;
using Utils;
namespace CodexReleaseTests.Utils
{
public class ChainMonitor
{
private readonly ILog log;
private readonly IGethNode gethNode;
private readonly ICodexContracts contracts;
private readonly IPeriodMonitorEventHandler periodMonitorEventHandler;
private readonly DateTime startUtc;
private readonly TimeSpan updateInterval;
private CancellationTokenSource cts = new CancellationTokenSource();
private Task worker = Task.CompletedTask;
public ChainMonitor(ILog log, IGethNode gethNode, ICodexContracts contracts, IPeriodMonitorEventHandler periodMonitorEventHandler, DateTime startUtc)
: this(log, gethNode, contracts, periodMonitorEventHandler, startUtc, TimeSpan.FromSeconds(3.0))
{
}
public ChainMonitor(ILog log, IGethNode gethNode, ICodexContracts contracts, IPeriodMonitorEventHandler periodMonitorEventHandler, DateTime startUtc, TimeSpan updateInterval)
{
this.log = log;
this.gethNode = gethNode;
this.contracts = contracts;
this.periodMonitorEventHandler = periodMonitorEventHandler;
this.startUtc = startUtc;
this.updateInterval = updateInterval;
}
public void Start(Action onFailure)
{
cts = new CancellationTokenSource();
worker = Task.Run(() => Worker(onFailure));
}
public void Stop()
{
cts.Cancel();
worker.Wait();
if (worker.Exception != null) throw worker.Exception;
}
private void Worker(Action onFailure)
{
var state = new ChainState(log, gethNode, contracts, new DoNothingThrowingChainEventHandler(), startUtc, true, periodMonitorEventHandler);
Thread.Sleep(updateInterval);
log.Log($"Chain monitoring started. Update interval: {Time.FormatDuration(updateInterval)}");
while (!cts.IsCancellationRequested)
{
try
{
UpdateChainState(state);
}
catch (Exception ex)
{
log.Error("Exception in chain monitor: " + ex);
onFailure();
throw;
}
cts.Token.WaitHandle.WaitOne(updateInterval);
}
}
private void UpdateChainState(ChainState state)
{
state.Update();
}
}
}
*/

View File

@ -1,506 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexContractsPlugin;
using CodexContractsPlugin.ChainMonitor;
using CodexContractsPlugin.Marketplace;
using CodexPlugin;
using CodexTests;
using GethPlugin;
using Logging;
using Nethereum.Hex.HexConvertors.Extensions;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.Utils
{
public abstract class MarketplaceAutoBootstrapDistTest : AutoBootstrapDistTest, IPeriodMonitorEventHandler
{
private MarketplaceHandle handle = null!;
protected const int StartingBalanceTST = 1000;
protected const int StartingBalanceEth = 10;
[SetUp]
public void SetupMarketplace()
{
var geth = StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version);
var monitor = SetupChainMonitor(GetTestLog(), geth, contracts, GetTestRunTimeRange().From);
handle = new MarketplaceHandle(geth, contracts, monitor);
}
[TearDown]
public void TearDownMarketplace()
{
if (handle.ChainMonitor != null) handle.ChainMonitor.Stop();
}
protected IGethNode GetGeth()
{
return handle.Geth;
}
protected ICodexContracts GetContracts()
{
return handle.Contracts;
}
protected ChainMonitor GetChainMonitor()
{
if (handle.ChainMonitor == null) throw new Exception($"Make sure {nameof(MonitorChainState)} is set to true.");
return handle.ChainMonitor;
}
protected TimeSpan GetPeriodDuration()
{
var config = GetContracts().Deployment.Config;
return TimeSpan.FromSeconds(config.Proofs.Period);
}
protected abstract int NumberOfHosts { get; }
protected abstract int NumberOfClients { get; }
protected abstract ByteSize HostAvailabilitySize { get; }
protected abstract TimeSpan HostAvailabilityMaxDuration { get; }
protected virtual bool MonitorChainState { get; } = true;
protected TimeSpan HostBlockTTL { get; } = TimeSpan.FromMinutes(1.0);
protected virtual void OnPeriod(PeriodReport report)
{
}
public ICodexNodeGroup StartHosts()
{
var hosts = StartCodex(NumberOfHosts, s => s
.WithName("host")
.WithBlockTTL(HostBlockTTL)
.WithBlockMaintenanceNumber(1000)
.WithBlockMaintenanceInterval(HostBlockTTL / 2)
.EnableMarketplace(GetGeth(), GetContracts(), m => m
.WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst())
.AsStorageNode()
)
);
var config = GetContracts().Deployment.Config;
foreach (var host in hosts)
{
AssertTstBalance(host, StartingBalanceTST.Tst(), nameof(StartHosts));
AssertEthBalance(host, StartingBalanceEth.Eth(), nameof(StartHosts));
host.Marketplace.MakeStorageAvailable(new CreateStorageAvailability(
totalSpace: HostAvailabilitySize,
maxDuration: HostAvailabilityMaxDuration,
minPricePerBytePerSecond: 1.TstWei(),
totalCollateral: 999999.Tst())
);
}
return hosts;
}
public ICodexNode StartOneHost()
{
var host = StartCodex(s => s
.WithName("singlehost")
.WithBlockTTL(HostBlockTTL)
.WithBlockMaintenanceNumber(1000)
.WithBlockMaintenanceInterval(HostBlockTTL / 2)
.EnableMarketplace(GetGeth(), GetContracts(), m => m
.WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst())
.AsStorageNode()
)
);
var config = GetContracts().Deployment.Config;
AssertTstBalance(host, StartingBalanceTST.Tst(), nameof(StartOneHost));
AssertEthBalance(host, StartingBalanceEth.Eth(), nameof(StartOneHost));
host.Marketplace.MakeStorageAvailable(new CreateStorageAvailability(
totalSpace: HostAvailabilitySize,
maxDuration: HostAvailabilityMaxDuration,
minPricePerBytePerSecond: 1.TstWei(),
totalCollateral: 999999.Tst())
);
return host;
}
public void AssertHostAvailabilitiesAreEmpty(IEnumerable<ICodexNode> hosts)
{
var retry = GetAvailabilitySpaceAssertRetry();
retry.Run(() =>
{
foreach (var host in hosts)
{
AssertHostAvailabilitiesAreEmpty(host);
}
});
}
private void AssertHostAvailabilitiesAreEmpty(ICodexNode host)
{
var availabilities = host.Marketplace.GetAvailabilities();
foreach (var a in availabilities)
{
if (a.FreeSpace.SizeInBytes != a.TotalSpace.SizeInBytes)
{
throw new Exception(nameof(AssertHostAvailabilitiesAreEmpty) + $" free: {a.FreeSpace} total: {a.TotalSpace}");
}
}
}
public void AssertTstBalance(ICodexNode node, TestToken expectedBalance, string message)
{
AssertTstBalance(node.EthAddress, expectedBalance, message);
}
public void AssertTstBalance(EthAddress address, TestToken expectedBalance, string message)
{
var retry = GetBalanceAssertRetry();
retry.Run(() =>
{
var balance = GetTstBalance(address);
if (balance != expectedBalance)
{
throw new Exception(nameof(AssertTstBalance) +
$" expected: {expectedBalance} but was: {balance} - message: " + message);
}
});
}
public void AssertEthBalance(ICodexNode node, Ether expectedBalance, string message)
{
var retry = GetBalanceAssertRetry();
retry.Run(() =>
{
var balance = GetEthBalance(node);
if (balance != expectedBalance)
{
throw new Exception(nameof(AssertEthBalance) +
$" expected: {expectedBalance} but was: {balance} - message: " + message);
}
});
}
public ICodexNodeGroup StartClients()
{
return StartClients(s => { });
}
public ICodexNodeGroup StartClients(Action<ICodexSetup> additional)
{
return StartCodex(NumberOfClients, s =>
{
s.WithName("client")
.EnableMarketplace(GetGeth(), GetContracts(), m => m
.WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst()));
additional(s);
});
}
public ICodexNode StartValidator()
{
return StartCodex(s => s
.WithName("validator")
.EnableMarketplace(GetGeth(), GetContracts(), m => m
.WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst())
.AsValidator()
)
);
}
public void OnPeriodReport(PeriodReport report)
{
OnPeriod(report);
}
public SlotFill[] GetOnChainSlotFills(IEnumerable<ICodexNode> possibleHosts, string purchaseId)
{
var fills = GetOnChainSlotFills(possibleHosts);
return fills.Where(f => f
.SlotFilledEvent.RequestId.ToHex(false).ToLowerInvariant() == purchaseId.ToLowerInvariant())
.ToArray();
}
public SlotFill[] GetOnChainSlotFills(IEnumerable<ICodexNode> possibleHosts)
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
var fills = events.GetSlotFilledEvents();
return fills.Select(f =>
{
// We can encounter a fill event that's from an old host.
// We must disregard those.
var host = possibleHosts.SingleOrDefault(h => h.EthAddress.Address == f.Host.Address);
if (host == null) return null;
return new SlotFill(f, host);
})
.Where(f => f != null)
.Cast<SlotFill>()
.ToArray();
}
protected void AssertClientHasPaidForContract(TestToken pricePerBytePerSecond, ICodexNode client, IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var expectedBalance = StartingBalanceTST.Tst() - GetContractFinalCost(pricePerBytePerSecond, contract, hosts);
AssertTstBalance(client, expectedBalance, "Client balance incorrect.");
Log($"Client has paid for contract. Balance: {expectedBalance}");
}
protected void AssertHostsWerePaidForContract(TestToken pricePerBytePerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var fills = GetOnChainSlotFills(hosts);
var submitUtc = GetContractOnChainSubmittedUtc(contract);
var finishUtc = submitUtc + contract.Purchase.Duration;
var slotSize = Convert.ToInt64(contract.GetStatus().Request.Ask.SlotSize).Bytes();
var expectedBalances = new Dictionary<EthAddress, TestToken>();
foreach (var host in hosts) expectedBalances.Add(host.EthAddress, StartingBalanceTST.Tst());
foreach (var fill in fills)
{
var slotDuration = finishUtc - fill.SlotFilledEvent.Block.Utc;
expectedBalances[fill.Host.EthAddress] += GetContractCostPerSlot(pricePerBytePerSecond, slotSize, slotDuration);
}
foreach (var pair in expectedBalances)
{
AssertTstBalance(pair.Key, pair.Value, $"Host {pair.Key} was not paid for storage.");
Log($"Host {pair.Key} was paid for storage. Balance: {pair.Value}");
}
}
protected 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)
{
var retry = GetBalanceAssertRetry();
retry.Run(() =>
{
if (GetTstBalance(host) < StartingBalanceTST.Tst())
{
throw new Exception(nameof(AssertHostsCollateralsAreUnchanged));
}
});
}
}
protected void WaitForContractStarted(IStoragePurchaseContract r)
{
try
{
r.WaitForStorageContractStarted();
}
catch
{
// Contract failed to start. Retrieve and log every call to ReserveSlot to identify which hosts
// should have filled the slot.
var requestId = r.PurchaseId.ToLowerInvariant();
var calls = new List<ReserveSlotFunction>();
GetContracts().GetEvents(GetTestRunTimeRange()).GetReserveSlotCalls(calls.Add);
Log($"Request '{requestId}' failed to start. There were {calls.Count} hosts who called reserve-slot for it:");
foreach (var c in calls)
{
Log($" - {c.Block.Utc} Host: {c.FromAddress} RequestId: {c.RequestId.ToHex()} SlotIndex: {c.SlotIndex}");
}
throw;
}
}
private ChainMonitor? SetupChainMonitor(ILog log, IGethNode gethNode, ICodexContracts contracts, DateTime startUtc)
{
if (!MonitorChainState) return null;
var result = new ChainMonitor(log, gethNode, contracts, this, startUtc);
result.Start(() =>
{
Assert.Fail("Failure in chain monitor.");
});
return result;
}
private Retry GetBalanceAssertRetry()
{
return new Retry("AssertBalance",
maxTimeout: TimeSpan.FromMinutes(10.0),
sleepAfterFail: TimeSpan.FromSeconds(10.0),
onFail: f => { },
failFast: false);
}
private Retry GetAvailabilitySpaceAssertRetry()
{
return new Retry("AssertAvailabilitySpace",
maxTimeout: HostBlockTTL * 3,
sleepAfterFail: TimeSpan.FromSeconds(10.0),
onFail: f => { },
failFast: false);
}
private TestToken GetTstBalance(ICodexNode node)
{
return GetContracts().GetTestTokenBalance(node);
}
private TestToken GetTstBalance(EthAddress address)
{
return GetContracts().GetTestTokenBalance(address);
}
private Ether GetEthBalance(ICodexNode node)
{
return GetGeth().GetEthBalance(node);
}
private Ether GetEthBalance(EthAddress address)
{
return GetGeth().GetEthBalance(address);
}
private TestToken GetContractFinalCost(TestToken pricePerBytePerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var fills = GetOnChainSlotFills(hosts);
var result = 0.Tst();
var submitUtc = GetContractOnChainSubmittedUtc(contract);
var finishUtc = submitUtc + contract.Purchase.Duration;
var slotSize = Convert.ToInt64(contract.GetStatus().Request.Ask.SlotSize).Bytes();
foreach (var fill in fills)
{
var slotDuration = finishUtc - fill.SlotFilledEvent.Block.Utc;
result += GetContractCostPerSlot(pricePerBytePerSecond, slotSize, slotDuration);
}
return result;
}
private DateTime GetContractOnChainSubmittedUtc(IStoragePurchaseContract contract)
{
return Time.Retry(() =>
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
var submitEvent = events.GetStorageRequestedEvents().SingleOrDefault(e => e.RequestId.ToHex() == contract.PurchaseId);
if (submitEvent == null)
{
// We're too early.
throw new TimeoutException(nameof(GetContractOnChainSubmittedUtc) + "StorageRequest not found on-chain.");
}
return submitEvent.Block.Utc;
}, nameof(GetContractOnChainSubmittedUtc));
}
private TestToken GetContractCostPerSlot(TestToken pricePerBytePerSecond, ByteSize slotSize, TimeSpan slotDuration)
{
var cost = pricePerBytePerSecond.TstWei * slotSize.SizeInBytes * (int)slotDuration.TotalSeconds;
return cost.TstWei();
}
protected void AssertContractSlotsAreFilledByHosts(IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var activeHosts = new Dictionary<int, SlotFill>();
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));
}
protected void AssertContractIsOnChain(IStoragePurchaseContract contract)
{
// Check the creation event.
AssertOnChainEvents(events =>
{
var onChainRequests = events.GetStorageRequestedEvents();
if (onChainRequests.Any(r => r.RequestId.ToHex() == contract.PurchaseId)) return;
throw new Exception($"OnChain request {contract.PurchaseId} not found...");
}, nameof(AssertContractIsOnChain));
// Check that the getRequest call returns it.
var rid = contract.PurchaseId.HexToByteArray();
var r = GetContracts().GetRequest(rid);
if (r == null) throw new Exception($"Failed to get Request from {nameof(GetRequestFunction)}");
Assert.That(r.Ask.Duration, Is.EqualTo(contract.Purchase.Duration.TotalSeconds));
Assert.That(r.Ask.Slots, Is.EqualTo(contract.Purchase.MinRequiredNumberOfNodes));
Assert.That(((int)r.Ask.ProofProbability), Is.EqualTo(contract.Purchase.ProofProbability));
}
protected void AssertOnChainEvents(Action<ICodexContractsEvents> onEvents, string description)
{
Time.Retry(() =>
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
onEvents(events);
}, description);
}
protected TimeSpan CalculateContractFailTimespan()
{
var config = GetContracts().Deployment.Config;
var requiredNumMissedProofs = Convert.ToInt32(config.Collateral.MaxNumberOfSlashes);
var periodDuration = GetPeriodDuration();
var gracePeriod = periodDuration;
// Each host could miss 1 proof per period,
// so the time we should wait is period time * requiredNum of missed proofs.
// Except: the proof requirement has a concept of "downtime":
// a segment of time where proof is not required.
// We calculate the probability of downtime and extend the waiting
// timeframe by a factor, such that all hosts are highly likely to have
// failed a sufficient number of proofs.
float n = requiredNumMissedProofs;
return gracePeriod + periodDuration * n * GetDowntimeFactor(config);
}
private float GetDowntimeFactor(MarketplaceConfig config)
{
byte numBlocksInDowntimeSegment = config.Proofs.Downtime;
float downtime = numBlocksInDowntimeSegment;
float window = 256.0f;
var chanceOfDowntime = downtime / window;
return 1.0f + (5.0f * chanceOfDowntime);
}
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, ChainMonitor? chainMonitor)
{
Geth = geth;
Contracts = contracts;
ChainMonitor = chainMonitor;
}
public IGethNode Geth { get; }
public ICodexContracts Contracts { get; }
public ChainMonitor? ChainMonitor { get; }
}
}
}
*/

View File

@ -1,75 +0,0 @@
/* MARKETPLACE REMOVED
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.Utils
{
public class PurchaseParams
{
private readonly ByteSize blockSize = 64.KB();
public PurchaseParams(int nodes, int tolerance, ByteSize uploadFilesize)
{
Nodes = nodes;
Tolerance = tolerance;
UploadFilesize = uploadFilesize;
EncodedDatasetSize = CalculateEncodedDatasetSize();
SlotSize = CalculateSlotSize();
Assert.That(IsPowerOfTwo(SlotSize));
}
public int Nodes { get; }
public int Tolerance { get; }
public ByteSize UploadFilesize { get; }
public ByteSize EncodedDatasetSize { get; }
public ByteSize SlotSize { get; }
private ByteSize CalculateSlotSize()
{
// encoded dataset is divided over the nodes.
// then each slot is rounded up to the nearest power-of-two blocks.
var numBlocks = EncodedDatasetSize.DivUp(blockSize);
var numSlotBlocks = 1 + ((numBlocks - 1) / Nodes); // round-up div.
// Next power of two:
var numSlotBlocksPow2 = IsOrNextPowerOf2(numSlotBlocks);
return new ByteSize(blockSize.SizeInBytes * numSlotBlocksPow2);
}
private ByteSize CalculateEncodedDatasetSize()
{
var numBlocks = UploadFilesize.DivUp(blockSize);
var ecK = Nodes - Tolerance;
var ecM = Tolerance;
// for each K blocks, we generate M parity blocks
var numParityBlocks = (numBlocks / ecK) * ecM;
var totalBlocks = numBlocks + numParityBlocks;
return new ByteSize(blockSize.SizeInBytes * totalBlocks);
}
private int IsOrNextPowerOf2(int n)
{
if (IsPowerOfTwo(n)) return n;
n = n - 1;
var lg = Convert.ToInt32(Math.Round(Math.Log2(Convert.ToDouble(n))));
return 1 << (lg + 1);
}
private static bool IsPowerOfTwo(ByteSize size)
{
var x = size.SizeInBytes;
return (x != 0) && ((x & (x - 1)) == 0);
}
private static bool IsPowerOfTwo(int x)
{
return (x != 0) && ((x & (x - 1)) == 0);
}
}
}
*/

View File

@ -50,23 +50,5 @@ namespace ExperimentalTests.BasicTests
LogNodeStatus(primary, metrics[0]);
LogNodeStatus(primary2, metrics[1]);
}
// MARKETPLACE REMOVED: GethBootstrapTest
// [Test]
// public void GethBootstrapTest()
// {
// var boot = StartGethNode(s => s.WithName("boot").IsMiner());
// var disconnected = StartGethNode(s => s.WithName("disconnected"));
// var follow = StartGethNode(s => s.WithBootstrapNode(boot).WithName("follow"));
//
// Thread.Sleep(12000);
//
// var bootN = boot.GetSyncedBlockNumber();
// var discN = disconnected.GetSyncedBlockNumber();
// var followN = follow.GetSyncedBlockNumber();
//
// Assert.That(bootN, Is.EqualTo(followN));
// Assert.That(discN, Is.LessThan(bootN));
// }
}
}

View File

@ -1,175 +0,0 @@
/* MARKETPLACE REMOVED
using CodexClient;
using CodexContractsPlugin;
using CodexContractsPlugin.Marketplace;
using CodexPlugin;
using CodexTests;
using FileUtils;
using GethPlugin;
using NUnit.Framework;
using Utils;
namespace ExperimentalTests.BasicTests
{
[TestFixture]
public class MarketplaceTests : AutoBootstrapDistTest
{
[Test]
[Combinatorial]
[CreateTranscript(nameof(MarketplaceTests), includeBlockReceivedEvents: false)]
public void MarketplaceExample(
[Values(64)] int numBlocks,
[Values(0)] int plusSizeKb,
[Values(0)] int plusSizeBytes
)
{
var hostInitialBalance = 234.TstWei();
var clientInitialBalance = 100000.TstWei();
var fileSize = new ByteSize(
numBlocks * 64 * 1024 +
plusSizeKb * 1024 +
plusSizeBytes
);
var geth = StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version);
var numberOfHosts = 5;
var hosts = StartCodex(numberOfHosts, s => s
.WithName("Host")
.WithLogLevel(CodexLogLevel.Trace, new CodexLogCustomTopics(CodexLogLevel.Error, CodexLogLevel.Error, CodexLogLevel.Warn)
{
ContractClock = CodexLogLevel.Trace,
})
.WithStorageQuota(11.GB())
.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), hostInitialBalance)
.AsStorageNode()
.AsValidator()));
foreach (var host in hosts)
{
AssertBalance(contracts, host, Is.EqualTo(hostInitialBalance), "Host initial balance");
var availability = new CreateStorageAvailability(
totalSpace: 10.GB(),
maxDuration: TimeSpan.FromMinutes(30),
minPricePerBytePerSecond: 1.TstWei(),
totalCollateral: 20.TstWei()
);
host.Marketplace.MakeStorageAvailable(availability);
}
var testFile = CreateFile(fileSize);
var client = StartCodex(s => s
.WithName("Client")
.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), clientInitialBalance)));
AssertBalance(contracts, client, Is.EqualTo(clientInitialBalance), "Client initial balance");
var uploadCid = client.UploadFile(testFile);
var purchase = new StoragePurchaseRequest(uploadCid)
{
PricePerBytePerSecond = 2.TstWei(),
CollateralPerByte = 10.TstWei(),
MinRequiredNumberOfNodes = 5,
NodeFailureTolerance = 2,
ProofProbability = 5,
Duration = TimeSpan.FromMinutes(6),
Expiry = TimeSpan.FromMinutes(5)
};
var purchaseContract = client.Marketplace.RequestStorage(purchase);
var contractCid = purchaseContract.ContentId;
Assert.That(uploadCid.Id, Is.Not.EqualTo(contractCid.Id));
// Download both from client.
testFile.AssertIsEqual(client.DownloadContent(uploadCid));
testFile.AssertIsEqual(client.DownloadContent(contractCid));
// Download both from another node.
var downloader = StartCodex(s => s.WithName("Downloader"));
testFile.AssertIsEqual(downloader.DownloadContent(uploadCid));
testFile.AssertIsEqual(downloader.DownloadContent(contractCid));
WaitForAllSlotFilledEvents(contracts, purchase, geth);
purchaseContract.WaitForStorageContractStarted();
var availabilities = hosts.Select(h => h.Marketplace.GetAvailabilities()).ToArray();
if (availabilities.All(h => h.All(a => a.FreeSpace.SizeInBytes == a.TotalSpace.SizeInBytes)))
{
Assert.Fail("Host availabilities were not used.");
}
var request = GetOnChainStorageRequest(contracts, geth);
AssertStorageRequest(request, purchase, contracts, client);
AssertContractSlot(contracts, request, 0);
purchaseContract.WaitForStorageContractFinished();
// todo: removed from codexclient:
//contracts.WaitUntilNextPeriod();
//contracts.WaitUntilNextPeriod();
//var blocks = 3;
//Log($"Waiting {blocks} blocks for nodes to process payouts...");
//Thread.Sleep(GethContainerRecipe.BlockInterval * blocks);
AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage.");
Assert.That(contracts.GetRequestState(request.RequestId), Is.EqualTo(RequestState.Finished));
}
private TrackedFile CreateFile(ByteSize fileSize)
{
var segmentSize = new ByteSize(fileSize.SizeInBytes / 4);
return GenerateTestFile(o => o
.Random(segmentSize)
.ByteRepeat(new byte[] { 0xaa }, segmentSize)
.Random(segmentSize)
.ByteRepeat(new byte[] { 0xee }, segmentSize)
);
}
private void WaitForAllSlotFilledEvents(ICodexContracts contracts, StoragePurchaseRequest purchase, IGethNode geth)
{
Time.Retry(() =>
{
var events = contracts.GetEvents(GetTestRunTimeRange());
var slotFilledEvents = events.GetSlotFilledEvents();
var msg = $"SlotFilledEvents: {slotFilledEvents.Length} - NumSlots: {purchase.MinRequiredNumberOfNodes}";
Debug(msg);
if (slotFilledEvents.Length != purchase.MinRequiredNumberOfNodes) throw new Exception(msg);
}, purchase.Expiry + TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5), "Checking SlotFilled events");
}
private void AssertStorageRequest(StorageRequestedEventDTO request, StoragePurchaseRequest purchase, ICodexContracts contracts, ICodexNode buyer)
{
Assert.That(contracts.GetRequestState(request.RequestId), Is.EqualTo(RequestState.Started));
var r = contracts.GetRequest(request.RequestId);
Assert.That(r.ClientAddress, Is.EqualTo(buyer.EthAddress));
Assert.That(request.Ask.Slots, Is.EqualTo(purchase.MinRequiredNumberOfNodes));
}
private StorageRequestedEventDTO GetOnChainStorageRequest(ICodexContracts contracts, IGethNode geth)
{
var events = contracts.GetEvents(GetTestRunTimeRange());
var requests = events.GetStorageRequestedEvents();
Assert.That(requests.Length, Is.EqualTo(1));
return requests.Single();
}
private void AssertContractSlot(ICodexContracts contracts, StorageRequestedEventDTO request, int contractSlotIndex)
{
var slotHost = contracts.GetSlotHost(request.RequestId, contractSlotIndex);
Assert.That(slotHost?.Address, Is.Not.Null);
}
}
}
*/

View File

@ -1,20 +1,15 @@
// MARKETPLACE REMOVED: using BlockchainUtils;
using CodexClient;
// MARKETPLACE REMOVED: using CodexContractsPlugin;
using CodexNetDeployer;
using CodexPlugin;
using CodexPlugin.OverwatchSupport;
using CodexTests.Helpers;
using Core;
using DistTestCore;
using DistTestCore.Helpers;
using DistTestCore.Logs;
// MARKETPLACE REMOVED: using GethPlugin;
using Logging;
using MetricsPlugin;
using Newtonsoft.Json;
using NUnit.Framework;
// MARKETPLACE REMOVED: using NUnit.Framework.Constraints;
using OverwatchTranscript;
using Utils;
@ -22,15 +17,12 @@ namespace CodexTests
{
public class CodexDistTest : DistTest
{
// MARKETPLACE REMOVED: private readonly BlockCache blockCache = new BlockCache();
private readonly List<ICodexNode> nodes = new List<ICodexNode>();
private CodexTranscriptWriter? writer;
public CodexDistTest()
{
ProjectPlugin.Load<CodexPlugin.CodexPlugin>();
// MARKETPLACE REMOVED: ProjectPlugin.Load<CodexContractsPlugin.CodexContractsPlugin>();
// MARKETPLACE REMOVED: ProjectPlugin.Load<GethPlugin.GethPlugin>();
ProjectPlugin.Load<MetricsPlugin.MetricsPlugin>();
}
@ -81,12 +73,6 @@ namespace CodexTests
return group;
}
// MARKETPLACE REMOVED: StartGethNode method
// public IGethNode StartGethNode(Action<IGethSetup> setup)
// {
// return Ci.StartGethNode(blockCache, setup);
// }
public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers()
{
return new PeerConnectionTestHelpers(GetTestLog());
@ -97,13 +83,6 @@ namespace CodexTests
return new PeerDownloadTestHelpers(GetTestLog(), GetFileManager());
}
// MARKETPLACE REMOVED: AssertBalance method
// public void AssertBalance(ICodexContracts contracts, ICodexNode codexNode, Constraint constraint, string msg)
// {
// Assert.Fail("Depricated, use MarketplaceAutobootstrapDistTest assertBalances instead.");
// AssertHelpers.RetryAssert(constraint, () => contracts.GetTestTokenBalance(codexNode), nameof(AssertBalance) + msg);
// }
public void CheckLogForErrors(params ICodexNode[] nodes)
{
foreach (var node in nodes) CheckLogForErrors(node);

View File

@ -49,25 +49,13 @@ namespace CodexTests
addNode(node);
}
public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount)
public void OnNodeStarting(DateTime startUtc, string image)
{
}
public void OnNodeStopping()
{
}
public void OnStorageAvailabilityCreated(StorageAvailability response)
{
}
public void OnStorageContractSubmitted(IStoragePurchaseContract storagePurchaseContract)
{
}
public void OnStorageContractUpdated(StoragePurchase purchaseStatus)
{
}
}
}
}

View File

@ -1,5 +1,4 @@
using CodexClient;
// MARKETPLACE REMOVED: using CodexContractsPlugin;
using CodexTests;
using NUnit.Framework;
using Utils;
@ -17,18 +16,6 @@ namespace ExperimentalTests.DownloadConnectivityTests
AssertAllNodesConnected(nodes);
}
// MARKETPLACE REMOVED: MarketplaceDoesNotInterfereWithPeerDownload test
// [Test]
// public void MarketplaceDoesNotInterfereWithPeerDownload()
// {
// var geth = StartGethNode(s => s.IsMiner());
// var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version);
// var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
// .WithInitial(10.Eth(), 1000.TstWei())));
//
// AssertAllNodesConnected(nodes);
// }
[Test]
[Combinatorial]
public void FullyConnectedDownloadTest(

View File

@ -1,4 +1,3 @@
// MARKETPLACE REMOVED: using CodexContractsPlugin;
using CodexClient;
using NUnit.Framework;
using Utils;
@ -27,18 +26,6 @@ namespace ExperimentalTests.PeerDiscoveryTests
AssertAllNodesConnected(nodes);
}
// MARKETPLACE REMOVED: MarketplaceDoesNotInterfereWithPeerDiscovery test
// [Test]
// public void MarketplaceDoesNotInterfereWithPeerDiscovery()
// {
// var geth = StartGethNode(s => s.IsMiner());
// var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version);
// var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
// .WithInitial(10.Eth(), 1000.TstWei())));
//
// AssertAllNodesConnected(nodes);
// }
[TestCase(2)]
[TestCase(3)]
[TestCase(10)]

View File

@ -1,48 +0,0 @@
/* MARKETPLACE REMOVED
using NUnit.Framework;
using System.Numerics;
using Utils;
namespace FrameworkTests.CodexContractsPlugin
{
[TestFixture]
public class TestTokenTests
{
private const decimal factor = 1000000000000000000m;
[Test]
public void RepresentsSmallAmount()
{
var t = 10.TstWei();
Assert.That(t.TstWei, Is.EqualTo(new BigInteger(10)));
Assert.That(t.Tst, Is.EqualTo(new BigInteger(0)));
Assert.That(t.ToString(), Is.EqualTo("10 TSTWEI"));
}
[Test]
public void RepresentsLargeAmount()
{
var t = 10.Tst();
var expected = new BigInteger(10 * factor);
Assert.That(t.TstWei, Is.EqualTo(expected));
Assert.That(t.Tst, Is.EqualTo(new BigInteger(10)));
Assert.That(t.ToString(), Is.EqualTo("10 TST"));
}
[Test]
public void RepresentsLongAmount()
{
var a = 10.Tst();
var b = 20.TstWei();
var t = a + b;
var expected = new BigInteger((10 * factor) + 20);
Assert.That(t.TstWei, Is.EqualTo(expected));
Assert.That(t.Tst, Is.EqualTo(new BigInteger(10)));
Assert.That(t.ToString(), Is.EqualTo("10 TST + 20 TSTWEI"));
}
}
}
*/

View File

@ -1,48 +0,0 @@
/* MARKETPLACE REMOVED
using NUnit.Framework;
using Utils;
namespace FrameworkTests.CodexContractsPlugin
{
[TestFixture]
public class TestTokenEqualityTests
{
[Test]
[Combinatorial]
public void Equal(
[Values(1, 22, 333, 4444, 55555)] int amount,
[Values(true, false)] bool isWei
)
{
var amount1 = CreateTst(amount, isWei);
var amount2 = CreateTst(amount, isWei);
Assert.That(amount1, Is.EqualTo(amount2));
Assert.That(amount1 == amount2);
Assert.That(!(amount1 != amount2));
}
[Test]
[Combinatorial]
public void NotEqual(
[Values(22, 333, 4444, 55555)] int amount,
[Values(true, false)] bool isWei,
[Values(1, 2, 10, -1, -2, -10)] int deltaWei
)
{
var amount1 = CreateTst(amount, isWei);
var amount2 = CreateTst(amount, isWei) + deltaWei.TstWei();
Assert.That(amount1, Is.Not.EqualTo(amount2));
Assert.That(amount1 != amount2);
Assert.That(!(amount1 == amount2));
}
private TestToken CreateTst(int amount, bool isWei)
{
if (isWei) return amount.TstWei();
return amount.Tst();
}
}
}
*/

View File

@ -1,203 +0,0 @@
/* MARKETPLACE REMOVED
using BlockchainUtils;
using Logging;
using Moq;
using NethereumWorkflow;
using NUnit.Framework;
namespace FrameworkTests.NethereumWorkflow
{
[TestFixture]
public class BlockTimeFinderTests
{
private readonly Mock<ILog> log = new Mock<ILog>();
private Mock<IWeb3Blocks> web3 = new Mock<IWeb3Blocks>();
private Dictionary<ulong, Block> blocks = new Dictionary<ulong, Block>();
private BlockTimeFinder finder = null!;
private void SetupBlockchain()
{
var start = DateTime.UtcNow.AddDays(-1).AddSeconds(-30);
blocks = new Dictionary<ulong, Block>();
Block? prev = null;
for (ulong i = 0; i < 30; i++)
{
ulong d = 100 + i;
var newBlock = new Block(d, start + TimeSpan.FromSeconds(i * 2));
blocks.Add(d, newBlock);
if (prev != null)
{
prev.Next = newBlock;
newBlock.Previous = prev;
}
prev = newBlock;
}
}
[SetUp]
public void SetUp()
{
SetupBlockchain();
web3 = new Mock<IWeb3Blocks>();
web3.Setup(w => w.GetCurrentBlockNumber()).Returns(blocks.Keys.Max());
web3.Setup(w => w.GetTimestampForBlock(It.IsAny<ulong>())).Returns<ulong>(d =>
{
if (blocks.ContainsKey(d)) return blocks[d].Time;
return null;
});
finder = new BlockTimeFinder(new BlockCache(), web3.Object, log.Object);
}
[Test]
public void FindsMiddleOfChain()
{
var b1 = blocks[115];
var b2 = blocks[116];
var momentBetween = b1.JustAfter;
var b1Number = finder.GetHighestBlockNumberBefore(momentBetween);
var b2Number = finder.GetLowestBlockNumberAfter(momentBetween);
Assert.That(b1Number, Is.EqualTo(b1.Number));
Assert.That(b2Number, Is.EqualTo(b2.Number));
}
[Test]
public void FindsFrontOfChain_Lowest()
{
var first = blocks.First().Value;
var firstNumber = finder.GetLowestBlockNumberAfter(first.JustBefore);
Assert.That(firstNumber, Is.EqualTo(first.Number));
}
[Test]
public void FindsFrontOfChain_Highest()
{
var first = blocks.First().Value;
var firstNumber = finder.GetHighestBlockNumberBefore(first.JustAfter);
Assert.That(firstNumber, Is.EqualTo(first.Number));
}
[Test]
public void FindsTailOfChain_Lowest()
{
var last = blocks.Last().Value;
var lastNumber = finder.GetLowestBlockNumberAfter(last.JustBefore);
Assert.That(lastNumber, Is.EqualTo(last.Number));
}
[Test]
public void FindsTailOfChain_Highest()
{
var last = blocks.Last().Value;
var lastNumber = finder.GetHighestBlockNumberBefore(last.JustAfter);
Assert.That(lastNumber, Is.EqualTo(last.Number));
}
[Test]
public void FindsGenesisBlockAtFrontOfChain()
{
var first = blocks.First().Value;
var firstNumber = finder.GetHighestBlockNumberBefore(first.Time);
Assert.That(firstNumber, Is.EqualTo(first.Number));
}
[Test]
public void FindsCurrentBlockAtTailOfChain()
{
var last = blocks.Last().Value;
var lastNumber = finder.GetLowestBlockNumberAfter(last.Time);
Assert.That(lastNumber, Is.EqualTo(last.Number));
}
[Test]
public void FailsToFindBlockBeforeFrontOfChain_history()
{
var first = blocks.First().Value;
var notFound = finder.GetHighestBlockNumberBefore(first.JustBefore);
Assert.That(notFound, Is.Null);
}
[Test]
public void FailsToFindBlockAfterTailOfChain_future()
{
var last = blocks.Last().Value;
var notFound = finder.GetLowestBlockNumberAfter(last.JustAfter);
Assert.That(notFound, Is.Null);
}
[Test]
public void RunThrough()
{
foreach (var pair in blocks)
{
var block = pair.Value;
AssertLink(block.Previous, finder.GetHighestBlockNumberBefore(block.JustBefore));
AssertLink(block, finder.GetHighestBlockNumberBefore(block.Time));
AssertLink(block, finder.GetHighestBlockNumberBefore(block.JustAfter));
AssertLink(block, finder.GetLowestBlockNumberAfter(block.JustBefore));
AssertLink(block, finder.GetLowestBlockNumberAfter(block.Time));
AssertLink(block.Next, finder.GetLowestBlockNumberAfter(block.JustAfter));
}
}
private void AssertLink(Block? expected, ulong? actual)
{
if (expected == null)
{
Assert.That(actual, Is.Null);
}
else
{
Assert.That(expected.Number, Is.EqualTo(actual!.Value));
}
}
}
public class Block
{
public Block(ulong number, DateTime time)
{
Number = number;
Time = time;
}
public ulong Number { get; }
public DateTime Time { get; }
public DateTime JustBefore { get { return Time.AddSeconds(-1); } }
public DateTime JustAfter { get { return Time.AddSeconds(1); } }
public Block? Next { get; set; }
public Block? Previous { get; set; }
public override string ToString()
{
return $"[{Number}]";
}
}
}
*/

View File

@ -1,30 +0,0 @@
/* MARKETPLACE REMOVED
using NUnit.Framework;
using System.Text;
using TestNetRewarder;
namespace FrameworkTests.Utils
{
[TestFixture]
public class EmojiMapsTests
{
private readonly Random random = new Random();
private readonly EmojiMaps maps = new EmojiMaps();
[Test]
public void GeneratesConsistentStrings(
[Values(1, 5, 10, 20)] int inputLength,
[Values(1, 2, 3, 5)] int outLength)
{
var buffer = new byte[inputLength];
random.NextBytes(buffer);
var input = Encoding.ASCII.GetString(buffer);
var out1 = maps.StringToEmojis(input, outLength);
var out2 = maps.StringToEmojis(input, outLength);
Assert.That(out1, Is.EqualTo(out2));
}
}
}
*/

View File

@ -1,33 +0,0 @@
/* MARKETPLACE REMOVED
using GethPlugin;
using NUnit.Framework;
namespace FrameworkTests.Utils
{
[TestFixture]
public class EthAccountEqualityTests
{
[Test]
public void Accounts()
{
var account1 = EthAccountGenerator.GenerateNew();
var account2 = EthAccountGenerator.GenerateNew();
Assert.That(account1, Is.EqualTo(account1));
Assert.That(account1 == account1);
Assert.That(account1 != account2);
}
[Test]
public void Addresses()
{
var address1 = EthAccountGenerator.GenerateNew().EthAddress;
var address2 = EthAccountGenerator.GenerateNew().EthAddress;
Assert.That(address1, Is.EqualTo(address1));
Assert.That(address1 == address1);
Assert.That(address1 != address2);
}
}
}
*/

View File

@ -1,48 +0,0 @@
/* MARKETPLACE REMOVED
using GethPlugin;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utils;
namespace FrameworkTests.Utils
{
[TestFixture]
public class EthAddressEqualityTests
{
[Test]
[Combinatorial]
public void Equal(
[Values(1, 2, 3, 4, 5)] int runs
)
{
var account = EthAccountGenerator.GenerateNew();
var str = account.EthAddress.Address;
var addr = new EthAddress(str);
Assert.That(addr, Is.EqualTo(account.EthAddress));
Assert.That(addr == account.EthAddress);
Assert.That(!(addr != account.EthAddress));
}
[Test]
[Combinatorial]
public void NotEqual(
[Values(1, 2, 3, 4, 5)] int runs
)
{
var account1 = EthAccountGenerator.GenerateNew();
var account2 = EthAccountGenerator.GenerateNew();
Assert.That(account1.EthAddress, Is.Not.EqualTo(account2.EthAddress));
Assert.That(account1.EthAddress != account2.EthAddress);
Assert.That(!(account1.EthAddress == account2.EthAddress));
}
}
}
*/