diff --git a/.github/workflows/trace-contract.yaml b/.github/workflows/trace-contract.yaml new file mode 100644 index 00000000..b09ceae1 --- /dev/null +++ b/.github/workflows/trace-contract.yaml @@ -0,0 +1,43 @@ +name: Trace Contract + +on: + workflow_dispatch: + inputs: + purchaseid: + description: "Testnet Purchase ID" + required: true + type: string + +env: + SOURCE: ${{ format('{0}/{1}', github.server_url, github.repository) }} + BRANCH: ${{ github.ref_name }} + OUTPUT_FOLDER: output + ES_USERNAME: ${{ secrets.ES_USERNAME }} + ES_PASSWORD: ${{ secrets.ES_PASSWORD }} + ES_HOST: ${{ secrets.ES_HOST }} + +jobs: + trace_contract: + name: Trace contract + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ inputs.workflow_source }} + + - name: Variables + run: | + echo "PURCHASE_ID=${{ inputs.purchaseid }}" >> $GITHUB_ENV + + - name: Run Trace + run: | + dotnet run --project Tools/TraceContract + + - name: Upload output + uses: actions/upload-artifact@v4 + with: + name: contract-trace + path: ${{ env.OUTPUT_FOLDER }}/ + if-no-files-found: error + retention-days: 7 diff --git a/Framework/BlockchainUtils/BlockTimeFinder.cs b/Framework/BlockchainUtils/BlockTimeFinder.cs index a84cda3a..ee49836c 100644 --- a/Framework/BlockchainUtils/BlockTimeFinder.cs +++ b/Framework/BlockchainUtils/BlockTimeFinder.cs @@ -20,9 +20,10 @@ namespace BlockchainUtils public BlockTimeEntry Get(ulong blockNumber) { - bounds.Initialize(); var b = cache.Get(blockNumber); if (b != null) return b; + + bounds.Initialize(); return GetBlock(blockNumber); } @@ -101,10 +102,18 @@ namespace BlockchainUtils previous.Utc < target; } - private BlockTimeEntry GetBlock(ulong number) + private BlockTimeEntry GetBlock(ulong number, bool retry = false) { if (number < bounds.Genesis.BlockNumber) throw new Exception("Can't fetch block before genesis."); - if (number > bounds.Current.BlockNumber) throw new Exception("Can't fetch block after current."); + if (number > bounds.Current.BlockNumber) + { + if (retry) throw new Exception("Can't fetch block after current."); + + // todo test and verify this: + Thread.Sleep(1000); + bounds.Initialize(); + return GetBlock(number, retry: true); + } var dateTime = web3.GetTimestampForBlock(number); if (dateTime == null) throw new Exception("Failed to get dateTime for block that should exist."); diff --git a/Framework/BlockchainUtils/BlockchainBounds.cs b/Framework/BlockchainUtils/BlockchainBounds.cs index 27328669..32ce728f 100644 --- a/Framework/BlockchainUtils/BlockchainBounds.cs +++ b/Framework/BlockchainUtils/BlockchainBounds.cs @@ -87,6 +87,8 @@ private void AddCurrentBlock() { var currentBlockNumber = web3.GetCurrentBlockNumber(); + if (Current != null && Current.BlockNumber == currentBlockNumber) return; + var blockTime = web3.GetTimestampForBlock(currentBlockNumber); if (blockTime == null) throw new Exception("Unable to get dateTime for current block."); AddCurrentBlock(currentBlockNumber, blockTime.Value); diff --git a/Framework/KubernetesWorkflow/ContainerCrashWatcher.cs b/Framework/KubernetesWorkflow/ContainerCrashWatcher.cs index f4f181aa..ff4dcc1e 100644 --- a/Framework/KubernetesWorkflow/ContainerCrashWatcher.cs +++ b/Framework/KubernetesWorkflow/ContainerCrashWatcher.cs @@ -14,6 +14,7 @@ namespace KubernetesWorkflow private CancellationTokenSource cts; private Task? worker; private Exception? workerException; + private bool hasCrashed = false; public ContainerCrashWatcher(ILog log, KubernetesClientConfiguration config, string containerName, string podName, string recipeName, string k8sNamespace) { @@ -47,10 +48,7 @@ namespace KubernetesWorkflow public bool HasCrashed() { - using var client = new Kubernetes(config); - var result = HasContainerBeenRestarted(client); - if (result) DownloadCrashedContainerLogs(client); - return result; + return hasCrashed; } private void Worker() @@ -72,6 +70,9 @@ namespace KubernetesWorkflow { if (HasContainerBeenRestarted(client)) { + hasCrashed = true; + cts.Cancel(); + DownloadCrashedContainerLogs(client); return; } diff --git a/Framework/KubernetesWorkflow/LogHandler.cs b/Framework/KubernetesWorkflow/LogHandler.cs index b8a8fa4b..84503e69 100644 --- a/Framework/KubernetesWorkflow/LogHandler.cs +++ b/Framework/KubernetesWorkflow/LogHandler.cs @@ -33,7 +33,7 @@ namespace KubernetesWorkflow sourceLog.Log(msg); LogFile.Write(msg); - LogFile.WriteRaw(description); + LogFile.Write(description); } public LogFile LogFile { get; } @@ -43,8 +43,9 @@ namespace KubernetesWorkflow // This line is not useful and has no topic so we can't filter it with // normal log-level controls. if (line.Contains("Received JSON-RPC response") && !line.Contains("topics=")) return; + if (line.Contains("object field not marked with serialize, skipping")) return; - LogFile.WriteRaw(line); + LogFile.Write(line); } } } diff --git a/Framework/Logging/BaseLog.cs b/Framework/Logging/BaseLog.cs index 56ff66ec..217b76b7 100644 --- a/Framework/Logging/BaseLog.cs +++ b/Framework/Logging/BaseLog.cs @@ -65,7 +65,7 @@ namespace Logging public void Raw(string message) { - LogFile.WriteRaw(message); + LogFile.Write(message); } public virtual void AddStringReplace(string from, string to) diff --git a/Framework/Logging/LogFile.cs b/Framework/Logging/LogFile.cs index 2ec94be0..15d2cbfc 100644 --- a/Framework/Logging/LogFile.cs +++ b/Framework/Logging/LogFile.cs @@ -1,6 +1,4 @@ -using Utils; - -namespace Logging +namespace Logging { public class LogFile { @@ -16,11 +14,6 @@ namespace Logging public string Filename { get; private set; } public void Write(string message) - { - WriteRaw($"{GetTimestamp()} {message}"); - } - - public void WriteRaw(string message) { try { @@ -50,11 +43,6 @@ namespace Logging } } - private static string GetTimestamp() - { - return $"[{Time.FormatTimestamp(DateTime.UtcNow)}]"; - } - private void EnsurePathExists(string filename) { var path = new FileInfo(filename).Directory!.FullName; diff --git a/Framework/Logging/LogPrefixer.cs b/Framework/Logging/LogPrefixer.cs index f0f303d6..30c6524d 100644 --- a/Framework/Logging/LogPrefixer.cs +++ b/Framework/Logging/LogPrefixer.cs @@ -24,17 +24,17 @@ public void Debug(string message = "", int skipFrames = 0) { - backingLog.Debug(Prefix + message, skipFrames); + backingLog.Debug(GetPrefix() + message, skipFrames); } public void Error(string message) { - backingLog.Error(Prefix + message); + backingLog.Error(GetPrefix() + message); } public void Log(string message) { - backingLog.Log(Prefix + message); + backingLog.Log(GetPrefix() + message); } public void AddStringReplace(string from, string to) @@ -51,5 +51,10 @@ { return backingLog.GetFullName(); } + + protected virtual string GetPrefix() + { + return Prefix; + } } } diff --git a/Framework/Logging/TimestampPrefixer.cs b/Framework/Logging/TimestampPrefixer.cs new file mode 100644 index 00000000..bc7c3843 --- /dev/null +++ b/Framework/Logging/TimestampPrefixer.cs @@ -0,0 +1,16 @@ +using Utils; + +namespace Logging +{ + public class TimestampPrefixer : LogPrefixer + { + public TimestampPrefixer(ILog backingLog) : base(backingLog) + { + } + + protected override string GetPrefix() + { + return $"[{Time.FormatTimestamp(DateTime.UtcNow)}]"; + } + } +} diff --git a/Framework/NethereumWorkflow/NethereumInteraction.cs b/Framework/NethereumWorkflow/NethereumInteraction.cs index 3b2c609c..47b7ee20 100644 --- a/Framework/NethereumWorkflow/NethereumInteraction.cs +++ b/Framework/NethereumWorkflow/NethereumInteraction.cs @@ -143,5 +143,16 @@ namespace NethereumWorkflow var blockTimeFinder = new BlockTimeFinder(blockCache, wrapper, log); return blockTimeFinder.Get(number); } + + public BlockWithTransactions GetBlockWithTransactions(ulong number) + { + var retry = new Retry(nameof(GetBlockWithTransactions), + maxTimeout: TimeSpan.FromMinutes(1.0), + sleepAfterFail: TimeSpan.FromSeconds(1.0), + onFail: f => { }, + failFast: false); + + return retry.Run(() => Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(number)))); + } } } diff --git a/Framework/NethereumWorkflow/Web3Wrapper.cs b/Framework/NethereumWorkflow/Web3Wrapper.cs index a68ad2e4..b7859685 100644 --- a/Framework/NethereumWorkflow/Web3Wrapper.cs +++ b/Framework/NethereumWorkflow/Web3Wrapper.cs @@ -19,23 +19,40 @@ namespace NethereumWorkflow public ulong GetCurrentBlockNumber() { - var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()); - return Convert.ToUInt64(number.ToDecimal()); + return Retry(() => + { + var number = Time.Wait(web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()); + return Convert.ToUInt64(number.ToDecimal()); + }); } public DateTime? GetTimestampForBlock(ulong blockNumber) { - try + return Retry(() => { - var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(blockNumber))); - if (block == null) return null; - return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime; - } - catch (Exception ex) - { - log.Error("Exception while getting timestamp for block: " + ex); - return null; - } + try + { + var block = Time.Wait(web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new BlockParameter(blockNumber))); + if (block == null) return null; + return DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(block.Timestamp.ToDecimal())).UtcDateTime; + } + catch (Exception ex) + { + log.Error("Exception while getting timestamp for block: " + ex); + return null; + } + }); + } + + private T Retry(Func action) + { + var retry = new Retry(nameof(Web3Wrapper), + maxTimeout: TimeSpan.FromSeconds(30), + sleepAfterFail: TimeSpan.FromSeconds(3), + onFail: f => { }, + failFast: false); + + return retry.Run(action); } } } diff --git a/Framework/Utils/ByteSize.cs b/Framework/Utils/ByteSize.cs index 9b0f7c8a..b66c952c 100644 --- a/Framework/Utils/ByteSize.cs +++ b/Framework/Utils/ByteSize.cs @@ -25,6 +25,21 @@ return new ByteSize(Convert.ToInt64(result)); } + public int DivUp(ByteSize div) + { + var d = div.SizeInBytes; + var remaining = SizeInBytes; + var result = 0; + while (remaining > d) + { + remaining -= d; + result++; + } + + if (remaining > 0) result++; + return result; + } + public override bool Equals(object? obj) { return obj is ByteSize size && SizeInBytes == size.SizeInBytes; diff --git a/Framework/Utils/EthAccount.cs b/Framework/Utils/EthAccount.cs index 0898a30a..018e43fc 100644 --- a/Framework/Utils/EthAccount.cs +++ b/Framework/Utils/EthAccount.cs @@ -1,7 +1,7 @@ namespace Utils { [Serializable] - public class EthAccount + public class EthAccount : IComparable { public EthAccount(EthAddress ethAddress, string privateKey) { @@ -12,9 +12,37 @@ public EthAddress EthAddress { get; } public string PrivateKey { get; } + public int CompareTo(EthAccount? other) + { + return PrivateKey.CompareTo(other!.PrivateKey); + } + + public override bool Equals(object? obj) + { + return obj is EthAccount token && PrivateKey == token.PrivateKey; + } + + public override int GetHashCode() + { + return HashCode.Combine(PrivateKey); + } + public override string ToString() { return EthAddress.ToString(); } + + public static bool operator ==(EthAccount? a, EthAccount? b) + { + if (ReferenceEquals(a, b)) return true; + if (ReferenceEquals(a, null)) return false; + if (ReferenceEquals(b, null)) return false; + return a.PrivateKey == b.PrivateKey; + } + + public static bool operator !=(EthAccount? a, EthAccount? b) + { + return !(a == b); + } } } diff --git a/Framework/Utils/EthAddress.cs b/Framework/Utils/EthAddress.cs index a16f4b46..88080fb9 100644 --- a/Framework/Utils/EthAddress.cs +++ b/Framework/Utils/EthAddress.cs @@ -6,7 +6,7 @@ } [Serializable] - public class EthAddress + public class EthAddress : IComparable { public EthAddress(string address) { @@ -15,10 +15,14 @@ public string Address { get; } + public int CompareTo(EthAddress? other) + { + return Address.CompareTo(other!.Address); + } + public override bool Equals(object? obj) { - return obj is EthAddress address && - Address == address.Address; + return obj is EthAddress token && Address == token.Address; } public override int GetHashCode() @@ -31,14 +35,17 @@ return Address; } - public static bool operator ==(EthAddress a, EthAddress b) + public static bool operator ==(EthAddress? a, EthAddress? b) { + if (ReferenceEquals(a, b)) return true; + if (ReferenceEquals(a, null)) return false; + if (ReferenceEquals(b, null)) return false; return a.Address == b.Address; } - public static bool operator !=(EthAddress a, EthAddress b) + public static bool operator !=(EthAddress? a, EthAddress? b) { - return a.Address != b.Address; + return !(a == b); } } } diff --git a/Framework/Utils/Str.cs b/Framework/Utils/Str.cs index aea19ec8..ab8fe46d 100644 --- a/Framework/Utils/Str.cs +++ b/Framework/Utils/Str.cs @@ -4,8 +4,11 @@ { public static string Between(string input, string open, string close) { - var openIndex = input.IndexOf(open) + open.Length; + var openI = input.IndexOf(open); + if (openI == -1) return input; + var openIndex = openI + open.Length; var closeIndex = input.LastIndexOf(close); + if (closeIndex == -1) return input; return input.Substring(openIndex, closeIndex - openIndex); } diff --git a/Framework/WebUtils/Endpoint.cs b/Framework/WebUtils/Endpoint.cs index bf1a95a1..5f727c8d 100644 --- a/Framework/WebUtils/Endpoint.cs +++ b/Framework/WebUtils/Endpoint.cs @@ -85,7 +85,7 @@ namespace WebUtils var result = Deserialize(response); if (result == null) throw new Exception("Failed to deserialize response"); return result; - }, $"HTTO-POST-JSON: {route}"); + }, $"HTTP-POST-JSON: {route}"); } public string HttpPostStream(string route, Stream stream) diff --git a/ProjectPlugins/CodexClient/CodexNode.cs b/ProjectPlugins/CodexClient/CodexNode.cs index 14c33fbb..4a444117 100644 --- a/ProjectPlugins/CodexClient/CodexNode.cs +++ b/ProjectPlugins/CodexClient/CodexNode.cs @@ -318,6 +318,14 @@ 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) diff --git a/ProjectPlugins/CodexClient/CodexTypes.cs b/ProjectPlugins/CodexClient/CodexTypes.cs index 9085aa2c..f9a0d9d2 100644 --- a/ProjectPlugins/CodexClient/CodexTypes.cs +++ b/ProjectPlugins/CodexClient/CodexTypes.cs @@ -17,6 +17,7 @@ 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() { diff --git a/ProjectPlugins/CodexClient/Mapper.cs b/ProjectPlugins/CodexClient/Mapper.cs index f72d7760..35f872ef 100644 --- a/ProjectPlugins/CodexClient/Mapper.cs +++ b/ProjectPlugins/CodexClient/Mapper.cs @@ -166,7 +166,8 @@ namespace CodexClient return new DebugInfoVersion { Version = obj.Version, - Revision = obj.Revision + Revision = obj.Revision, + Contracts = obj.Contracts }; } diff --git a/ProjectPlugins/CodexClient/openapi.yaml b/ProjectPlugins/CodexClient/openapi.yaml index fd66648e..fae38467 100644 --- a/ProjectPlugins/CodexClient/openapi.yaml +++ b/ProjectPlugins/CodexClient/openapi.yaml @@ -124,6 +124,9 @@ components: revision: type: string example: 0c647d8 + contracts: + type: string + example: 0b537c7 PeersTable: type: object @@ -202,6 +205,7 @@ components: required: - id - totalRemainingCollateral + - freeSize allOf: - $ref: "#/components/schemas/SalesAvailability" - type: object @@ -624,6 +628,26 @@ paths: "500": description: Well it was bad-bad + delete: + summary: "Deletes either a single block or an entire dataset from the local node." + tags: [Data] + operationId: deleteLocal + parameters: + - in: path + name: cid + required: true + schema: + $ref: "#/components/schemas/Cid" + description: Block or dataset to be deleted. + + responses: + "204": + description: Data was successfully deleted. + "400": + description: Invalid CID is specified + "500": + description: There was an error during deletion + "/data/{cid}/network": post: summary: "Download a file from the network to the local node if it's not available locally. Note: Download is performed async. Call can return before download is completed." diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index c099c248..5dfa0114 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -47,7 +47,7 @@ namespace CodexContractsPlugin.ChainMonitor handler = changeHandler; this.doProofPeriodMonitoring = doProofPeriodMonitoring; TotalSpan = new TimeRange(startUtc, startUtc); - PeriodMonitor = new PeriodMonitor(this.log, contracts); + PeriodMonitor = new PeriodMonitor(contracts); } public TimeRange TotalSpan { get; private set; } @@ -199,21 +199,23 @@ namespace CodexContractsPlugin.ChainMonitor private ChainStateRequest? FindRequest(IHasRequestId request) { var r = requests.SingleOrDefault(r => Equal(r.Request.RequestId, request.RequestId)); - if (r == null) + if (r != null) return r; + + try { - var blockNumber = "unknown"; - if (request is IHasBlock blk) - { - blockNumber = blk.Block.BlockNumber.ToString(); - } - - var msg = $"Received event of type '{request.GetType()}' in block '{blockNumber}' for request by Id: '{request.RequestId}'. " + - $"Failed to find request. Request creation event not seen! (Tracker start time: {TotalSpan.From})"; - + var req = contracts.GetRequest(request.RequestId); + var state = contracts.GetRequestState(req); + var newRequest = new ChainStateRequest(log, req, state); + requests.Add(newRequest); + return newRequest; + } + catch (Exception ex) + { + var msg = "Failed to get request from chain: " + ex; log.Error(msg); handler.OnError(msg); + return null; } - return r; } private bool Equal(byte[] a, byte[] b) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs index 30403d2a..603e1b4f 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/PeriodMonitor.cs @@ -1,18 +1,15 @@ -using Logging; -using Utils; +using Utils; namespace CodexContractsPlugin.ChainMonitor { public class PeriodMonitor { - private readonly ILog log; private readonly ICodexContracts contracts; private readonly List reports = new List(); private ulong? currentPeriod = null; - public PeriodMonitor(ILog log, ICodexContracts contracts) + public PeriodMonitor(ICodexContracts contracts) { - this.log = log; this.contracts = contracts; } @@ -87,6 +84,8 @@ namespace CodexContractsPlugin.ChainMonitor private void CalcStats() { IsEmpty = Reports.All(r => r.TotalProofsRequired == 0); + if (Reports.Length == 0) return; + PeriodLow = Reports.Min(r => r.PeriodNumber); PeriodHigh = Reports.Max(r => r.PeriodNumber); AverageNumSlots = Reports.Average(r => Convert.ToSingle(r.TotalNumSlots)); diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs index 14143e11..40ee264e 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsAccess.cs @@ -25,6 +25,7 @@ namespace CodexContractsPlugin ICodexContractsEvents GetEvents(BlockInterval blockInterval); EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex); RequestState GetRequestState(Request request); + Request GetRequest(byte[] requestId); ulong GetPeriodNumber(DateTime utc); void WaitUntilNextPeriod(); ProofState GetProofState(Request storageRequest, decimal slotIndex, ulong blockNumber, ulong period); @@ -123,6 +124,17 @@ namespace CodexContractsPlugin return gethNode.Call(Deployment.MarketplaceAddress, func); } + public Request GetRequest(byte[] requestId) + { + var func = new GetRequestFunction + { + RequestId = requestId + }; + + var request = gethNode.Call(Deployment.MarketplaceAddress, func); + return request.ReturnValue1; + } + public ulong GetPeriodNumber(DateTime utc) { DateTimeOffset utco = DateTime.SpecifyKind(utc, DateTimeKind.Utc); diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs index e09a0ee7..eadbf4e9 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsContainerRecipe.cs @@ -1,4 +1,5 @@ -using GethPlugin; +using CodexClient; +using GethPlugin; using KubernetesWorkflow; using KubernetesWorkflow.Recipe; @@ -8,14 +9,14 @@ namespace CodexContractsPlugin { public const string MarketplaceAddressFilename = "/hardhat/deployments/codexdisttestnetwork/Marketplace.json"; public const string MarketplaceArtifactFilename = "/hardhat/artifacts/contracts/Marketplace.sol/Marketplace.json"; - private readonly VersionRegistry versionRegistry; + private readonly DebugInfoVersion versionInfo; public override string AppName => "codex-contracts"; - public override string Image => versionRegistry.GetContractsDockerImage(); + public override string Image => GetContractsDockerImage(); - public CodexContractsContainerRecipe(VersionRegistry versionRegistry) + public CodexContractsContainerRecipe(DebugInfoVersion versionInfo) { - this.versionRegistry = versionRegistry; + this.versionInfo = versionInfo; } protected override void Initialize(StartupConfig startupConfig) @@ -28,7 +29,13 @@ namespace CodexContractsPlugin AddEnvVar("DISTTEST_NETWORK_URL", address.ToString()); 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"; + } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs index 6decfde6..6adf4ae7 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsEvents.cs @@ -19,6 +19,7 @@ namespace CodexContractsPlugin SlotFreedEventDTO[] GetSlotFreedEvents(); SlotReservationsFullEventDTO[] GetSlotReservationsFullEvents(); ProofSubmittedEventDTO[] GetProofSubmittedEvents(); + void GetReserveSlotCalls(Action onFunction); } public class CodexContractsEvents : ICodexContractsEvents @@ -99,6 +100,15 @@ namespace CodexContractsPlugin return events.Select(SetBlockOnEvent).ToArray(); } + public void GetReserveSlotCalls(Action onFunction) + { + gethNode.IterateFunctionCalls(BlockInterval, (b, fn) => + { + fn.Block = b; + onFunction(fn); + }); + } + private T SetBlockOnEvent(EventLog e) where T : IHasBlock { var result = e.Event; diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs index 6e02280d..1d122d3b 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.cs @@ -7,15 +7,11 @@ namespace CodexContractsPlugin { private readonly IPluginTools tools; private readonly CodexContractsStarter starter; - private readonly VersionRegistry versionRegistry; - private readonly CodexContractsContainerRecipe recipe; public CodexContractsPlugin(IPluginTools tools) { this.tools = tools; - versionRegistry = new VersionRegistry(tools.GetLog()); - recipe = new CodexContractsContainerRecipe(versionRegistry); - starter = new CodexContractsStarter(tools, recipe); + starter = new CodexContractsStarter(tools); } public string LogPrefix => "(CodexContracts) "; @@ -31,16 +27,16 @@ namespace CodexContractsPlugin public void AddMetadata(IAddMetadata metadata) { - metadata.Add("codexcontractsid", recipe.Image); + metadata.Add("codexcontractsid", "dynamic"); } public void Decommission() { } - public CodexContractsDeployment DeployContracts(CoreInterface ci, IGethNode gethNode) + public CodexContractsDeployment DeployContracts(CoreInterface ci, IGethNode gethNode, CodexClient.DebugInfoVersion versionInfo) { - return starter.Deploy(ci, gethNode); + return starter.Deploy(ci, gethNode, versionInfo); } public ICodexContracts WrapDeploy(IGethNode gethNode, CodexContractsDeployment deployment) @@ -48,10 +44,5 @@ namespace CodexContractsPlugin deployment = SerializeGate.Gate(deployment); return starter.Wrap(gethNode, deployment); } - - public void SetCodexDockerImageProvider(ICodexDockerImageProvider provider) - { - versionRegistry.SetProvider(provider); - } } } diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj index 24f87068..34f6cad1 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsPlugin.csproj @@ -13,6 +13,7 @@ + diff --git a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs index 5ecc22a9..c7e900be 100644 --- a/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs +++ b/ProjectPlugins/CodexContractsPlugin/CodexContractsStarter.cs @@ -1,4 +1,5 @@ -using CodexContractsPlugin.Marketplace; +using CodexClient; +using CodexContractsPlugin.Marketplace; using Core; using GethPlugin; using KubernetesWorkflow; @@ -12,15 +13,13 @@ namespace CodexContractsPlugin public class CodexContractsStarter { private readonly IPluginTools tools; - private readonly CodexContractsContainerRecipe recipe; - public CodexContractsStarter(IPluginTools tools, CodexContractsContainerRecipe recipe) + public CodexContractsStarter(IPluginTools tools) { this.tools = tools; - this.recipe = recipe; } - public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode) + public CodexContractsDeployment Deploy(CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo) { Log("Starting Codex SmartContracts container..."); @@ -28,6 +27,9 @@ namespace CodexContractsPlugin 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]; diff --git a/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs b/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs index ea123bc9..d07e25f7 100644 --- a/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs +++ b/ProjectPlugins/CodexContractsPlugin/CoreInterfaceExtensions.cs @@ -1,13 +1,14 @@ -using Core; +using CodexClient; +using Core; using GethPlugin; namespace CodexContractsPlugin { public static class CoreInterfaceExtensions { - public static CodexContractsDeployment DeployCodexContracts(this CoreInterface ci, IGethNode gethNode) + public static CodexContractsDeployment DeployCodexContracts(this CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo) { - return Plugin(ci).DeployContracts(ci, gethNode); + return Plugin(ci).DeployContracts(ci, gethNode, versionInfo); } public static ICodexContracts WrapCodexContractsDeployment(this CoreInterface ci, IGethNode gethNode, CodexContractsDeployment deployment) @@ -15,17 +16,12 @@ namespace CodexContractsPlugin return Plugin(ci).WrapDeploy(gethNode, deployment); } - public static ICodexContracts StartCodexContracts(this CoreInterface ci, IGethNode gethNode) + public static ICodexContracts StartCodexContracts(this CoreInterface ci, IGethNode gethNode, DebugInfoVersion versionInfo) { - var deployment = DeployCodexContracts(ci, gethNode); + var deployment = DeployCodexContracts(ci, gethNode, versionInfo); return WrapCodexContractsDeployment(ci, gethNode, deployment); } - public static void SetCodexDockerImageProvider(this CoreInterface ci, ICodexDockerImageProvider provider) - { - Plugin(ci).SetCodexDockerImageProvider(provider); - } - private static CodexContractsPlugin Plugin(CoreInterface ci) { return ci.GetPlugin(); diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs index a93df5d1..c69dab8b 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Customizations.cs @@ -1,5 +1,6 @@ #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 Utils; @@ -15,6 +16,11 @@ namespace CodexContractsPlugin.Marketplace byte[] RequestId { get; set; } } + public interface IHasSlotIndex + { + ulong SlotIndex { get; set; } + } + public partial class Request : RequestBase, IHasBlock, IHasRequestId { [JsonIgnore] @@ -51,20 +57,25 @@ namespace CodexContractsPlugin.Marketplace public BlockTimeEntry Block { get; set; } } - public partial class SlotFilledEventDTO : IHasBlock, IHasRequestId + public partial class SlotFilledEventDTO : IHasBlock, IHasRequestId, 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 : IHasBlock, IHasRequestId + public partial class SlotFreedEventDTO : IHasBlock, IHasRequestId, IHasSlotIndex { [JsonIgnore] public BlockTimeEntry Block { get; set; } } - public partial class SlotReservationsFullEventDTO : IHasBlock, IHasRequestId + public partial class SlotReservationsFullEventDTO : IHasBlock, IHasRequestId, IHasSlotIndex { [JsonIgnore] public BlockTimeEntry Block { get; set; } @@ -75,5 +86,11 @@ namespace CodexContractsPlugin.Marketplace [JsonIgnore] public BlockTimeEntry Block { get; set; } } + + public partial class ReserveSlotFunction : IHasBlock, IHasRequestId, IHasSlotIndex + { + [JsonIgnore] + public BlockTimeEntry Block { get; set; } + } } #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. diff --git a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs index ae309f1f..dba71566 100644 --- a/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs +++ b/ProjectPlugins/CodexContractsPlugin/Marketplace/Marketplace.cs @@ -15,7 +15,7 @@ namespace CodexContractsPlugin.Marketplace public class MarketplaceDeploymentBase : ContractDeploymentMessage { - public static string BYTECODE = "0x60c060405234801561001057600080fd5b50604051614f04380380614f0483398101604081905261002f9161053b565b602083015180516040850151516001805460ff191660ff90921691909117905582906001600160401b03811660000361007b5760405163015536c760e51b815260040160405180910390fd5b6001600160401b031660805261010043116100a9576040516338f5f66160e11b815260040160405180910390fd5b8151600280546020850151604086015160608701516001600160401b039586166001600160801b0319909416939093176801000000000000000095909216949094021761ffff60801b1916600160801b60ff9485160260ff60881b191617600160881b9390911692909202919091178155608083015183919060039061012f90826106d9565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff909116111561017d576040516302bd816360e41b815260040160405180910390fd5b606483600001516040015160ff1611156101aa576040516354e5e0ab60e11b815260040160405180910390fd5b825160408101516020909101516064916101c391610797565b60ff1611156101e5576040516317ff9d0f60e21b815260040160405180910390fd5b82518051600b805460208085015160408087015160609788015160ff90811663010000000263ff0000001992821662010000029290921663ffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600c80549383015196830151978301518516600160881b0260ff60881b1998909516600160801b029790971661ffff60801b196001600160401b0397881668010000000000000000026001600160801b031990951697909216969096179290921791909116939093171783556080820151869391929190600d906102d090826106d9565b50505060408201515160038201805460ff191660ff909216919091179055606090910151600490910180546001600160401b0319166001600160401b03909216919091179055506107c8915050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156103575761035761031f565b60405290565b604051608081016001600160401b03811182821017156103575761035761031f565b604051601f8201601f191681016001600160401b03811182821017156103a7576103a761031f565b604052919050565b805160ff811681146103c057600080fd5b919050565b80516001600160401b03811681146103c057600080fd5b600060a082840312156103ee57600080fd5b6103f6610335565b9050610401826103c5565b815261040f602083016103c5565b6020820152610420604083016103af565b6040820152610431606083016103af565b606082015260808201516001600160401b0381111561044f57600080fd5b8201601f8101841361046057600080fd5b80516001600160401b038111156104795761047961031f565b61048c601f8201601f191660200161037f565b8181528560208385010111156104a157600080fd5b60005b828110156104c0576020818501810151838301820152016104a4565b5060006020838301015280608085015250505092915050565b6000602082840312156104eb57600080fd5b604051602081016001600160401b038111828210171561050d5761050d61031f565b60405290508061051c836103af565b905292915050565b80516001600160a01b03811681146103c057600080fd5b60008060006060848603121561055057600080fd5b83516001600160401b0381111561056657600080fd5b840180860360e081121561057957600080fd5b61058161035d565b608082121561058f57600080fd5b61059761035d565b91506105a2836103af565b82526105b0602084016103af565b60208301526105c1604084016103af565b60408301526105d2606084016103af565b60608301529081526080820151906001600160401b038211156105f457600080fd5b610600888385016103dc565b60208201526106128860a085016104d9565b604082015261062360c084016103c5565b6060820152945061063991505060208501610524565b915061064760408501610524565b90509250925092565b600181811c9082168061066457607f821691505b60208210810361068457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156106d457806000526020600020601f840160051c810160208510156106b15750805b601f840160051c820191505b818110156106d157600081556001016106bd565b50505b505050565b81516001600160401b038111156106f2576106f261031f565b610706816107008454610650565b8461068a565b6020601f82116001811461073a57600083156107225750848201515b600019600385901b1c1916600184901b1784556106d1565b600084815260208120601f198516915b8281101561076a578785015182556020948501946001909201910161074a565b50848210156107885786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b60ff81811683821602908116908181146107c157634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a0516146df610825600039600081816104bf01528181610f7001528181612019015281816125cd0152818161267d0152818161280c015281816128bc0152612ce601526000818161355001526137e001526146df6000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80636e2b54ee116100f9578063c0cc4add11610097578063e8aa0a0711610071578063e8aa0a0714610461578063f752196b14610474578063fb1e61ca1461049d578063fc0c546a146104bd57600080fd5b8063c0cc4add14610428578063c5d433511461043b578063d02bbe331461044e57600080fd5b8063a29c29a4116100d3578063a29c29a4146103b2578063a3a0807e146103c5578063b396dc79146103e8578063be5cdc481461040857600080fd5b80636e2b54ee146103845780639777b72c1461039757806399b6da0c1461039f57600080fd5b8063329b5a0b1161016657806351a766421161014057806351a76642146103035780635da73835146103165780636b00c8cf1461032b5780636c70bee91461036f57600080fd5b8063329b5a0b14610298578063458d2bf1146102cb5780634641dce6146102de57600080fd5b806312827602116101a2578063128276021461022e5780631d873c1b14610241578063237d84821461025457806326d6f8341461026757600080fd5b806302fa8e65146101c957806305b90773146101f95780630aefaabe14610219575b600080fd5b6101dc6101d73660046138ad565b6104e3565b6040516001600160401b0390911681526020015b60405180910390f35b61020c6102073660046138ad565b6105c1565b6040516101f091906138dc565b61022c61022736600461390b565b6106e4565b005b61022c61023c366004613972565b610877565b61022c61024f3660046139b5565b610948565b61022c610262366004613972565b610dfa565b61028a6102753660046138ad565b60009081526012602052604090206003015490565b6040519081526020016101f0565b6101dc6102a63660046138ad565b600090815260116020526040902060020154600160c01b90046001600160401b031690565b61028a6102d93660046138ad565b611046565b6102f16102ec3660046138ad565b61105f565b60405160ff90911681526020016101f0565b61028a6103113660046138ad565b611072565b61031e6110d1565b6040516101f091906139f5565b6103576103393660046138ad565b6000908152601260205260409020600401546001600160a01b031690565b6040516001600160a01b0390911681526020016101f0565b6103776110f8565b6040516101f09190613ad3565b61022c6103923660046138ad565b61126f565b61031e61127c565b61022c6103ad366004613b5b565b61129b565b61022c6103c03660046138ad565b6117e1565b6103d86103d33660046138ad565b611833565b60405190151581526020016101f0565b6103fb6103f63660046138ad565b61186f565b6040516101f09190613c8a565b61041b6104163660046138ad565b611b51565b6040516101f09190613cc5565b6103d86104363660046138ad565b611c1f565b61022c610449366004613cd9565b611c32565b6103d861045c366004613972565b6120b3565b61022c61046f366004613cfe565b61211f565b6101dc6104823660046138ad565b6000908152600660205260409020546001600160401b031690565b6104b06104ab3660046138ad565b612298565b6040516101f09190613d2c565b7f0000000000000000000000000000000000000000000000000000000000000000610357565b6000806104ef836105c1565b90506000816004811115610505576105056138c6565b148061052257506001816004811115610520576105206138c6565b145b1561054e575050600090815260116020526040902060020154600160801b90046001600160401b031690565b6002816004811115610562576105626138c6565b0361058e575050600090815260116020526040902060020154600160c01b90046001600160401b031690565b6000838152601160205260409020600201546105ba90600160801b90046001600160401b0316426124ae565b9392505050565b60008181526010602052604081205482906001600160a01b03166105f857604051635eeb253d60e11b815260040160405180910390fd5b600083815260116020526040812090815460ff16600481111561061d5761061d6138c6565b14801561065c5750600084815260116020526040902060020154600160c01b90046001600160401b03166001600160401b0316426001600160401b0316115b1561066b5760029250506106de565b6001815460ff166004811115610683576106836138c6565b14806106a457506000815460ff1660048111156106a2576106a26138c6565b145b80156106c8575060028101546001600160401b03600160801b909104811642909116115b156106d75760039250506106de565b5460ff1691505b50919050565b826000808281526012602052604090205460ff166006811115610709576107096138c6565b0361072757604051638b41ec7f60e01b815260040160405180910390fd5b600084815260126020526040902060048101546001600160a01b0316331461077b576040517f57a6f4e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061078686611b51565b9050600481600681111561079c5761079c6138c6565b036107d3576040517fc2cbf77700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028160068111156107e7576107e76138c6565b03610801576107fc82600101548787876124be565b61086f565b6005816006811115610815576108156138c6565b0361082a576107fc8260010154878787612707565b600381600681111561083e5761083e6138c6565b0361084d576107fc3387612950565b6001816006811115610861576108616138c6565b0361086f5761086f86612972565b505050505050565b61088182826120b3565b6108b7576040517f424a04ab00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006108c38383612bca565b60008181526020819052604090209091506108de9033612c0f565b50600154600082815260208190526040902060ff909116906108ff90612c24565b03610943576040516001600160401b038316815283907fc8e6c955744189a19222ec226b72ac1435d88d5745252dac56e6f679f64c037a9060200160405180910390a25b505050565b60008381526010602052604090205483906001600160a01b031661097f57604051635eeb253d60e11b815260040160405180910390fd5b600084815260106020526040902060048101546001600160401b03908116908516106109d7576040517f3b920b8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109e38686612bca565b60008181526020819052604090209091506109fe9033612c2e565b610a34576040517fd651ce1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601260209081526040808320600181018a90556002810180546fffffffffffffffff00000000000000001916600160401b6001600160401b038c1602179055898452601190925282209091610a8d84611b51565b6006811115610a9e57610a9e6138c6565b14158015610ac657506006610ab284611b51565b6006811115610ac357610ac36138c6565b14155b15610afd576040517fff556acf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60048201805473ffffffffffffffffffffffffffffffffffffffff1916331790556002820180546001600160401b03421667ffffffffffffffff19909116179055610b6e83600090815260056020526040902080546001600160401b03421667ffffffffffffffff19909116179055565b610b78838761211f565b60028101805460019190600090610b999084906001600160401b0316613d55565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610bde888360020160009054906101000a90046001600160401b0316612c50565b816001016000828254610bf19190613d74565b90915550506040805160e081018252600186015481526002860154602082015260038601549181019190915260048501546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526000908190610c6a90612c7f565b90506006610c7786611b51565b6006811115610c8857610c886138c6565b03610cbb57600b54606490610ca09060ff1683613d87565b610caa9190613db4565b610cb49082613d74565b9150610cbf565b8091505b610cc93383612c9e565b8160136000016000828254610cde9190613dc8565b9091555050600384018190556004840154610d02906001600160a01b031686612d72565b835460ff191660011784556040516001600160401b038a1681528a907f8f301470a994578b52323d625dfbf827ca5208c81747d3459be7b8867baec3ec9060200160405180910390a2600486015460028401546001600160401b039081169116148015610d8457506000835460ff166004811115610d8257610d826138c6565b145b15610dee57825460ff191660011783556002830180546001600160401b034216600160401b026fffffffffffffffff0000000000000000199091161790556040518a907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b50505050505050505050565b6001610e0583611b51565b6006811115610e1657610e166138c6565b14610e4d576040517fae9dcffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e578282612d94565b6000828152601260209081526040808320600180820154855260108452828520600b54845160e08101865292820154835260028201549583019590955260038101549382019390935260048301546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c0820152909391926064916201000090910460ff1690610efa90612c7f565b610f049190613d87565b610f0e9190613db4565b600b54909150600090606490610f2e906301000000900460ff1684613d87565b610f389190613db4565b90508060136001016000828254610f4f9190613dc8565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610fc1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe59190613ddb565b610ff157610ff1613dfd565b818460030160008282546110059190613d74565b9091555050600b5460008781526006602052604090205461010090910460ff16906001600160401b03166001600160401b03161061086f5761086f86612972565b600061105982611054612fab565b612fb6565b92915050565b60006110598261106d612fab565b612fca565b60008181526012602090815260408083206001810154845260109092528220600c54610100906110ac90600160801b900460ff1682613e13565b60018301546110bf9161ffff1690613d87565b6110c99190613db4565b949350505050565b336000908152600a602052604090206060906110f3906110f09061305c565b90565b905090565b611100613805565b604080516101008082018352600b805460ff8082166080808701918252948304821660a080880191909152620100008404831660c08801526301000000909304821660e0870152855285519182018652600c80546001600160401b038082168552600160401b820416602085810191909152600160801b82048416988501989098527101000000000000000000000000000000000090049091166060830152600d80549596939593870194929391928401916111bb90613e2d565b80601f01602080910402602001604051908101604052809291908181526020018280546111e790613e2d565b80156112345780601f1061120957610100808354040283529160200191611234565b820191906000526020600020905b81548152906001019060200180831161121757829003601f168201915b5050509190925250505081526040805160208181018352600385015460ff1682528301526004909201546001600160401b0316910152919050565b6112798133611c32565b50565b3360009081526009602052604090206060906110f3906110f09061305c565b60006112ae6112a983613fbe565b613069565b9050336112be60208401846140c7565b6001600160a01b0316146112e5576040516334c69e3160e11b815260040160405180910390fd5b6000818152601060205260409020546001600160a01b031615611334576040517ffc7d069000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611346610140830161012084016140e4565b6001600160401b0316158061138d575061136660e0830160c084016140e4565b6001600160401b0316611381610140840161012085016140e4565b6001600160401b031610155b156113c4576040517fdf63f61a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113d460a08301608084016140e4565b6001600160401b0316600003611416576040517f535ed2be00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142660a08301608084016140e4565b6001600160401b0316611440610100840160e085016140e4565b6001600160401b03161115611481576040517fb9551ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61149160e0830160c084016140e4565b6001600160401b03166000036114d3576040517f090a5ecd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020820135600003611511576040517f6aba7aae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082013560000361154f576040517ffb7df0c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604082013560000361158d576040517f47ba51c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61159b610100830183614101565b6115a59080614121565b90506000036115e0576040517f86f8cf9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600f546001600160401b03166115fc60e0840160c085016140e4565b6001600160401b0316111561163d576040517f1267b3f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601060205260409020829061165782826142c1565b5061166a905060e0830160c084016140e4565b6116749042613d55565b600082815260116020526040902060020180546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556116c1610140830161012084016140e4565b6116cb9042613d55565b600082815260116020908152604090912060020180546001600160401b0393909316600160c01b0277ffffffffffffffffffffffffffffffffffffffffffffffff9093169290921790915561172c90611726908401846140c7565b82613099565b600061173f61173a84613fbe565b6130bb565b600083815260116020526040812060010182905560138054929350839290919061176a908490613dc8565b9091555061177a90503382612c9e565b6000828152601160209081526040918290206002015491517f1bf9c457accf8703dbf7cdf1b58c2f74ddf2e525f98155c70b3d318d74609bd8926117d492869290880191600160c01b90046001600160401b031690614475565b60405180910390a1505050565b806000808281526012602052604090205460ff166006811115611806576118066138c6565b0361182457604051638b41ec7f60e01b815260040160405180910390fd5b61182f8233336106e4565b5050565b600080600061184984611844612fab565b6130f7565b90925090508180156110c95750600254600160801b900460ff9081169116109392505050565b6118f2604051806040016040528061386e6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b816000808281526012602052604090205460ff166006811115611917576119176138c6565b0361193557604051638b41ec7f60e01b815260040160405180910390fd5b60008381526012602052604090206119c6604051806040016040528061386e6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b600180830154600090815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e08101865295830154865260028301548685015260038301548686015260048301546001600160401b038082166060890152600160401b820481166080890152600160801b8204811692880192909252600160c01b90041660c0860152918201939093528151808301835260058401805492949385019282908290611a7b90613e2d565b80601f0160208091040260200160405190810160405280929190818152602001828054611aa790613e2d565b8015611af45780601f10611ac957610100808354040283529160200191611af4565b820191906000526020600020905b815481529060010190602001808311611ad757829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0390811683830152600890930154604090920191909152918352600290930154600160401b900490921691810191909152915050919050565b600081815260126020526040812060018101548203611b735750600092915050565b6000611b8282600101546105c1565b90506004825460ff166006811115611b9c57611b9c6138c6565b03611bab575060049392505050565b6002816004811115611bbf57611bbf6138c6565b03611bce575060059392505050565b6003816004811115611be257611be26138c6565b03611bf1575060029392505050565b6004816004811115611c0557611c056138c6565b03611c14575060039392505050565b505460ff1692915050565b600061105982611c2d612fab565b6131af565b60008281526010602052604090205482906001600160a01b0316611c6957604051635eeb253d60e11b815260040160405180910390fd5b6000838152601060209081526040808320601190925290912081546001600160a01b03163314611cac576040516334c69e3160e11b815260040160405180910390fd5b6000611cb7866105c1565b90506002816004811115611ccd57611ccd6138c6565b14158015611ced57506004816004811115611cea57611cea6138c6565b14155b8015611d0b57506003816004811115611d0857611d086138c6565b14155b15611d42576040517fc00b5b5700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010154600003611d80576040517fbd8bdd9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002816004811115611d9457611d946138c6565b03611e3257815460ff1916600217825560405186907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600086815260116020526040902060020154611dfc908790600160c01b90046001600160401b0316612c50565b6002830154611e1491906001600160401b0316613d87565b826001016000828254611e279190613dc8565b90915550611fbf9050565b6004816004811115611e4657611e466138c6565b03611fb3576040805160a0808201835285546001600160a01b03168252825160e08101845260018701548152600287015460208281019190915260038801548286015260048801546001600160401b038082166060850152600160401b820481166080850152600160801b8204811694840194909452600160c01b900490921660c08201529082015281518083018352600586018054611fa994889390850192909182908290611ef590613e2d565b80601f0160208091040260200160405190810160405280929190818152602001828054611f2190613e2d565b8015611f6e5780601f10611f4357610100808354040283529160200191611f6e565b820191906000526020600020905b815481529060010190602001808311611f5157829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101526130bb565b6001830155611fbf565b815460ff191660031782555b8254611fd4906001600160a01b0316876131e6565b600182015460148054829190600090611fee908490613dc8565b909155505060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612062573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120869190613ddb565b6120a357604051637c2ccffd60e11b815260040160405180910390fd5b5050600060019091015550505050565b600033816120c18585612bca565b90506120cc81613208565b80156120f55750600154600082815260208190526040902060ff909116906120f390612c24565b105b8015612116575060008181526020819052604090206121149083612c2e565b155b95945050505050565b6000828152601260209081526040808320600101548084526010909252909120546001600160a01b031661216657604051635eeb253d60e11b815260040160405180910390fd5b600083815260126020526040902060048101546001600160a01b031633146121ba576040517fce351b9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600181015460009081526010602052604080822081516003808252608082019093529092918160200160208202803683370190505090506122026121fd87611046565b613235565b8160008151811061221557612215614522565b6020908102919091010152600682015461222e90613246565b8160018151811061224157612241614522565b6020026020010181815250508260020160089054906101000a90046001600160401b03166001600160401b03168160028151811061228157612281614522565b60200260200101818152505061086f868683613252565b61230d6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b60008281526010602052604090205482906001600160a01b031661234457604051635eeb253d60e11b815260040160405180910390fd5b600083815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186526001840154815260028401548186015260038401548187015260048401546001600160401b038082166060840152600160401b820481166080840152600160801b8204811693830193909352600160c01b900490911660c082015292810192909252825180840184526005820180549394929392850192829082906123f890613e2d565b80601f016020809104026020016040519081016040528092919081815260200182805461242490613e2d565b80156124715780601f1061244657610100808354040283529160200191612471565b820191906000526020600020905b81548152906001019060200180831161245457829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101529392505050565b60008282188284100282186105ba565b60008481526010602052604090205484906001600160a01b03166124f557604051635eeb253d60e11b815260040160405180910390fd5b600085815260116020908152604080832060108352818420815460ff19166003178255888552601290935292208154612537906001600160a01b0316896131e6565b600481015461254f906001600160a01b031688612950565b600281015460009061256b908a906001600160401b0316612c50565b600383015490915061257d8183613dc8565b6014805460009061258f908490613dc8565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038981166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612616573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061263a9190613ddb565b61265757604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038881166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156126c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126ea9190613ddb565b610dee57604051637c2ccffd60e11b815260040160405180910390fd5b60008481526010602052604090205484906001600160a01b031661273e57604051635eeb253d60e11b815260040160405180910390fd5b60008481526012602052604090206004810154612764906001600160a01b031686612950565b60028101546000906127aa9088906001600160401b03166127a5826000908152601160205260409020600201546001600160401b03600160c01b9091041690565b6133f0565b60038301549091506127bc8183613dc8565b601480546000906127ce908490613dc8565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612855573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128799190613ddb565b61289657604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038681166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612905573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129299190613ddb565b61294657604051637c2ccffd60e11b815260040160405180910390fd5b5050505050505050565b6001600160a01b0382166000908152600a6020526040902061094390826134cf565b600081815260126020908152604080832060018101548085526011909352922060028301546129ab9083906001600160401b0316612c50565b8160010160008282546129be9190613dc8565b909155505060048301546129db906001600160a01b031685612950565b60008481526020819052604081209081816129f6828261387b565b5050845460ff1916600617855550506002808401805467ffffffffffffffff1916905560006003850181905560048501805473ffffffffffffffffffffffffffffffffffffffff19169055908201805460019290612a5e9084906001600160401b0316614538565b82546101009290920a6001600160401b038181021990931691831602179091556002850154604051600160401b90910490911681528391507f33ba8f7627565d89f7ada2a6b81ea532b7aa9b11e91a78312d6e1fca0bfcd1dc9060200160405180910390a26000848152600660205260409020805467ffffffffffffffff19169055600082815260106020526040812060028301546004820154919291612b11916001600160401b039081169116614538565b60048301546001600160401b039182169250600160c01b90041681118015612b4e57506001835460ff166004811115612b4c57612b4c6138c6565b145b1561086f57825460ff19166004178355612b69600142614538565b6002840180546001600160401b0392909216600160801b0267ffffffffffffffff60801b1990921691909117905560405184907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a2505050505050565b60008282604051602001612bf19291909182526001600160401b0316602082015260400190565b60405160208183030381529060405280519060200120905092915050565b60006105ba836001600160a01b0384166134db565b6000611059825490565b6001600160a01b038116600090815260018301602052604081205415156105ba565b6000828152601160205260408120600201546105ba9084908490600160801b90046001600160401b03166133f0565b600081608001516001600160401b031682604001516110599190613d87565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015612d31573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d559190613ddb565b61094357604051637c2ccffd60e11b815260040160405180910390fd5b6001600160a01b0382166000908152600a60205260409020610943908261352a565b6000612d9f82613536565b6001600160401b03169050428110612de3576040517f6b4b1a4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254612e0090600160401b90046001600160401b031682613dc8565b4210612e38576040517fde55698e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602090815260408083206001600160401b038616845290915290205460ff1615612e94576040517efab7d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612e9e83836131af565b612ed4576040517fd3ffa66b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038616845290915290205460ff1615612f31576040517f98e7e55100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038087168552908352818420805460ff1916600190811790915587855260069093529083208054929390929091612f8291859116613d55565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b60006110f342613549565b60006105ba612fc58484612fca565b613575565b600080612fd961010043614557565b600254909150600090610100906130089071010000000000000000000000000000000000900460ff168661456b565b613012919061458d565b6001600160401b03169050600061302b61010087614557565b905060006101008261303d8587613dc8565b6130479190613dc8565b6130519190614557565b979650505050505050565b606060006105ba836135cf565b60008160405160200161307c9190613d2c565b604051602081830303815290604052805190602001209050919050565b6001600160a01b0382166000908152600960205260409020610943908261352a565b60006130ca826020015161362b565b602083015160a08101516060909101516130e4919061456b565b6001600160401b03166110599190613d87565b600080600061310585611b51565b6000868152600560205260408120549192509061312a906001600160401b0316613549565b90506001826006811115613140576131406138c6565b1415806131545750613152858261364a565b155b15613167576000809350935050506131a8565b6131718686612fca565b9250600061317e84613575565b9050600061318b88611072565b90508015806131a1575061319f8183614557565b155b9550505050505b9250929050565b60008060006131be85856130f7565b90925090508180156121165750600254600160801b900460ff90811691161015949350505050565b6001600160a01b038216600090815260096020526040902061094390826134cf565b60008060008381526012602052604090205460ff16600681111561322e5761322e6138c6565b1492915050565b600060ff198216816110c982613660565b6000806105ba83613660565b600083815260076020526040812090613269612fab565b6001600160401b0316815260208101919091526040016000205460ff16156132bd576040517f3edef7db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d916133079186918691016145bb565b602060405180830381865afa158015613324573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906133489190613ddb565b61337e576040517ffcd03a4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000838152600760205260408120600191613397612fab565b6001600160401b031681526020808201929092526040908101600020805460ff19169315159390931790925590518481527f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d91016117d4565b60008381526010602052604081206001600160401b0380841690851610613443576040517f56607cb000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160e081018252600183015481526002830154602082015260038301549181019190915260048201546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526134b29061362b565b6134bc8585614538565b6001600160401b03166121169190613d87565b60006105ba83836136d2565b600081815260018301602052604081205461352257508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611059565b506000611059565b60006105ba83836134db565b6000611059613544836137cc565b6137d9565b60006110597f000000000000000000000000000000000000000000000000000000000000000083614665565b60008060ff8316613587600143613d74565b6135919190613d74565b40905060008190036135a5576135a5613dfd565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561361f57602002820191906000526020600020905b81548152602001906001019080831161360b575b50505050509050919050565b600081608001516001600160401b031682602001516110599190613d87565b60006001600160401b03808416908316106105ba565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b60208110156106de57600891821c916136a0908290613d87565b83901b7fff00000000000000000000000000000000000000000000000000000000000000169190911790600101613686565b600081815260018301602052604081205480156137bb5760006136f6600183613d74565b855490915060009061370a90600190613d74565b905080821461376f57600086600001828154811061372a5761372a614522565b906000526020600020015490508087600001848154811061374d5761374d614522565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061378057613780614693565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611059565b6000915050611059565b5092915050565b6000611059826001613d55565b60006110597f00000000000000000000000000000000000000000000000000000000000000008361456b565b60408051610100810182526000608080830182815260a080850184905260c0850184905260e08501849052908452845190810185528281526020808201849052818601849052606080830185905292820192909252818401528351908101845290815290918201905b8152600060209091015290565b508054600082559060005260206000209081019061127991905b808211156138a95760008155600101613895565b5090565b6000602082840312156138bf57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b60208101600583106138f0576138f06138c6565b91905290565b6001600160a01b038116811461127957600080fd5b60008060006060848603121561392057600080fd5b833592506020840135613932816138f6565b91506040840135613942816138f6565b809150509250925092565b6001600160401b038116811461127957600080fd5b803561396d8161394d565b919050565b6000806040838503121561398557600080fd5b8235915060208301356139978161394d565b809150509250929050565b600061010082840312156106de57600080fd5b600080600061014084860312156139cb57600080fd5b8335925060208401356139dd8161394d565b91506139ec85604086016139a2565b90509250925092565b602080825282518282018190526000918401906040840190835b81811015613a2d578351835260209384019390920191600101613a0f565b509095945050505050565b6000815180845260005b81811015613a5e57602081850181015186830182015201613a42565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160401b0381511682526001600160401b03602082015116602083015260ff604082015116604083015260ff60608201511660608301526000608082015160a060808501526110c960a0850182613a38565b602081526000825160ff815116602084015260ff602082015116604084015260ff604082015116606084015260ff606082015116608084015250602083015160e060a0840152613b27610100840182613a7e565b90506040840151613b3e60c08501825160ff169052565b5060608401516001600160401b03811660e0850152509392505050565b600060208284031215613b6d57600080fd5b81356001600160401b03811115613b8357600080fd5b820161016081850312156105ba57600080fd5b6000815160408452613bab6040850182613a38565b602093840151949093019390935250919050565b6001600160a01b038151168252600060208201518051602085015260208101516040850152604081015160608501526001600160401b0360608201511660808501526001600160401b0360808201511660a08501526001600160401b0360a08201511660c08501526001600160401b0360c08201511660e0850152506040820151610160610100850152613c57610160850182613b96565b90506060830151613c746101208601826001600160401b03169052565b5060808301516101408501528091505092915050565b602081526000825160406020840152613ca66060840182613bbf565b90506001600160401b0360208501511660408401528091505092915050565b60208101600783106138f0576138f06138c6565b60008060408385031215613cec57600080fd5b823591506020830135613997816138f6565b6000806101208385031215613d1257600080fd5b82359150613d2384602085016139a2565b90509250929050565b6020815260006105ba6020830184613bbf565b634e487b7160e01b600052601160045260246000fd5b6001600160401b03818116838216019081111561105957611059613d3f565b8181038181111561105957611059613d3f565b808202811582820484141761105957611059613d3f565b634e487b7160e01b600052601260045260246000fd5b600082613dc357613dc3613d9e565b500490565b8082018082111561105957611059613d3f565b600060208284031215613ded57600080fd5b815180151581146105ba57600080fd5b634e487b7160e01b600052600160045260246000fd5b61ffff828116828216039081111561105957611059613d3f565b600181811c90821680613e4157607f821691505b6020821081036106de57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715613e9957613e99613e61565b60405290565b60405160a081016001600160401b0381118282101715613e9957613e99613e61565b60405160e081016001600160401b0381118282101715613e9957613e99613e61565b604051601f8201601f191681016001600160401b0381118282101715613f0b57613f0b613e61565b604052919050565b600060408284031215613f2557600080fd5b613f2d613e77565b905081356001600160401b03811115613f4557600080fd5b8201601f81018413613f5657600080fd5b80356001600160401b03811115613f6f57613f6f613e61565b613f82601f8201601f1916602001613ee3565b818152856020838501011115613f9757600080fd5b81602084016020830137600060209282018301528352928301359282019290925292915050565b6000813603610160811215613fd257600080fd5b613fda613e9f565b8335613fe5816138f6565b815260e0601f1983011215613ff957600080fd5b614001613ec1565b6020858101358252604080870135918301919091526060860135908201529150608084013561402f8161394d565b606083015260a08401356140428161394d565b608083015260c08401356140558161394d565b60a083015260e08401356140688161394d565b60c08301526020810191909152610100830135906001600160401b0382111561409057600080fd5b61409c36838601613f13565b60408201526140ae6101208501613962565b6060820152610140939093013560808401525090919050565b6000602082840312156140d957600080fd5b81356105ba816138f6565b6000602082840312156140f657600080fd5b81356105ba8161394d565b60008235603e1983360301811261411757600080fd5b9190910192915050565b6000808335601e1984360301811261413857600080fd5b8301803591506001600160401b0382111561415257600080fd5b6020019150368190038213156131a857600080fd5b600081356110598161394d565b601f82111561094357806000526020600020601f840160051c8101602085101561419b5750805b601f840160051c820191505b818110156141bb57600081556001016141a7565b5050505050565b8135601e198336030181126141d657600080fd5b820180356001600160401b03811180156141ef57600080fd5b81360360208401131561420157600080fd5b60009050614219826142138654613e2d565b86614174565b80601f83116001811461424e578284156142365750848201602001355b600019600386901b1c1916600185901b1786556142ad565b600086815260209020601f19851690845b828110156142815760208589018101358355948501946001909201910161425f565b50858210156142a15760001960f88760031b161c19602085890101351681555b505060018460011b0186555b505050505060209190910135600190910155565b81356142cc816138f6565b815473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03919091161781556020820135600182015560408201356002820155606082013560038201556004810160808301356143248161394d565b815467ffffffffffffffff19166001600160401b0382161782555060a083013561434d8161394d565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161781556143c161438c60c08501614167565b825467ffffffffffffffff60801b191660809190911b77ffffffffffffffff0000000000000000000000000000000016178255565b6144196143d060e08501614167565b825477ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7fffffffffffffffff00000000000000000000000000000000000000000000000016178255565b5061443461442b610100840184614101565b600583016141c2565b6144656144446101208401614167565b600783016001600160401b0382166001600160401b03198254161781555050565b6101409190910135600890910155565b83815282356020808301919091528301356040808301919091528301356060808301919091526101208201908401356144ad8161394d565b6001600160401b03811660808401525060808401356144cb8161394d565b6001600160401b03811660a08401525060a08401356144e98161394d565b6001600160401b03811660c08401525061450560c08501613962565b6001600160401b0390811660e084015283166101008301526110c9565b634e487b7160e01b600052603260045260246000fd5b6001600160401b03828116828216039081111561105957611059613d3f565b60008261456657614566613d9e565b500690565b6001600160401b0381811683821602908116908181146137c5576137c5613d3f565b60006001600160401b038316806145a6576145a6613d9e565b806001600160401b0384160691505092915050565b8235815260208084013590820152600061012082016145ea604084016040870180358252602090810135910152565b614604608084016080870180358252602090810135910152565b61461e60c0840160c0870180358252602090810135910152565b610120610100840152835190819052602084019061014084019060005b8181101561465957835183526020938401939092019160010161463b565b50909695505050505050565b60006001600160401b0383168061467e5761467e613d9e565b806001600160401b0384160491505092915050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212206b0b03ebd7dbbc3b4661bc03194b4eeec33ef1f5a894e376e036a4d4e8a6908f64736f6c634300081c0033"; + public static string BYTECODE = "0x60c060405234801561001057600080fd5b50604051614f41380380614f4183398101604081905261002f9161053b565b602083015180516040850151516001805460ff191660ff90921691909117905582906001600160401b03811660000361007b5760405163015536c760e51b815260040160405180910390fd5b6001600160401b031660805261010043116100a9576040516338f5f66160e11b815260040160405180910390fd5b8151600280546020850151604086015160608701516001600160401b039586166001600160801b0319909416939093176801000000000000000095909216949094021761ffff60801b1916600160801b60ff9485160260ff60881b191617600160881b9390911692909202919091178155608083015183919060039061012f90826106d9565b5050600480546001600160a01b0319166001600160a01b0393841617905550831660a05250825151606460ff909116111561017d576040516302bd816360e41b815260040160405180910390fd5b606483600001516040015160ff1611156101aa576040516354e5e0ab60e11b815260040160405180910390fd5b825160408101516020909101516064916101c391610797565b60ff1611156101e5576040516317ff9d0f60e21b815260040160405180910390fd5b82518051600b805460208085015160408087015160609788015160ff90811663010000000263ff0000001992821662010000029290921663ffff0000199482166101000261ffff1990971698821698909817959095179290921695909517178355808801518051600c80549383015196830151978301518516600160881b0260ff60881b1998909516600160801b029790971661ffff60801b196001600160401b0397881668010000000000000000026001600160801b031990951697909216969096179290921791909116939093171783556080820151869391929190600d906102d090826106d9565b50505060408201515160038201805460ff191660ff909216919091179055606090910151600490910180546001600160401b0319166001600160401b03909216919091179055506107c8915050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156103575761035761031f565b60405290565b604051608081016001600160401b03811182821017156103575761035761031f565b604051601f8201601f191681016001600160401b03811182821017156103a7576103a761031f565b604052919050565b805160ff811681146103c057600080fd5b919050565b80516001600160401b03811681146103c057600080fd5b600060a082840312156103ee57600080fd5b6103f6610335565b9050610401826103c5565b815261040f602083016103c5565b6020820152610420604083016103af565b6040820152610431606083016103af565b606082015260808201516001600160401b0381111561044f57600080fd5b8201601f8101841361046057600080fd5b80516001600160401b038111156104795761047961031f565b61048c601f8201601f191660200161037f565b8181528560208385010111156104a157600080fd5b60005b828110156104c0576020818501810151838301820152016104a4565b5060006020838301015280608085015250505092915050565b6000602082840312156104eb57600080fd5b604051602081016001600160401b038111828210171561050d5761050d61031f565b60405290508061051c836103af565b905292915050565b80516001600160a01b03811681146103c057600080fd5b60008060006060848603121561055057600080fd5b83516001600160401b0381111561056657600080fd5b840180860360e081121561057957600080fd5b61058161035d565b608082121561058f57600080fd5b61059761035d565b91506105a2836103af565b82526105b0602084016103af565b60208301526105c1604084016103af565b60408301526105d2606084016103af565b60608301529081526080820151906001600160401b038211156105f457600080fd5b610600888385016103dc565b60208201526106128860a085016104d9565b604082015261062360c084016103c5565b6060820152945061063991505060208501610524565b915061064760408501610524565b90509250925092565b600181811c9082168061066457607f821691505b60208210810361068457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156106d457806000526020600020601f840160051c810160208510156106b15750805b601f840160051c820191505b818110156106d157600081556001016106bd565b50505b505050565b81516001600160401b038111156106f2576106f261031f565b610706816107008454610650565b8461068a565b6020601f82116001811461073a57600083156107225750848201515b600019600385901b1c1916600184901b1784556106d1565b600084815260208120601f198516915b8281101561076a578785015182556020948501946001909201910161074a565b50848210156107885786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b60ff81811683821602908116908181146107c157634e487b7160e01b600052601160045260246000fd5b5092915050565b60805160a05161471c610825600039600081816104bf01528181610f700152818161201901528181612603015281816126b301528181612842015281816128f20152612d1401526000818161355d015261384f015261471c6000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80636e2b54ee116100f9578063c0cc4add11610097578063e8aa0a0711610071578063e8aa0a0714610461578063f752196b14610474578063fb1e61ca1461049d578063fc0c546a146104bd57600080fd5b8063c0cc4add14610428578063c5d433511461043b578063d02bbe331461044e57600080fd5b8063a29c29a4116100d3578063a29c29a4146103b2578063a3a0807e146103c5578063b396dc79146103e8578063be5cdc481461040857600080fd5b80636e2b54ee146103845780639777b72c1461039757806399b6da0c1461039f57600080fd5b8063329b5a0b1161016657806351a766421161014057806351a76642146103035780635da73835146103165780636b00c8cf1461032b5780636c70bee91461036f57600080fd5b8063329b5a0b14610298578063458d2bf1146102cb5780634641dce6146102de57600080fd5b806312827602116101a2578063128276021461022e5780631d873c1b14610241578063237d84821461025457806326d6f8341461026757600080fd5b806302fa8e65146101c957806305b90773146101f95780630aefaabe14610219575b600080fd5b6101dc6101d73660046138ea565b6104e3565b6040516001600160401b0390911681526020015b60405180910390f35b61020c6102073660046138ea565b6105c1565b6040516101f09190613919565b61022c610227366004613948565b6106e4565b005b61022c61023c3660046139af565b610877565b61022c61024f3660046139f2565b610948565b61022c6102623660046139af565b610dfa565b61028a6102753660046138ea565b60009081526012602052604090206003015490565b6040519081526020016101f0565b6101dc6102a63660046138ea565b600090815260116020526040902060020154600160c01b90046001600160401b031690565b61028a6102d93660046138ea565b611046565b6102f16102ec3660046138ea565b61105f565b60405160ff90911681526020016101f0565b61028a6103113660046138ea565b611072565b61031e6110d1565b6040516101f09190613a32565b6103576103393660046138ea565b6000908152601260205260409020600401546001600160a01b031690565b6040516001600160a01b0390911681526020016101f0565b6103776110f8565b6040516101f09190613b10565b61022c6103923660046138ea565b61126f565b61031e61127c565b61022c6103ad366004613b98565b61129b565b61022c6103c03660046138ea565b6117e1565b6103d86103d33660046138ea565b611833565b60405190151581526020016101f0565b6103fb6103f63660046138ea565b61186f565b6040516101f09190613cc7565b61041b6104163660046138ea565b611b51565b6040516101f09190613d02565b6103d86104363660046138ea565b611c1f565b61022c610449366004613d16565b611c32565b6103d861045c3660046139af565b6120b3565b61022c61046f366004613d3b565b612155565b6101dc6104823660046138ea565b6000908152600660205260409020546001600160401b031690565b6104b06104ab3660046138ea565b6122ce565b6040516101f09190613d69565b7f0000000000000000000000000000000000000000000000000000000000000000610357565b6000806104ef836105c1565b9050600081600481111561050557610505613903565b14806105225750600181600481111561052057610520613903565b145b1561054e575050600090815260116020526040902060020154600160801b90046001600160401b031690565b600281600481111561056257610562613903565b0361058e575050600090815260116020526040902060020154600160c01b90046001600160401b031690565b6000838152601160205260409020600201546105ba90600160801b90046001600160401b0316426124e4565b9392505050565b60008181526010602052604081205482906001600160a01b03166105f857604051635eeb253d60e11b815260040160405180910390fd5b600083815260116020526040812090815460ff16600481111561061d5761061d613903565b14801561065c5750600084815260116020526040902060020154600160c01b90046001600160401b03166001600160401b0316426001600160401b0316115b1561066b5760029250506106de565b6001815460ff16600481111561068357610683613903565b14806106a457506000815460ff1660048111156106a2576106a2613903565b145b80156106c8575060028101546001600160401b03600160801b909104811642909116115b156106d75760039250506106de565b5460ff1691505b50919050565b826000808281526012602052604090205460ff16600681111561070957610709613903565b0361072757604051638b41ec7f60e01b815260040160405180910390fd5b600084815260126020526040902060048101546001600160a01b0316331461077b576040517f57a6f4e900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061078686611b51565b9050600481600681111561079c5761079c613903565b036107d3576040517fc2cbf77700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028160068111156107e7576107e7613903565b03610801576107fc82600101548787876124f4565b61086f565b600581600681111561081557610815613903565b0361082a576107fc826001015487878761273d565b600381600681111561083e5761083e613903565b0361084d576107fc3387612986565b600181600681111561086157610861613903565b0361086f5761086f866129a8565b505050505050565b61088182826120b3565b6108b7576040517f424a04ab00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006108c38383612bf8565b60008181526020819052604090209091506108de9033612c3d565b50600154600082815260208190526040902060ff909116906108ff90612c52565b03610943576040516001600160401b038316815283907fc8e6c955744189a19222ec226b72ac1435d88d5745252dac56e6f679f64c037a9060200160405180910390a25b505050565b60008381526010602052604090205483906001600160a01b031661097f57604051635eeb253d60e11b815260040160405180910390fd5b600084815260106020526040902060048101546001600160401b03908116908516106109d7576040517f3b920b8800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006109e38686612bf8565b60008181526020819052604090209091506109fe9033612c5c565b610a34576040517fd651ce1800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601260209081526040808320600181018a90556002810180546fffffffffffffffff00000000000000001916600160401b6001600160401b038c1602179055898452601190925282209091610a8d84611b51565b6006811115610a9e57610a9e613903565b14158015610ac657506006610ab284611b51565b6006811115610ac357610ac3613903565b14155b15610afd576040517fff556acf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60048201805473ffffffffffffffffffffffffffffffffffffffff1916331790556002820180546001600160401b03421667ffffffffffffffff19909116179055610b6e83600090815260056020526040902080546001600160401b03421667ffffffffffffffff19909116179055565b610b788387612155565b60028101805460019190600090610b999084906001600160401b0316613d92565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550610bde888360020160009054906101000a90046001600160401b0316612c7e565b816001016000828254610bf19190613db1565b90915550506040805160e081018252600186015481526002860154602082015260038601549181019190915260048501546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526000908190610c6a90612cad565b90506006610c7786611b51565b6006811115610c8857610c88613903565b03610cbb57600b54606490610ca09060ff1683613dc4565b610caa9190613df1565b610cb49082613db1565b9150610cbf565b8091505b610cc93383612ccc565b8160136000016000828254610cde9190613e05565b9091555050600384018190556004840154610d02906001600160a01b031686612da0565b835460ff191660011784556040516001600160401b038a1681528a907f8f301470a994578b52323d625dfbf827ca5208c81747d3459be7b8867baec3ec9060200160405180910390a2600486015460028401546001600160401b039081169116148015610d8457506000835460ff166004811115610d8257610d82613903565b145b15610dee57825460ff191660011783556002830180546001600160401b034216600160401b026fffffffffffffffff0000000000000000199091161790556040518a907f85e1543bf2f84fe80c6badbce3648c8539ad1df4d2b3d822938ca0538be727e690600090a25b50505050505050505050565b6001610e0583611b51565b6006811115610e1657610e16613903565b14610e4d576040517fae9dcffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e578282612dc2565b6000828152601260209081526040808320600180820154855260108452828520600b54845160e08101865292820154835260028201549583019590955260038101549382019390935260048301546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c0820152909391926064916201000090910460ff1690610efa90612cad565b610f049190613dc4565b610f0e9190613df1565b600b54909150600090606490610f2e906301000000900460ff1684613dc4565b610f389190613df1565b90508060136001016000828254610f4f9190613e05565b909155505060405163a9059cbb60e01b8152336004820152602481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063a9059cbb906044016020604051808303816000875af1158015610fc1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe59190613e18565b610ff157610ff1613e3a565b818460030160008282546110059190613db1565b9091555050600b5460008781526006602052604090205461010090910460ff16906001600160401b03166001600160401b03161061086f5761086f866129a8565b600061105982611054612fd9565b612fe4565b92915050565b60006110598261106d612fd9565b612ff8565b60008181526012602090815260408083206001810154845260109092528220600c54610100906110ac90600160801b900460ff1682613e50565b60018301546110bf9161ffff1690613dc4565b6110c99190613df1565b949350505050565b336000908152600a602052604090206060906110f3906110f09061308a565b90565b905090565b611100613874565b604080516101008082018352600b805460ff8082166080808701918252948304821660a080880191909152620100008404831660c08801526301000000909304821660e0870152855285519182018652600c80546001600160401b038082168552600160401b820416602085810191909152600160801b82048416988501989098527101000000000000000000000000000000000090049091166060830152600d80549596939593870194929391928401916111bb90613e6a565b80601f01602080910402602001604051908101604052809291908181526020018280546111e790613e6a565b80156112345780601f1061120957610100808354040283529160200191611234565b820191906000526020600020905b81548152906001019060200180831161121757829003601f168201915b5050509190925250505081526040805160208181018352600385015460ff1682528301526004909201546001600160401b0316910152919050565b6112798133611c32565b50565b3360009081526009602052604090206060906110f3906110f09061308a565b60006112ae6112a983613ffb565b613097565b9050336112be6020840184614104565b6001600160a01b0316146112e5576040516334c69e3160e11b815260040160405180910390fd5b6000818152601060205260409020546001600160a01b031615611334576040517ffc7d069000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61134661014083016101208401614121565b6001600160401b0316158061138d575061136660e0830160c08401614121565b6001600160401b031661138161014084016101208501614121565b6001600160401b031610155b156113c4576040517fdf63f61a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6113d460a0830160808401614121565b6001600160401b0316600003611416576040517f535ed2be00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61142660a0830160808401614121565b6001600160401b0316611440610100840160e08501614121565b6001600160401b03161115611481576040517fb9551ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61149160e0830160c08401614121565b6001600160401b03166000036114d3576040517f090a5ecd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020820135600003611511576040517f6aba7aae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606082013560000361154f576040517ffb7df0c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604082013560000361158d576040517f47ba51c700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61159b61010083018361413e565b6115a5908061415e565b90506000036115e0576040517f86f8cf9b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600f546001600160401b03166115fc60e0840160c08501614121565b6001600160401b0316111561163d576040517f1267b3f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000818152601060205260409020829061165782826142fe565b5061166a905060e0830160c08401614121565b6116749042613d92565b600082815260116020526040902060020180546001600160401b0392909216600160801b0267ffffffffffffffff60801b199092169190911790556116c161014083016101208401614121565b6116cb9042613d92565b600082815260116020908152604090912060020180546001600160401b0393909316600160c01b0277ffffffffffffffffffffffffffffffffffffffffffffffff9093169290921790915561172c9061172690840184614104565b826130c7565b600061173f61173a84613ffb565b6130e9565b600083815260116020526040812060010182905560138054929350839290919061176a908490613e05565b9091555061177a90503382612ccc565b6000828152601160209081526040918290206002015491517f1bf9c457accf8703dbf7cdf1b58c2f74ddf2e525f98155c70b3d318d74609bd8926117d492869290880191600160c01b90046001600160401b0316906144b2565b60405180910390a1505050565b806000808281526012602052604090205460ff16600681111561180657611806613903565b0361182457604051638b41ec7f60e01b815260040160405180910390fd5b61182f8233336106e4565b5050565b600080600061184984611844612fd9565b613125565b90925090508180156110c95750600254600160801b900460ff9081169116109392505050565b6118f260405180604001604052806138dd6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b816000808281526012602052604090205460ff16600681111561191757611917613903565b0361193557604051638b41ec7f60e01b815260040160405180910390fd5b60008381526012602052604090206119c660405180604001604052806138dd6040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b600180830154600090815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e08101865295830154865260028301548685015260038301548686015260048301546001600160401b038082166060890152600160401b820481166080890152600160801b8204811692880192909252600160c01b90041660c0860152918201939093528151808301835260058401805492949385019282908290611a7b90613e6a565b80601f0160208091040260200160405190810160405280929190818152602001828054611aa790613e6a565b8015611af45780601f10611ac957610100808354040283529160200191611af4565b820191906000526020600020905b815481529060010190602001808311611ad757829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0390811683830152600890930154604090920191909152918352600290930154600160401b900490921691810191909152915050919050565b600081815260126020526040812060018101548203611b735750600092915050565b6000611b8282600101546105c1565b90506004825460ff166006811115611b9c57611b9c613903565b03611bab575060049392505050565b6002816004811115611bbf57611bbf613903565b03611bce575060059392505050565b6003816004811115611be257611be2613903565b03611bf1575060029392505050565b6004816004811115611c0557611c05613903565b03611c14575060039392505050565b505460ff1692915050565b600061105982611c2d612fd9565b6131dd565b60008281526010602052604090205482906001600160a01b0316611c6957604051635eeb253d60e11b815260040160405180910390fd5b6000838152601060209081526040808320601190925290912081546001600160a01b03163314611cac576040516334c69e3160e11b815260040160405180910390fd5b6000611cb7866105c1565b90506002816004811115611ccd57611ccd613903565b14158015611ced57506004816004811115611cea57611cea613903565b14155b8015611d0b57506003816004811115611d0857611d08613903565b14155b15611d42576040517fc00b5b5700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8160010154600003611d80576040517fbd8bdd9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002816004811115611d9457611d94613903565b03611e3257815460ff1916600217825560405186907ff903f4774c7bd27355f9d7fcbc382b079b164a697a44ac5d95267a4c3cb3bb2290600090a2600086815260116020526040902060020154611dfc908790600160c01b90046001600160401b0316612c7e565b6002830154611e1491906001600160401b0316613dc4565b826001016000828254611e279190613e05565b90915550611fbf9050565b6004816004811115611e4657611e46613903565b03611fb3576040805160a0808201835285546001600160a01b03168252825160e08101845260018701548152600287015460208281019190915260038801548286015260048801546001600160401b038082166060850152600160401b820481166080850152600160801b8204811694840194909452600160c01b900490921660c08201529082015281518083018352600586018054611fa994889390850192909182908290611ef590613e6a565b80601f0160208091040260200160405190810160405280929190818152602001828054611f2190613e6a565b8015611f6e5780601f10611f4357610100808354040283529160200191611f6e565b820191906000526020600020905b815481529060010190602001808311611f5157829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101526130e9565b6001830155611fbf565b815460ff191660031782555b8254611fd4906001600160a01b031687613217565b600182015460148054829190600090611fee908490613e05565b909155505060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af1158015612062573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120869190613e18565b6120a357604051637c2ccffd60e11b815260040160405180910390fd5b5050600060019091015550505050565b600033816120c18585612bf8565b905060006120ce82611b51565b905060008160068111156120e4576120e4613903565b1480612101575060068160068111156120ff576120ff613903565b145b801561212a5750600154600083815260208190526040902060ff9091169061212890612c52565b105b801561214b575060008281526020819052604090206121499084612c5c565b155b9695505050505050565b6000828152601260209081526040808320600101548084526010909252909120546001600160a01b031661219c57604051635eeb253d60e11b815260040160405180910390fd5b600083815260126020526040902060048101546001600160a01b031633146121f0576040517fce351b9400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001810154600090815260106020526040808220815160038082526080820190935290929181602001602082028036833701905050905061223861223387611046565b613239565b8160008151811061224b5761224b61455f565b602090810291909101015260068201546122649061324a565b816001815181106122775761227761455f565b6020026020010181815250508260020160089054906101000a90046001600160401b03166001600160401b0316816002815181106122b7576122b761455f565b60200260200101818152505061086f868683613256565b6123436040805160a080820183526000808352835160e081018552818152602080820183905281860183905260608083018490526080830184905293820183905260c0820183905280850191909152845180860186529283528201529091820190815260006020820181905260409091015290565b60008281526010602052604090205482906001600160a01b031661237a57604051635eeb253d60e11b815260040160405180910390fd5b600083815260106020908152604091829020825160a0808201855282546001600160a01b03168252845160e0810186526001840154815260028401548186015260038401548187015260048401546001600160401b038082166060840152600160401b820481166080840152600160801b8204811693830193909352600160c01b900490911660c0820152928101929092528251808401845260058201805493949293928501928290829061242e90613e6a565b80601f016020809104026020016040519081016040528092919081815260200182805461245a90613e6a565b80156124a75780601f1061247c576101008083540402835291602001916124a7565b820191906000526020600020905b81548152906001019060200180831161248a57829003601f168201915b50505091835250506001919091015460209182015290825260078301546001600160401b0316908201526008909101546040909101529392505050565b60008282188284100282186105ba565b60008481526010602052604090205484906001600160a01b031661252b57604051635eeb253d60e11b815260040160405180910390fd5b600085815260116020908152604080832060108352818420815460ff1916600317825588855260129093529220815461256d906001600160a01b031689613217565b6004810154612585906001600160a01b031688612986565b60028101546000906125a1908a906001600160401b0316612c7e565b60038301549091506125b38183613e05565b601480546000906125c5908490613e05565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038981166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561264c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126709190613e18565b61268d57604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038881166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af11580156126fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906127209190613e18565b610dee57604051637c2ccffd60e11b815260040160405180910390fd5b60008481526010602052604090205484906001600160a01b031661277457604051635eeb253d60e11b815260040160405180910390fd5b6000848152601260205260409020600481015461279a906001600160a01b031686612986565b60028101546000906127e09088906001600160401b03166127db826000908152601160205260409020600201546001600160401b03600160c01b9091041690565b6133f4565b60038301549091506127f28183613e05565b60148054600090612804908490613e05565b90915550508254600490849060ff1916600183021790555060405163a9059cbb60e01b81526001600160a01b038781166004830152602482018490527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561288b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906128af9190613e18565b6128cc57604051637c2ccffd60e11b815260040160405180910390fd5b60405163a9059cbb60e01b81526001600160a01b038681166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063a9059cbb906044016020604051808303816000875af115801561293b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061295f9190613e18565b61297c57604051637c2ccffd60e11b815260040160405180910390fd5b5050505050505050565b6001600160a01b0382166000908152600a6020526040902061094390826134d3565b600081815260126020908152604080832060018101548085526011909352922060028301546129e19083906001600160401b0316612c7e565b8160010160008282546129f49190613e05565b90915550506004830154612a11906001600160a01b031685612986565b6000848152602081905260409020612a28906134df565b825460ff191660061783556002808401805467ffffffffffffffff1916905560006003850181905560048501805473ffffffffffffffffffffffffffffffffffffffff19169055908201805460019290612a8c9084906001600160401b0316614575565b82546101009290920a6001600160401b038181021990931691831602179091556002850154604051600160401b90910490911681528391507f33ba8f7627565d89f7ada2a6b81ea532b7aa9b11e91a78312d6e1fca0bfcd1dc9060200160405180910390a26000848152600660205260409020805467ffffffffffffffff19169055600082815260106020526040812060028301546004820154919291612b3f916001600160401b039081169116614575565b60048301546001600160401b039182169250600160c01b90041681118015612b7c57506001835460ff166004811115612b7a57612b7a613903565b145b1561086f57825460ff19166004178355612b97600142614575565b6002840180546001600160401b0392909216600160801b0267ffffffffffffffff60801b1990921691909117905560405184907f4769361a442504ecaf038f35e119bcccdd5e42096b24c09e3c17fd17c6684c0290600090a2505050505050565b60008282604051602001612c1f9291909182526001600160401b0316602082015260400190565b60405160208183030381529060405280519060200120905092915050565b60006105ba836001600160a01b0384166134e8565b6000611059825490565b6001600160a01b038116600090815260018301602052604081205415156105ba565b6000828152601160205260408120600201546105ba9084908490600160801b90046001600160401b03166133f4565b600081608001516001600160401b031682604001516110599190613dc4565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152306024830181905260448301849052917f0000000000000000000000000000000000000000000000000000000000000000909116906323b872dd906064016020604051808303816000875af1158015612d5f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612d839190613e18565b61094357604051637c2ccffd60e11b815260040160405180910390fd5b6001600160a01b0382166000908152600a602052604090206109439082613537565b6000612dcd82613543565b6001600160401b03169050428110612e11576040517f6b4b1a4e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600254612e2e90600160401b90046001600160401b031682613e05565b4210612e66576040517fde55698e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526007602090815260408083206001600160401b038616845290915290205460ff1615612ec2576040517efab7d900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612ecc83836131dd565b612f02576040517fd3ffa66b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038616845290915290205460ff1615612f5f576040517f98e7e55100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526008602090815260408083206001600160401b038087168552908352818420805460ff1916600190811790915587855260069093529083208054929390929091612fb091859116613d92565b92506101000a8154816001600160401b0302191690836001600160401b03160217905550505050565b60006110f342613556565b60006105ba612ff38484612ff8565b613582565b60008061300761010043614594565b600254909150600090610100906130369071010000000000000000000000000000000000900460ff16866145a8565b61304091906145ca565b6001600160401b03169050600061305961010087614594565b905060006101008261306b8587613e05565b6130759190613e05565b61307f9190614594565b979650505050505050565b606060006105ba836135dc565b6000816040516020016130aa9190613d69565b604051602081830303815290604052805190602001209050919050565b6001600160a01b03821660009081526009602052604090206109439082613537565b60006130f88260200151613638565b602083015160a081015160609091015161311291906145a8565b6001600160401b03166110599190613dc4565b600080600061313385611b51565b60008681526005602052604081205491925090613158906001600160401b0316613556565b9050600182600681111561316e5761316e613903565b14158061318257506131808582613657565b155b15613195576000809350935050506131d6565b61319f8686612ff8565b925060006131ac84613582565b905060006131b988611072565b90508015806131cf57506131cd8183614594565b155b9550505050505b9250929050565b60008060006131ec8585613125565b909250905081801561320e575060025460ff600160801b909104811690821610155b95945050505050565b6001600160a01b038216600090815260096020526040902061094390826134d3565b600060ff198216816110c98261366d565b6000806105ba8361366d565b60008381526007602052604081209061326d612fd9565b6001600160401b0316815260208101919091526040016000205460ff16156132c1576040517f3edef7db00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600480546040517f94c8919d0000000000000000000000000000000000000000000000000000000081526001600160a01b03909116916394c8919d9161330b9186918691016145f8565b602060405180830381865afa158015613328573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061334c9190613e18565b613382576040517ffcd03a4700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600083815260076020526040812060019161339b612fd9565b6001600160401b031681526020808201929092526040908101600020805460ff19169315159390931790925590518481527f3b989d183b84b02259d7c14b34a9c9eb0fccb4c355a920d25e581e25aef4993d91016117d4565b60008381526010602052604081206001600160401b0380841690851610613447576040517f56607cb000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160e081018252600183015481526002830154602082015260038301549181019190915260048201546001600160401b038082166060840152600160401b820481166080840152600160801b8204811660a0840152600160c01b9091041660c08201526134b690613638565b6134c08585614575565b6001600160401b031661320e9190613dc4565b60006105ba83836136df565b611279816137d9565b600081815260018301602052604081205461352f57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155611059565b506000611059565b60006105ba83836134e8565b60006110596135518361383b565b613848565b60006110597f0000000000000000000000000000000000000000000000000000000000000000836146a2565b60008060ff8316613594600143613db1565b61359e9190613db1565b40905060008190036135b2576135b2613e3a565b60408051602081018390520160405160208183030381529060405280519060200120915050919050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561362c57602002820191906000526020600020905b815481526020019060010190808311613618575b50505050509050919050565b600081608001516001600160401b031682602001516110599190613dc4565b60006001600160401b03808416908316106105ba565b7fff00000000000000000000000000000000000000000000000000000000000000811660015b60208110156106de57600891821c916136ad908290613dc4565b83901b7fff00000000000000000000000000000000000000000000000000000000000000169190911790600101613693565b600081815260018301602052604081205480156137c8576000613703600183613db1565b855490915060009061371790600190613db1565b905080821461377c5760008660000182815481106137375761373761455f565b906000526020600020015490508087600001848154811061375a5761375a61455f565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061378d5761378d6146d0565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050611059565b6000915050611059565b5092915050565b60006137e3825490565b905060005b818110156138335782600101600084600001838154811061380b5761380b61455f565b90600052602060002001548152602001908152602001600020600090558060010190506137e8565b505060009055565b6000611059826001613d92565b60006110597f0000000000000000000000000000000000000000000000000000000000000000836145a8565b60408051610100810182526000608080830182815260a080850184905260c0850184905260e08501849052908452845190810185528281526020808201849052818601849052606080830185905292820192909252818401528351908101845290815290918201905b8152600060209091015290565b6000602082840312156138fc57600080fd5b5035919050565b634e487b7160e01b600052602160045260246000fd5b602081016005831061392d5761392d613903565b91905290565b6001600160a01b038116811461127957600080fd5b60008060006060848603121561395d57600080fd5b83359250602084013561396f81613933565b9150604084013561397f81613933565b809150509250925092565b6001600160401b038116811461127957600080fd5b80356139aa8161398a565b919050565b600080604083850312156139c257600080fd5b8235915060208301356139d48161398a565b809150509250929050565b600061010082840312156106de57600080fd5b60008060006101408486031215613a0857600080fd5b833592506020840135613a1a8161398a565b9150613a2985604086016139df565b90509250925092565b602080825282518282018190526000918401906040840190835b81811015613a6a578351835260209384019390920191600101613a4c565b509095945050505050565b6000815180845260005b81811015613a9b57602081850181015186830182015201613a7f565b506000602082860101526020601f19601f83011685010191505092915050565b6001600160401b0381511682526001600160401b03602082015116602083015260ff604082015116604083015260ff60608201511660608301526000608082015160a060808501526110c960a0850182613a75565b602081526000825160ff815116602084015260ff602082015116604084015260ff604082015116606084015260ff606082015116608084015250602083015160e060a0840152613b64610100840182613abb565b90506040840151613b7b60c08501825160ff169052565b5060608401516001600160401b03811660e0850152509392505050565b600060208284031215613baa57600080fd5b81356001600160401b03811115613bc057600080fd5b820161016081850312156105ba57600080fd5b6000815160408452613be86040850182613a75565b602093840151949093019390935250919050565b6001600160a01b038151168252600060208201518051602085015260208101516040850152604081015160608501526001600160401b0360608201511660808501526001600160401b0360808201511660a08501526001600160401b0360a08201511660c08501526001600160401b0360c08201511660e0850152506040820151610160610100850152613c94610160850182613bd3565b90506060830151613cb16101208601826001600160401b03169052565b5060808301516101408501528091505092915050565b602081526000825160406020840152613ce36060840182613bfc565b90506001600160401b0360208501511660408401528091505092915050565b602081016007831061392d5761392d613903565b60008060408385031215613d2957600080fd5b8235915060208301356139d481613933565b6000806101208385031215613d4f57600080fd5b82359150613d6084602085016139df565b90509250929050565b6020815260006105ba6020830184613bfc565b634e487b7160e01b600052601160045260246000fd5b6001600160401b03818116838216019081111561105957611059613d7c565b8181038181111561105957611059613d7c565b808202811582820484141761105957611059613d7c565b634e487b7160e01b600052601260045260246000fd5b600082613e0057613e00613ddb565b500490565b8082018082111561105957611059613d7c565b600060208284031215613e2a57600080fd5b815180151581146105ba57600080fd5b634e487b7160e01b600052600160045260246000fd5b61ffff828116828216039081111561105957611059613d7c565b600181811c90821680613e7e57607f821691505b6020821081036106de57634e487b7160e01b600052602260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b604080519081016001600160401b0381118282101715613ed657613ed6613e9e565b60405290565b60405160a081016001600160401b0381118282101715613ed657613ed6613e9e565b60405160e081016001600160401b0381118282101715613ed657613ed6613e9e565b604051601f8201601f191681016001600160401b0381118282101715613f4857613f48613e9e565b604052919050565b600060408284031215613f6257600080fd5b613f6a613eb4565b905081356001600160401b03811115613f8257600080fd5b8201601f81018413613f9357600080fd5b80356001600160401b03811115613fac57613fac613e9e565b613fbf601f8201601f1916602001613f20565b818152856020838501011115613fd457600080fd5b81602084016020830137600060209282018301528352928301359282019290925292915050565b600081360361016081121561400f57600080fd5b614017613edc565b833561402281613933565b815260e0601f198301121561403657600080fd5b61403e613efe565b6020858101358252604080870135918301919091526060860135908201529150608084013561406c8161398a565b606083015260a084013561407f8161398a565b608083015260c08401356140928161398a565b60a083015260e08401356140a58161398a565b60c08301526020810191909152610100830135906001600160401b038211156140cd57600080fd5b6140d936838601613f50565b60408201526140eb610120850161399f565b6060820152610140939093013560808401525090919050565b60006020828403121561411657600080fd5b81356105ba81613933565b60006020828403121561413357600080fd5b81356105ba8161398a565b60008235603e1983360301811261415457600080fd5b9190910192915050565b6000808335601e1984360301811261417557600080fd5b8301803591506001600160401b0382111561418f57600080fd5b6020019150368190038213156131d657600080fd5b600081356110598161398a565b601f82111561094357806000526020600020601f840160051c810160208510156141d85750805b601f840160051c820191505b818110156141f857600081556001016141e4565b5050505050565b8135601e1983360301811261421357600080fd5b820180356001600160401b038111801561422c57600080fd5b81360360208401131561423e57600080fd5b60009050614256826142508654613e6a565b866141b1565b80601f83116001811461428b578284156142735750848201602001355b600019600386901b1c1916600185901b1786556142ea565b600086815260209020601f19851690845b828110156142be5760208589018101358355948501946001909201910161429c565b50858210156142de5760001960f88760031b161c19602085890101351681555b505060018460011b0186555b505050505060209190910135600190910155565b813561430981613933565b815473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03919091161781556020820135600182015560408201356002820155606082013560038201556004810160808301356143618161398a565b815467ffffffffffffffff19166001600160401b0382161782555060a083013561438a8161398a565b81546fffffffffffffffff0000000000000000191660409190911b6fffffffffffffffff0000000000000000161781556143fe6143c960c085016141a4565b825467ffffffffffffffff60801b191660809190911b77ffffffffffffffff0000000000000000000000000000000016178255565b61445661440d60e085016141a4565b825477ffffffffffffffffffffffffffffffffffffffffffffffff1660c09190911b7fffffffffffffffff00000000000000000000000000000000000000000000000016178255565b5061447161446861010084018461413e565b600583016141ff565b6144a261448161012084016141a4565b600783016001600160401b0382166001600160401b03198254161781555050565b6101409190910135600890910155565b83815282356020808301919091528301356040808301919091528301356060808301919091526101208201908401356144ea8161398a565b6001600160401b03811660808401525060808401356145088161398a565b6001600160401b03811660a08401525060a08401356145268161398a565b6001600160401b03811660c08401525061454260c0850161399f565b6001600160401b0390811660e084015283166101008301526110c9565b634e487b7160e01b600052603260045260246000fd5b6001600160401b03828116828216039081111561105957611059613d7c565b6000826145a3576145a3613ddb565b500690565b6001600160401b0381811683821602908116908181146137d2576137d2613d7c565b60006001600160401b038316806145e3576145e3613ddb565b806001600160401b0384160691505092915050565b823581526020808401359082015260006101208201614627604084016040870180358252602090810135910152565b614641608084016080870180358252602090810135910152565b61465b60c0840160c0870180358252602090810135910152565b610120610100840152835190819052602084019061014084019060005b81811015614696578351835260209384019390920191600101614678565b50909695505050505050565b60006001600160401b038316806146bb576146bb613ddb565b806001600160401b0384160491505092915050565b634e487b7160e01b600052603160045260246000fdfea26469706673582212203444a73360bb50dd0606abd72470bb042d6e86a23b0e86c86dd00cdc8a0275f564736f6c634300081c0033"; public MarketplaceDeploymentBase() : base(BYTECODE) { } public MarketplaceDeploymentBase(string byteCode) : base(byteCode) { } [Parameter("tuple", "config", 1)] diff --git a/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs b/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs deleted file mode 100644 index f52a38a8..00000000 --- a/ProjectPlugins/CodexContractsPlugin/VersionRegistry.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.Diagnostics; -using Logging; - -namespace CodexContractsPlugin -{ - public interface ICodexDockerImageProvider - { - string GetCodexDockerImage(); - } - - public class VersionRegistry - { - private ICodexDockerImageProvider provider = new ExceptionProvider(); - private static readonly Dictionary cache = new Dictionary(); - private static readonly object cacheLock = new object(); - private readonly ILog log; - - public VersionRegistry(ILog log) - { - this.log = log; - } - - public void SetProvider(ICodexDockerImageProvider provider) - { - this.provider = provider; - } - - public string GetContractsDockerImage() - { - try - { - var codexImage = provider.GetCodexDockerImage(); - return GetContractsDockerImage(codexImage); - } - catch (Exception exc) - { - throw new Exception("Failed to get contracts docker image.", exc); - } - } - - private string GetContractsDockerImage(string codexImage) - { - lock (cacheLock) - { - if (cache.TryGetValue(codexImage, out string? value)) - { - return value; - } - var result = GetContractsImage(codexImage); - cache.Add(codexImage, result); - return result; - } - } - - private string GetContractsImage(string codexImage) - { - var inspectResult = InspectCodexImage(codexImage); - var image = ParseCodexContractsImageName(inspectResult); - log.Log($"From codex docker image '{codexImage}', determined codex-contracts docker image: '{image}'"); - return image; - } - - private string InspectCodexImage(string img) - { - Execute("docker", $"pull {img}"); - return Execute("docker", $"inspect {img}"); - } - - private string ParseCodexContractsImageName(string inspectResult) - { - // It is a nice json structure. But we only need this one line. - // "storage.codex.nim-codex.blockchain-image": "codexstorage/codex-contracts-eth:sha-0bf1385-dist-tests" - var lines = inspectResult.Split('\n', StringSplitOptions.RemoveEmptyEntries); - var line = lines.Single(l => l.Contains("storage.codex.nim-codex.blockchain-image")); - var tokens = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); - return tokens.Last().Replace("\"", "").Trim(); - } - - private string Execute(string cmd, string args) - { - var startInfo = new ProcessStartInfo( - fileName: cmd, - arguments: args - ); - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - - var process = Process.Start(startInfo); - if (process == null) - { - throw new Exception("Failed to start: " + cmd + args); - } - KillAfterTimeout(process); - - process.WaitForExit(); - return process.StandardOutput.ReadToEnd(); - } - - private void KillAfterTimeout(Process process) - { - // There's a known issue that some docker commands on some platforms - // will fail to stop on their own. This has been known since 2019 and it's not fixed. - // So we will issue a kill to the process ourselves if it exceeds a timeout. - - Task.Run(() => - { - Thread.Sleep(TimeSpan.FromSeconds(30.0)); - - if (process != null && !process.HasExited) - { - process.Kill(); - } - }); - } - } - - internal class ExceptionProvider : ICodexDockerImageProvider - { - public string GetCodexDockerImage() - { - throw new InvalidOperationException("CodexContractsPlugin has not yet received a CodexDockerImageProvider " + - "and so cannot select a compatible contracts docker image."); - } - } -} diff --git a/ProjectPlugins/CodexPlugin/ApiChecker.cs b/ProjectPlugins/CodexPlugin/ApiChecker.cs index 656c6588..d4c1a615 100644 --- a/ProjectPlugins/CodexPlugin/ApiChecker.cs +++ b/ProjectPlugins/CodexPlugin/ApiChecker.cs @@ -10,7 +10,7 @@ namespace CodexPlugin public class ApiChecker { // - private const string OpenApiYamlHash = "1A-F7-DF-C3-E1-C6-98-FF-32-20-21-9B-26-40-B0-51-08-35-C2-E7-DB-41-49-93-60-A9-CE-47-B5-AD-3D-A3"; + 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 DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK"; diff --git a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs index 5ed36c92..57619f0b 100644 --- a/ProjectPlugins/CodexPlugin/CodexDockerImage.cs +++ b/ProjectPlugins/CodexPlugin/CodexDockerImage.cs @@ -1,10 +1,8 @@ -using CodexContractsPlugin; - -namespace CodexPlugin +namespace CodexPlugin { - public class CodexDockerImage : ICodexDockerImageProvider + public class CodexDockerImage { - private const string DefaultDockerImage = "codexstorage/nim-codex:latest-dist-tests"; + private const string DefaultDockerImage = "codexstorage/nim-codex:0.2.3-dist-tests"; public static string Override { get; set; } = string.Empty; diff --git a/ProjectPlugins/CodexPlugin/CodexPlugin.cs b/ProjectPlugins/CodexPlugin/CodexPlugin.cs index 277b6101..0c8f4510 100644 --- a/ProjectPlugins/CodexPlugin/CodexPlugin.cs +++ b/ProjectPlugins/CodexPlugin/CodexPlugin.cs @@ -42,7 +42,6 @@ namespace CodexPlugin public void Awake(IPluginAccess access) { - access.GetPlugin().SetCodexDockerImageProvider(codexDockerImage); } public void Announce() diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs index afd8d1a2..95b8dde6 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriter.cs @@ -23,16 +23,27 @@ namespace CodexPlugin.OverwatchSupport converter = new CodexLogConverter(writer, config, identityMap); } - public void Finalize(string outputFilepath) + public void FinalizeWriter() { log.Log("Finalizing Codex transcript..."); writer.AddHeader(CodexHeaderKey, CreateCodexHeader()); - writer.Write(outputFilepath); + writer.Write(GetOutputFullPath()); log.Log("Done"); } + private string GetOutputFullPath() + { + var outputPath = Path.GetDirectoryName(log.GetFullName()); + if (outputPath == null) throw new Exception("Logfile path is null"); + var filename = Path.GetFileNameWithoutExtension(log.GetFullName()); + if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); + var outputFile = Path.Combine(outputPath, filename + "_" + config.OutputPath); + if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; + return outputFile; + } + public ICodexNodeHooks CreateHooks(string nodeName) { nodeName = Str.Between(nodeName, "'", "'"); diff --git a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs index 247494c8..112f5b16 100644 --- a/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs +++ b/ProjectPlugins/CodexPlugin/OverwatchSupport/CodexTranscriptWriterConfig.cs @@ -2,11 +2,13 @@ { public class CodexTranscriptWriterConfig { - public CodexTranscriptWriterConfig(bool includeBlockReceivedEvents) + public CodexTranscriptWriterConfig(string outputPath, bool includeBlockReceivedEvents) { + OutputPath = outputPath; IncludeBlockReceivedEvents = includeBlockReceivedEvents; } + public string OutputPath { get; } public bool IncludeBlockReceivedEvents { get; } } } diff --git a/ProjectPlugins/GethPlugin/GethNode.cs b/ProjectPlugins/GethPlugin/GethNode.cs index 26bf1f03..cfd0af37 100644 --- a/ProjectPlugins/GethPlugin/GethNode.cs +++ b/ProjectPlugins/GethPlugin/GethNode.cs @@ -31,6 +31,7 @@ namespace GethPlugin List> GetEvents(string address, TimeRange timeRange) where TEvent : IEventDTO, new(); BlockInterval ConvertTimeRangeToBlockRange(TimeRange timeRange); BlockTimeEntry GetBlockForNumber(ulong number); + void IterateFunctionCalls(BlockInterval blockInterval, Action onCall) where TFunc : FunctionMessage, new(); } public class DeploymentGethNode : BaseGethNode, IGethNode @@ -183,6 +184,33 @@ namespace GethPlugin return StartInteraction().GetBlockForNumber(number); } + public BlockWithTransactions GetBlk(ulong number) + { + return StartInteraction().GetBlockWithTransactions(number); + } + + public void IterateFunctionCalls(BlockInterval blockRange, Action onCall) where TFunc : FunctionMessage, new() + { + var i = StartInteraction(); + for (var blkI = blockRange.From; blkI <= blockRange.To; blkI++) + { + var blk = i.GetBlockWithTransactions(blkI); + + foreach (var t in blk.Transactions) + { + if (t.IsTransactionForFunctionMessage()) + { + var func = t.DecodeTransactionToFunctionMessage(); + if (func != null) + { + var b = GetBlockForNumber(blkI); + onCall(b, func); + } + } + } + } + } + protected abstract NethereumInteraction StartInteraction(); } } diff --git a/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs b/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs index 507b1ba7..7e1548b2 100644 --- a/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs +++ b/ProjectPlugins/MetricsPlugin/MetricsDownloader.cs @@ -28,11 +28,11 @@ namespace MetricsPlugin var file = log.CreateSubfile("csv"); log.Log($"Downloading metrics for {nodeName} to file {file.Filename}"); - file.WriteRaw(string.Join(",", headers)); + file.Write(string.Join(",", headers)); foreach (var pair in map) { - file.WriteRaw(string.Join(",", new[] { FormatTimestamp(pair.Key) }.Concat(pair.Value))); + file.Write(string.Join(",", new[] { FormatTimestamp(pair.Key) }.Concat(pair.Value))); } return file; diff --git a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs index e198650c..5f1e968c 100644 --- a/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs +++ b/Tests/CodexContinuousTests/ElasticSearchLogDownloader.cs @@ -199,7 +199,7 @@ namespace ContinuousTests private void WriteEntryToFile(LogQueueEntry currentEntry) { - targetFile.WriteRaw(currentEntry.Message); + targetFile.Write(currentEntry.Message); } private void DeleteOldEntries(ulong wantedNumber) diff --git a/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs b/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs index 263a9e8b..4b9139e6 100644 --- a/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs +++ b/Tests/CodexReleaseTests/DataTests/DataExpiryTest.cs @@ -50,8 +50,9 @@ namespace CodexReleaseTests.DataTests var blockTtl = TimeSpan.FromMinutes(1.0); var interval = TimeSpan.FromSeconds(10.0); + var bootstrapNode = StartCodex(); var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, bootstrapNode.Version); var node = StartCodex(s => s .EnableMarketplace(geth, contracts, m => m.WithInitial(100.Eth(), 100.Tst())) .WithBlockTTL(blockTtl) diff --git a/Tests/CodexReleaseTests/DataTests/DecodeTest.cs b/Tests/CodexReleaseTests/DataTests/DecodeTest.cs index f6a1f309..1aaf742f 100644 --- a/Tests/CodexReleaseTests/DataTests/DecodeTest.cs +++ b/Tests/CodexReleaseTests/DataTests/DecodeTest.cs @@ -1,5 +1,5 @@ using System.Security.Cryptography; -using CodexReleaseTests.MarketTests; +using CodexReleaseTests.Utils; using Nethereum.JsonRpc.Client; using NUnit.Framework; using Utils; diff --git a/Tests/CodexReleaseTests/MarketTests/ContractFailedTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractFailedTest.cs deleted file mode 100644 index f4301f39..00000000 --- a/Tests/CodexReleaseTests/MarketTests/ContractFailedTest.cs +++ /dev/null @@ -1,173 +0,0 @@ -using CodexClient; -using CodexContractsPlugin.ChainMonitor; -using CodexContractsPlugin.Marketplace; -using CodexPlugin; -using NUnit.Framework; -using System.Numerics; -using Utils; - -namespace CodexReleaseTests.MarketTests -{ - public class ContractFailedTest : MarketplaceAutoBootstrapDistTest - { - private const int FilesizeMb = 10; - private const int NumberOfSlots = 3; - - protected override int NumberOfHosts => 6; - protected override int NumberOfClients => 1; - protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); - protected override TimeSpan HostAvailabilityMaxDuration => TimeSpan.FromDays(5.0); - private readonly TestToken pricePerBytePerSecond = 10.TstWei(); - - [Test] - public void ContractFailed() - { - var hosts = StartHosts(); - var client = StartClients().Single(); - var validator = StartValidator(); - - var request = CreateStorageRequest(client); - - request.WaitForStorageContractSubmitted(); - AssertContractIsOnChain(request); - - request.WaitForStorageContractStarted(); - AssertContractSlotsAreFilledByHosts(request, hosts); - - hosts.Stop(waitTillStopped: true); - - var config = GetContracts().Deployment.Config; - request.WaitForContractFailed(config); - - var frees = GetOnChainSlotFrees(hosts); - Assert.That(frees.Length, Is.EqualTo( - request.Purchase.MinRequiredNumberOfNodes - request.Purchase.NodeFailureTolerance)); - - var periodReports = GetPeriodMonitorReports(); - var missedProofs = periodReports.Reports.SelectMany(r => r.MissedProofs).ToArray(); - AssertEnoughProofsWereMissedForSlotFree(frees, missedProofs, config); - - AssertClientPaidNothing(client); - AssertValidatorWasPaidPerMissedProof(validator, request, missedProofs, config); - AssertHostCollateralWasBurned(hosts, request); - } - - private void AssertClientPaidNothing(ICodexNode client) - { - AssertTstBalance(client, StartingBalanceTST.Tst(), "Client should not have paid for failed contract."); - } - - private void AssertValidatorWasPaidPerMissedProof(ICodexNode validator, IStoragePurchaseContract request, PeriodProofMissed[] missedProofs, MarketplaceConfig config) - { - var rewardPerMissedProof = GetValidatorRewardPerMissedProof(request, config); - var totalValidatorReward = rewardPerMissedProof * missedProofs.Length; - - AssertTstBalance(validator, StartingBalanceTST.Tst() + totalValidatorReward, $"Validator is rewarded per slot marked as missing. " + - $"numberOfMissedProofs: {missedProofs.Length} rewardPerMissedProof: {rewardPerMissedProof}"); - } - - private TestToken GetCollatoralPerSlot(IStoragePurchaseContract request) - { - var slotSize = new ByteSize(Convert.ToInt64(request.GetStatus()!.Request.Ask.SlotSize)); - return new TestToken(request.Purchase.CollateralPerByte.TstWei * slotSize.SizeInBytes); - } - - private void AssertHostCollateralWasBurned(ICodexNodeGroup hosts, IStoragePurchaseContract request) - { - var slotFills = GetOnChainSlotFills(hosts); - foreach (var host in hosts) - { - AssertHostCollateralWasBurned(host, slotFills, request); - } - } - - private void AssertHostCollateralWasBurned(ICodexNode host, SlotFill[] slotFills, IStoragePurchaseContract request) - { - // In case of a failed contract, the entire slotColateral is lost. - var filledByHost = slotFills.Where(f => f.Host.EthAddress == host.EthAddress).ToArray(); - var numSlotsOfHost = filledByHost.Length; - var collatoralPerSlot = GetCollatoralPerSlot(request); - var totalCost = collatoralPerSlot * numSlotsOfHost; - - AssertTstBalance(host, StartingBalanceTST.Tst() - totalCost, $"Host has lost collateral for each slot. " + - $"numberOfSlotsByHost: {numSlotsOfHost} collateralPerSlot: {collatoralPerSlot}"); - } - - private TestToken GetValidatorRewardPerMissedProof(IStoragePurchaseContract request, MarketplaceConfig config) - { - var collatoralPerSlot = GetCollatoralPerSlot(request); - var slashPercentage = config.Collateral.SlashPercentage; - var validatorRewardPercentage = config.Collateral.ValidatorRewardPercentage; - - var rewardPerMissedProof = - PercentageOf( - PercentageOf(collatoralPerSlot, slashPercentage), - validatorRewardPercentage); - - return rewardPerMissedProof; - } - - private TestToken PercentageOf(TestToken value, byte percentage) - { - var p = new BigInteger(percentage); - return new TestToken((value.TstWei * p) / 100); - } - - private void AssertEnoughProofsWereMissedForSlotFree(SlotFree[] frees, PeriodProofMissed[] missedProofs, MarketplaceConfig config) - { - foreach (var free in frees) - { - AssertEnoughProofsWereMissedForSlotFree(free, missedProofs, config); - } - } - - private void AssertEnoughProofsWereMissedForSlotFree(SlotFree free, PeriodProofMissed[] missedProofs, MarketplaceConfig config) - { - var missedByHost = missedProofs.Where(p => p.Host != null && p.Host.Address == free.Host.EthAddress.Address).ToArray(); - var maxNumMissedProofsBeforeFreeSlot = config.Collateral.MaxNumberOfSlashes; - Assert.That(missedByHost.Length, Is.EqualTo(maxNumMissedProofsBeforeFreeSlot)); - } - - private TimeSpan CalculateContractFailTimespan() - { - var config = GetContracts().Deployment.Config; - var requiredNumMissedProofs = Convert.ToInt32(config.Collateral.MaxNumberOfSlashes); - var periodDuration = GetPeriodDuration(); - - // 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 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 + chanceOfDowntime + chanceOfDowntime; - } - - private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) - { - var cid = client.UploadFile(GenerateTestFile(FilesizeMb.MB())); - return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) - { - Duration = TimeSpan.FromMinutes(20.0), - Expiry = TimeSpan.FromMinutes(5.0), - MinRequiredNumberOfNodes = NumberOfSlots, - NodeFailureTolerance = 1, - PricePerBytePerSecond = pricePerBytePerSecond, - ProofProbability = 1, // Require a proof every period - CollateralPerByte = 1.TstWei() - }); - } - } -} diff --git a/Tests/CodexReleaseTests/MarketTests/ContractRepairedTest.cs b/Tests/CodexReleaseTests/MarketTests/ContractRepairedTest.cs deleted file mode 100644 index 752afe18..00000000 --- a/Tests/CodexReleaseTests/MarketTests/ContractRepairedTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodexReleaseTests.MarketTests -{ - public class ContractRepairedTest - { - [Test] - [Ignore("TODO - Test in which a host fails, but the slot is repaired")] - public void ContractRepaired() - { - } - } -} diff --git a/Tests/CodexReleaseTests/MarketTests/FailTest.cs b/Tests/CodexReleaseTests/MarketTests/FailTest.cs new file mode 100644 index 00000000..c5a0715c --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/FailTest.cs @@ -0,0 +1,78 @@ +using CodexClient; +using CodexReleaseTests.Utils; +using NUnit.Framework; +using Utils; + +namespace CodexReleaseTests.MarketTests +{ + public class FailTest : MarketplaceAutoBootstrapDistTest + { + protected override int NumberOfHosts => 4; + protected override int NumberOfClients => 1; + protected override ByteSize HostAvailabilitySize => 1.GB(); + protected override TimeSpan HostAvailabilityMaxDuration => TimeSpan.FromDays(1.0); + + [Ignore("Slots are never freed because proofs are never marked as missing. Issue: https://github.com/codex-storage/nim-codex/issues/1153")] + [Test] + [Combinatorial] + public void Fail( + [Values([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])] 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(); + if (slotFreed.Length == NumberOfHosts) + { + 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(5.MB())); + return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) + { + Duration = HostAvailabilityMaxDuration / 2, + Expiry = TimeSpan.FromMinutes(5.0), + MinRequiredNumberOfNodes = (uint)NumberOfHosts, + NodeFailureTolerance = (uint)(NumberOfHosts / 2), + PricePerBytePerSecond = 100.TstWei(), + ProofProbability = 1, // Require a proof every period + CollateralPerByte = 1.TstWei() + }); + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs b/Tests/CodexReleaseTests/MarketTests/FinishTest.cs similarity index 60% rename from Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs rename to Tests/CodexReleaseTests/MarketTests/FinishTest.cs index 7b9dd82e..87df023c 100644 --- a/Tests/CodexReleaseTests/MarketTests/ContractSuccessfulTest.cs +++ b/Tests/CodexReleaseTests/MarketTests/FinishTest.cs @@ -1,22 +1,34 @@ using CodexClient; +using CodexReleaseTests.Utils; using NUnit.Framework; using Utils; namespace CodexReleaseTests.MarketTests { - [TestFixture] - public class ContractSuccessfulTest : MarketplaceAutoBootstrapDistTest + [TestFixture(5, 3, 1)] + [TestFixture(10, 20, 10)] + public class FinishTest : MarketplaceAutoBootstrapDistTest { - private const int FilesizeMb = 10; + public FinishTest(int hosts, int slots, int tolerance) + { + this.hosts = hosts; + purchaseParams = new PurchaseParams(slots, tolerance, uploadFilesize: 10.MB()); + } - protected override int NumberOfHosts => 6; - protected override int NumberOfClients => 1; - protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); - protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration(); 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 => Get8TimesConfiguredPeriodDuration() * 12; [Test] - public void ContractSuccessful() + [Combinatorial] + public void Finish( + [Values([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])] int rerun + ) { var hosts = StartHosts(); var client = StartClients().Single(); @@ -27,7 +39,7 @@ namespace CodexReleaseTests.MarketTests request.WaitForStorageContractSubmitted(); AssertContractIsOnChain(request); - request.WaitForStorageContractStarted(); + WaitForContractStarted(request); AssertContractSlotsAreFilledByHosts(request, hosts); request.WaitForStorageContractFinished(); @@ -40,16 +52,14 @@ namespace CodexReleaseTests.MarketTests private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) { - var cid = client.UploadFile(GenerateTestFile(FilesizeMb.MB())); + var cid = client.UploadFile(GenerateTestFile(purchaseParams.UploadFilesize)); + var config = GetContracts().Deployment.Config; return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) { Duration = GetContractDuration(), Expiry = GetContractExpiry(), - // TODO: this should work with NumberOfHosts, but - // an ongoing issue makes hosts sometimes not pick up slots. - // When it's resolved, we can reduce the number of hosts and slim down this test. - MinRequiredNumberOfNodes = 3, - NodeFailureTolerance = 1, + MinRequiredNumberOfNodes = (uint)purchaseParams.Nodes, + NodeFailureTolerance = (uint)purchaseParams.Tolerance, PricePerBytePerSecond = pricePerBytePerSecond, ProofProbability = 20, CollateralPerByte = 100.TstWei() @@ -63,7 +73,7 @@ namespace CodexReleaseTests.MarketTests private TimeSpan GetContractDuration() { - return Get8TimesConfiguredPeriodDuration() / 2; + return Get8TimesConfiguredPeriodDuration(); } private TimeSpan Get8TimesConfiguredPeriodDuration() diff --git a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs b/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs deleted file mode 100644 index 6ad10643..00000000 --- a/Tests/CodexReleaseTests/MarketTests/MultipleContractsTest.cs +++ /dev/null @@ -1,89 +0,0 @@ -using CodexClient; -using NUnit.Framework; -using Utils; - -namespace CodexReleaseTests.MarketTests -{ - [TestFixture] - public class MultipleContractsTest : MarketplaceAutoBootstrapDistTest - { - private const int FilesizeMb = 10; - - protected override int NumberOfHosts => 8; - protected override int NumberOfClients => 3; - protected override ByteSize HostAvailabilitySize => (5 * FilesizeMb).MB(); - protected override TimeSpan HostAvailabilityMaxDuration => Get8TimesConfiguredPeriodDuration(); - private readonly TestToken pricePerBytePerSecond = 10.TstWei(); - - [Test] - [Ignore("TODO - Test where multiple successful contracts are run simultaenously")] - public void MultipleSuccessfulContracts() - { - var hosts = StartHosts(); - var clients = StartClients(); - - var requests = clients.Select(c => CreateStorageRequest(c)).ToArray(); - - All(requests, r => - { - r.WaitForStorageContractSubmitted(); - AssertContractIsOnChain(r); - }); - - All(requests, r => r.WaitForStorageContractStarted()); - All(requests, r => AssertContractSlotsAreFilledByHosts(r, hosts)); - - All(requests, r => r.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); - - // todo: - //AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts); - //AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts); - //AssertHostsCollateralsAreUnchanged(hosts); - } - - private void All(IStoragePurchaseContract[] requests, Action action) - { - foreach (var r in requests) action(r); - } - - private IStoragePurchaseContract CreateStorageRequest(ICodexNode client) - { - var cid = client.UploadFile(GenerateTestFile(FilesizeMb.MB())); - var config = GetContracts().Deployment.Config; - return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) - { - Duration = GetContractDuration(), - Expiry = GetContractExpiry(), - MinRequiredNumberOfNodes = (uint)NumberOfHosts, - NodeFailureTolerance = (uint)(NumberOfHosts / 2), - PricePerBytePerSecond = pricePerBytePerSecond, - ProofProbability = 20, - CollateralPerByte = 1.Tst() - }); - } - - private TimeSpan GetContractExpiry() - { - return GetContractDuration() / 2; - } - - private TimeSpan GetContractDuration() - { - return Get8TimesConfiguredPeriodDuration() / 2; - } - - private TimeSpan Get8TimesConfiguredPeriodDuration() - { - var config = GetContracts().Deployment.Config; - return TimeSpan.FromSeconds(((double)config.Proofs.Period) * 8.0); - } - } -} diff --git a/Tests/CodexReleaseTests/MarketTests/RepairTest.cs b/Tests/CodexReleaseTests/MarketTests/RepairTest.cs new file mode 100644 index 00000000..c515f639 --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/RepairTest.cs @@ -0,0 +1,177 @@ +using CodexClient; +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 => 5; + 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 + + [Ignore("Test is ready. Waiting for repair implementation. " + + "Slots are never freed because proofs are never marked as missing. Issue: https://github.com/codex-storage/nim-codex/issues/1153")] + [Test] + [Combinatorial] + public void RollingRepairSingleFailure( + [Values([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])] int rerun, + [Values(10)] int numFailures) + { + var hosts = StartHosts().ToList(); + var client = StartClients().Single(); + StartValidator(); + + var contract = CreateStorageRequest(client); + contract.WaitForStorageContractStarted(); + // All slots are filled. + + for (var i = 0; i < numFailures; i++) + { + Log($"Failure step: {i}"); + + // 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) + { + Log(nameof(WaitForSlotFreedEvent)); + var start = DateTime.UtcNow; + var timeout = CalculateContractFailTimespan(); + + 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) + { + if (free.RequestId.ToHex().ToLowerInvariant() == 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; + 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()}"); + } + + 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 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() + }); + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/SequentialContracts.cs b/Tests/CodexReleaseTests/MarketTests/SequentialContracts.cs new file mode 100644 index 00000000..383093cf --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/SequentialContracts.cs @@ -0,0 +1,117 @@ +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 => Get8TimesConfiguredPeriodDuration() * 12; + private readonly TestToken pricePerBytePerSecond = 10.TstWei(); + + [Test] + [Combinatorial] + public void Sequential( + [Values(10)] 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[] items, Action 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[] items, Func 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 = 10000, + 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); + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/StartTest.cs b/Tests/CodexReleaseTests/MarketTests/StartTest.cs new file mode 100644 index 00000000..c64aef2a --- /dev/null +++ b/Tests/CodexReleaseTests/MarketTests/StartTest.cs @@ -0,0 +1,72 @@ +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: 10.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( + [Values([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])] 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; + } + } +} diff --git a/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs index 9b9f4bbf..f6cba228 100644 --- a/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs +++ b/Tests/CodexReleaseTests/NodeTests/BasicInfoTests.cs @@ -1,11 +1,5 @@ -using CodexPlugin; -using CodexTests; +using CodexTests; using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Utils; namespace CodexReleaseTests.NodeTests diff --git a/Tests/CodexReleaseTests/Parallelism.cs b/Tests/CodexReleaseTests/Parallelism.cs index a1b26c73..44731c5e 100644 --- a/Tests/CodexReleaseTests/Parallelism.cs +++ b/Tests/CodexReleaseTests/Parallelism.cs @@ -1,6 +1,6 @@ using NUnit.Framework; -[assembly: LevelOfParallelism(1)] -namespace CodexReleaseTests.DataTests +[assembly: LevelOfParallelism(2)] +namespace CodexReleaseTests { } diff --git a/Tests/CodexReleaseTests/Utils/ChainMonitor.cs b/Tests/CodexReleaseTests/Utils/ChainMonitor.cs new file mode 100644 index 00000000..8b4bd816 --- /dev/null +++ b/Tests/CodexReleaseTests/Utils/ChainMonitor.cs @@ -0,0 +1,79 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using Logging; + +namespace CodexReleaseTests.Utils +{ + public class ChainMonitor + { + private readonly ILog log; + private readonly ICodexContracts contracts; + private readonly DateTime startUtc; + private readonly TimeSpan updateInterval; + private CancellationTokenSource cts = new CancellationTokenSource(); + private Task worker = Task.CompletedTask; + + public ChainMonitor(ILog log, ICodexContracts contracts, DateTime startUtc) + : this(log, contracts, startUtc, TimeSpan.FromSeconds(3.0)) + { + } + + public ChainMonitor(ILog log, ICodexContracts contracts, DateTime startUtc, TimeSpan updateInterval) + { + this.log = log; + this.contracts = contracts; + 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, contracts, new DoNothingChainEventHandler(), startUtc, doProofPeriodMonitoring: true); + Thread.Sleep(updateInterval); + + log.Log("Chain monitoring started"); + 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(); + + var reports = state.PeriodMonitor.GetAndClearReports(); + if (reports.IsEmpty) return; + + var slots = reports.Reports.Sum(r => Convert.ToInt32(r.TotalNumSlots)); + var required = reports.Reports.Sum(r => Convert.ToInt32(r.TotalProofsRequired)); + var missed = reports.Reports.Sum(r => r.MissedProofs.Length); + + log.Log($"Proof report: Slots={slots} Required={required} Missed={missed}"); + } + } +} diff --git a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs b/Tests/CodexReleaseTests/Utils/MarketplaceAutoBootstrapDistTest.cs similarity index 70% rename from Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs rename to Tests/CodexReleaseTests/Utils/MarketplaceAutoBootstrapDistTest.cs index 2c6844e9..72442098 100644 --- a/Tests/CodexReleaseTests/MarketTests/MarketplaceAutoBootstrapDistTest.cs +++ b/Tests/CodexReleaseTests/Utils/MarketplaceAutoBootstrapDistTest.cs @@ -1,49 +1,45 @@ using CodexClient; using CodexContractsPlugin; -using CodexContractsPlugin.ChainMonitor; using CodexContractsPlugin.Marketplace; using CodexPlugin; using CodexTests; -using DistTestCore; using GethPlugin; +using Logging; using Nethereum.Hex.HexConvertors.Extensions; using NUnit.Framework; using Utils; -namespace CodexReleaseTests.MarketTests +namespace CodexReleaseTests.Utils { public abstract class MarketplaceAutoBootstrapDistTest : AutoBootstrapDistTest { - private readonly Dictionary handles = new Dictionary(); + private MarketplaceHandle handle = null!; protected const int StartingBalanceTST = 1000; protected const int StartingBalanceEth = 10; - protected override void LifecycleStart(TestLifecycle lifecycle) + [SetUp] + public void SetupMarketplace() { - base.LifecycleStart(lifecycle); var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); - var monitor = new ChainMonitor(lifecycle.Log, contracts, lifecycle.TestStart, TimeSpan.FromSeconds(1.0)); - monitor.Start(); - - handles.Add(lifecycle, new MarketplaceHandle(geth, contracts, monitor)); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); + var monitor = SetupChainMonitor(GetTestLog(), contracts, GetTestRunTimeRange().From); + handle = new MarketplaceHandle(geth, contracts, monitor); } - protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) + [TearDown] + public void TearDownMarketplace() { - handles[lifecycle].Monitor.Stop(); - handles.Remove(lifecycle); - base.LifecycleStop(lifecycle, result); + if (handle.ChainMonitor != null) handle.ChainMonitor.Stop(); } protected IGethNode GetGeth() { - return handles[Get()].Geth; + return handle.Geth; } protected ICodexContracts GetContracts() { - return handles[Get()].Contracts; + return handle.Contracts; } protected TimeSpan GetPeriodDuration() @@ -52,24 +48,16 @@ namespace CodexReleaseTests.MarketTests return TimeSpan.FromSeconds(config.Proofs.Period); } - protected PeriodMonitorResult GetPeriodMonitorReports() - { - return handles[Get()].Monitor.GetPeriodReports(); - } - protected abstract int NumberOfHosts { get; } protected abstract int NumberOfClients { get; } protected abstract ByteSize HostAvailabilitySize { get; } protected abstract TimeSpan HostAvailabilityMaxDuration { get; } - protected TimeSpan HostBlockTTL { get; } = TimeSpan.FromMinutes(1.0); + protected virtual bool MonitorChainState { get; } = true; public ICodexNodeGroup StartHosts() { var hosts = StartCodex(NumberOfHosts, s => s .WithName("host") - .WithBlockTTL(HostBlockTTL) - .WithBlockMaintenanceNumber(100) - .WithBlockMaintenanceInterval(HostBlockTTL / 2) .EnableMarketplace(GetGeth(), GetContracts(), m => m .WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst()) .AsStorageNode() @@ -81,47 +69,38 @@ namespace CodexReleaseTests.MarketTests { AssertTstBalance(host, StartingBalanceTST.Tst(), nameof(StartHosts)); AssertEthBalance(host, StartingBalanceEth.Eth(), nameof(StartHosts)); - - var spaceBefore = host.Space(); - Assert.That(spaceBefore.QuotaReservedBytes, Is.EqualTo(0)); - Assert.That(spaceBefore.QuotaUsedBytes, Is.EqualTo(0)); - - host.Marketplace.MakeStorageAvailable(new CreateStorageAvailability( + + host.Marketplace.MakeStorageAvailable(new StorageAvailability( totalSpace: HostAvailabilitySize, maxDuration: HostAvailabilityMaxDuration, minPricePerBytePerSecond: 1.TstWei(), totalCollateral: 999999.Tst()) ); - - var spaceAfter = host.Space(); - Assert.That(spaceAfter.QuotaReservedBytes, Is.EqualTo(HostAvailabilitySize.SizeInBytes)); - Assert.That(spaceAfter.QuotaUsedBytes, Is.EqualTo(0)); } return hosts; } - public void AssertHostAvailabilitiesAreEmpty(IEnumerable hosts) + public ICodexNode StartOneHost() { - var retry = GetAvailabilitySpaceAssertRetry(); - retry.Run(() => - { - foreach (var host in hosts) - { - AssertHostAvailabilitiesAreEmpty(host); - } - }); - } + var host = StartCodex(s => s + .WithName("singlehost") + .EnableMarketplace(GetGeth(), GetContracts(), m => m + .WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst()) + .AsStorageNode() + ) + ); - 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}"); - } - } + var config = GetContracts().Deployment.Config; + AssertTstBalance(host, StartingBalanceTST.Tst(), nameof(StartOneHost)); + AssertEthBalance(host, StartingBalanceEth.Eth(), nameof(StartOneHost)); + + host.Marketplace.MakeStorageAvailable(new StorageAvailability( + totalSpace: HostAvailabilitySize, + maxDuration: HostAvailabilityMaxDuration, + minPricePerBytePerSecond: 1.TstWei(), + totalCollateral: 999999.Tst()) + ); + return host; } public void AssertTstBalance(ICodexNode node, TestToken expectedBalance, string message) @@ -159,6 +138,19 @@ namespace CodexReleaseTests.MarketTests }); } + private ChainMonitor? SetupChainMonitor(ILog log, ICodexContracts contracts, DateTime startUtc) + { + if (!MonitorChainState) return null; + + var result = new ChainMonitor(log, contracts, startUtc); + result.Start(() => + { + log.Error("Failure in chain monitor. No chain updates after this point."); + //Assert.Fail("Failure in chain monitor."); + }); + return result; + } + private Retry GetBalanceAssertRetry() { return new Retry("AssertBalance", @@ -168,15 +160,6 @@ namespace CodexReleaseTests.MarketTests 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); @@ -225,7 +208,7 @@ namespace CodexReleaseTests.MarketTests ); } - public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts, string purchaseId) + public SlotFill[] GetOnChainSlotFills(IEnumerable possibleHosts, string purchaseId) { var fills = GetOnChainSlotFills(possibleHosts); return fills.Where(f => f @@ -233,7 +216,7 @@ namespace CodexReleaseTests.MarketTests .ToArray(); } - public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts) + public SlotFill[] GetOnChainSlotFills(IEnumerable possibleHosts) { var events = GetContracts().GetEvents(GetTestRunTimeRange()); var fills = events.GetSlotFilledEvents(); @@ -245,34 +228,13 @@ namespace CodexReleaseTests.MarketTests }).ToArray(); } - public SlotFree[] GetOnChainSlotFrees(ICodexNodeGroup possibleHosts, string purchaseId) - { - var fills = GetOnChainSlotFrees(possibleHosts); - return fills.Where(f => f - .SlotFreedEvent.RequestId.ToHex(false).ToLowerInvariant() == purchaseId.ToLowerInvariant()) - .ToArray(); - } - - public SlotFree[] GetOnChainSlotFrees(ICodexNodeGroup possibleHosts) - { - var events = GetContracts().GetEvents(GetTestRunTimeRange()); - var fills = GetOnChainSlotFills(possibleHosts); - var frees = events.GetSlotFreedEvents(); - return frees.Select(f => - { - var matchingFill = fills.Single(fill => fill.SlotFilledEvent.RequestId == f.RequestId && - fill.SlotFilledEvent.SlotIndex == f.SlotIndex); - - return new SlotFree(f, matchingFill.Host); - - }).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) @@ -292,7 +254,9 @@ namespace CodexReleaseTests.MarketTests foreach (var pair in expectedBalances) { - AssertTstBalance(pair.Key, pair.Value, "Host was not paid for storage."); + AssertTstBalance(pair.Key, pair.Value, $"Host {pair.Key} was not paid for storage."); + + Log($"Host {pair.Key} was paid for storage. Balance: {pair.Value}"); } } @@ -313,6 +277,30 @@ namespace CodexReleaseTests.MarketTests } } + 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(); + 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 TestToken GetContractFinalCost(TestToken pricePerBytePerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts) { var fills = GetOnChainSlotFills(hosts); @@ -332,7 +320,7 @@ namespace CodexReleaseTests.MarketTests private DateTime GetContractOnChainSubmittedUtc(IStoragePurchaseContract contract) { - return Time.Retry(() => + return Time.Retry(() => { var events = GetContracts().GetEvents(GetTestRunTimeRange()); var submitEvent = events.GetStorageRequests().SingleOrDefault(e => e.RequestId.ToHex(false) == contract.PurchaseId); @@ -391,6 +379,33 @@ namespace CodexReleaseTests.MarketTests }, 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 + chanceOfDowntime + chanceOfDowntime; + } public class SlotFill { public SlotFill(SlotFilledEventDTO slotFilledEvent, ICodexNode host) @@ -403,30 +418,18 @@ namespace CodexReleaseTests.MarketTests public ICodexNode Host { get; } } - public class SlotFree - { - public SlotFree(SlotFreedEventDTO slotFreedEvent, ICodexNode host) - { - SlotFreedEvent = slotFreedEvent; - Host = host; - } - - public SlotFreedEventDTO SlotFreedEvent { get; } - public ICodexNode Host { get; } - } - private class MarketplaceHandle { - public MarketplaceHandle(IGethNode geth, ICodexContracts contracts, ChainMonitor monitor) + public MarketplaceHandle(IGethNode geth, ICodexContracts contracts, ChainMonitor? chainMonitor) { Geth = geth; Contracts = contracts; - Monitor = monitor; + ChainMonitor = chainMonitor; } public IGethNode Geth { get; } public ICodexContracts Contracts { get; } - public ChainMonitor Monitor { get; } + public ChainMonitor? ChainMonitor { get; } } } } diff --git a/Tests/CodexReleaseTests/Utils/PurchaseParams.cs b/Tests/CodexReleaseTests/Utils/PurchaseParams.cs new file mode 100644 index 00000000..483d6f36 --- /dev/null +++ b/Tests/CodexReleaseTests/Utils/PurchaseParams.cs @@ -0,0 +1,67 @@ +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 = NextPowerOf2(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 NextPowerOf2(int 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); + } + } +} diff --git a/Tests/DistTestCore/DistTest.cs b/Tests/DistTestCore/DistTest.cs index 534eb74a..4c6cb0af 100644 --- a/Tests/DistTestCore/DistTest.cs +++ b/Tests/DistTestCore/DistTest.cs @@ -12,70 +12,45 @@ using Assert = NUnit.Framework.Assert; namespace DistTestCore { [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public abstract class DistTest { - private const string TestNamespacePrefix = "cdx-"; - private readonly Configuration configuration = new Configuration(); - private readonly Assembly[] testAssemblies; + private static readonly Global global = new Global(); private readonly FixtureLog fixtureLog; private readonly StatusLog statusLog; - private readonly object lifecycleLock = new object(); - private readonly EntryPoint globalEntryPoint; - private readonly Dictionary lifecycles = new Dictionary(); - private readonly string deployId; - + private readonly TestLifecycle lifecycle; + private readonly string deployId = NameUtils.MakeDeployId(); + public DistTest() { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - testAssemblies = assemblies.Where(a => a.FullName!.ToLowerInvariant().Contains("test")).ToArray(); - - deployId = NameUtils.MakeDeployId(); - - var logConfig = configuration.GetLogConfig(); + var logConfig = global.Configuration.GetLogConfig(); var startTime = DateTime.UtcNow; fixtureLog = FixtureLog.Create(logConfig, startTime, deployId); statusLog = new StatusLog(logConfig, startTime, "dist-tests", deployId); - globalEntryPoint = new EntryPoint(fixtureLog, configuration.GetK8sConfiguration(new DefaultK8sTimeSet(), TestNamespacePrefix), configuration.GetFileManagerFolder()); + fixtureLog.Log("Test framework revision: " + GitInfo.GetStatus()); + + lifecycle = new TestLifecycle(fixtureLog.CreateTestLog(startTime), global.Configuration, + GetWebCallTimeSet(), + GetK8sTimeSet(), + Global.TestNamespacePrefix + Guid.NewGuid().ToString(), + deployId, + ShouldWaitForCleanup() + ); Initialize(fixtureLog); } [OneTimeSetUp] - public void GlobalSetup() + public static void GlobalSetup() { - fixtureLog.Log($"Starting..."); - globalEntryPoint.Announce(); - - // Previous test run may have been interrupted. - // Begin by cleaning everything up. - try - { - Stopwatch.Measure(fixtureLog, "Global setup", () => - { - globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix, wait: true); - }); - } - catch (Exception ex) - { - GlobalTestFailure.HasFailed = true; - fixtureLog.Error($"Global setup cleanup failed with: {ex}"); - throw; - } - - fixtureLog.Log("Test framework revision: " + GitInfo.GetStatus()); - fixtureLog.Log("Global setup cleanup successful"); + global.Setup(); } [OneTimeTearDown] - public void GlobalTearDown() + public static void GlobalTearDown() { - globalEntryPoint.Decommission( - // There shouldn't be any of either, but clean everything up regardless. - deleteKubernetesResources: true, - deleteTrackedFiles: true, - waitTillDone: true - ); + global.TearDown(); } [SetUp] @@ -85,18 +60,6 @@ namespace DistTestCore { Assert.Inconclusive("Skip test: Previous test failed during clean up."); } - else - { - try - { - CreateNewTestLifecycle(); - } - catch (Exception ex) - { - fixtureLog.Error("Setup failed: " + ex); - GlobalTestFailure.HasFailed = true; - } - } } [TearDown] @@ -117,18 +80,18 @@ namespace DistTestCore { get { - return Get().CoreInterface; + return lifecycle.CoreInterface; } } public TrackedFile GenerateTestFile(ByteSize size, string label = "") { - return Get().GenerateTestFile(size, label); + return lifecycle.GenerateTestFile(size, label); } public TrackedFile GenerateTestFile(Action options, string label = "") { - return Get().GenerateTestFile(options, label); + return lifecycle.GenerateTestFile(options, label); } /// @@ -137,12 +100,22 @@ namespace DistTestCore /// public void ScopedTestFiles(Action action) { - Get().GetFileManager().ScopedFiles(action); + lifecycle.GetFileManager().ScopedFiles(action); } public ILog GetTestLog() { - return Get().Log; + return lifecycle.Log; + } + + public IFileManager GetFileManager() + { + return lifecycle.GetFileManager(); + } + + public string GetTestNamespace() + { + return lifecycle.TestNamespace; } public void Log(string msg) @@ -159,64 +132,24 @@ namespace DistTestCore public void Measure(string name, Action action) { - Stopwatch.Measure(Get().Log, name, action); + Stopwatch.Measure(lifecycle.Log, name, action); } protected TimeRange GetTestRunTimeRange() { - return new TimeRange(Get().TestStart, DateTime.UtcNow); + return new TimeRange(lifecycle.TestStartUtc, DateTime.UtcNow); } protected virtual void Initialize(FixtureLog fixtureLog) { } - protected virtual void LifecycleStart(TestLifecycle lifecycle) - { - } - - protected virtual void LifecycleStop(TestLifecycle lifecycle, DistTestResult testResult) - { - } - protected virtual void CollectStatusLogData(TestLifecycle lifecycle, Dictionary data) { } - protected TestLifecycle Get() - { - lock (lifecycleLock) - { - return lifecycles[GetCurrentTestName()]; - } - } - - private void CreateNewTestLifecycle() - { - var testName = GetCurrentTestName(); - fixtureLog.WriteLogTag(); - Stopwatch.Measure(fixtureLog, $"Setup for {testName}", () => - { - lock (lifecycleLock) - { - var testNamespace = TestNamespacePrefix + Guid.NewGuid().ToString(); - var lifecycle = new TestLifecycle( - fixtureLog.CreateTestLog(), - configuration, - GetWebCallTimeSet(), - GetK8sTimeSet(), - testNamespace, - deployId, - ShouldWaitForCleanup()); - lifecycles.Add(testName, lifecycle); - LifecycleStart(lifecycle); - } - }); - } - private void DisposeTestLifecycle() { - var lifecycle = Get(); var testResult = GetTestResult(); var testDuration = lifecycle.GetTestDuration(); var data = lifecycle.GetPluginMetadata(); @@ -228,9 +161,7 @@ namespace DistTestCore WriteEndTestLog(lifecycle.Log); IncludeLogsOnTestFailure(lifecycle); - LifecycleStop(lifecycle, testResult); lifecycle.DeleteAllResources(); - lifecycles.Remove(GetCurrentTestName()); }); } @@ -287,7 +218,7 @@ namespace DistTestCore var className = currentTest.ClassName; var methodName = currentTest.MethodName; - var testClasses = testAssemblies.SelectMany(a => a.GetTypes()).Where(c => c.FullName == className).ToArray(); + var testClasses = global.TestAssemblies.SelectMany(a => a.GetTypes()).Where(c => c.FullName == className).ToArray(); var testMethods = testClasses.SelectMany(c => c.GetMethods()).Where(m => m.Name == methodName).ToArray(); return testMethods.Select(m => m.GetCustomAttribute()) @@ -296,19 +227,24 @@ namespace DistTestCore .ToArray(); } + protected IDownloadedLog[] DownloadAllLogs() + { + return lifecycle.DownloadAllLogs(); + } + private void IncludeLogsOnTestFailure(TestLifecycle lifecycle) { var testStatus = TestContext.CurrentContext.Result.Outcome.Status; if (ShouldDownloadAllLogs(testStatus)) { lifecycle.Log.Log("Downloading all container logs..."); - lifecycle.DownloadAllLogs(); + DownloadAllLogs(); } } private bool ShouldDownloadAllLogs(TestStatus testStatus) { - if (configuration.AlwaysDownloadContainerLogs) return true; + if (global.Configuration.AlwaysDownloadContainerLogs) return true; if (!IsDownloadingLogsEnabled()) return false; if (testStatus == TestStatus.Failed) { @@ -323,7 +259,7 @@ namespace DistTestCore return $"[{TestContext.CurrentContext.Test.Name}]"; } - private DistTestResult GetTestResult() + public DistTestResult GetTestResult() { var success = TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Passed; var status = TestContext.CurrentContext.Result.Outcome.Status.ToString(); diff --git a/Tests/DistTestCore/Global.cs b/Tests/DistTestCore/Global.cs new file mode 100644 index 00000000..ddd7a5cc --- /dev/null +++ b/Tests/DistTestCore/Global.cs @@ -0,0 +1,65 @@ +using System.Diagnostics; +using System.Reflection; +using Core; +using Logging; + +namespace DistTestCore +{ + public class Global + { + public const string TestNamespacePrefix = "cdx-"; + public Configuration Configuration { get; } = new Configuration(); + + public Assembly[] TestAssemblies { get; } + private readonly EntryPoint globalEntryPoint; + private readonly ILog log; + + public Global() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + TestAssemblies = assemblies.Where(a => a.FullName!.ToLowerInvariant().Contains("test")).ToArray(); + + log = new ConsoleLog(); + globalEntryPoint = new EntryPoint( + log, + Configuration.GetK8sConfiguration( + new DefaultK8sTimeSet(), + TestNamespacePrefix + ), + Configuration.GetFileManagerFolder() + ); + } + + public void Setup() + { + try + { + Trace.Listeners.Add(new ConsoleTraceListener()); + + Logging.Stopwatch.Measure(log, "Global setup", () => + { + globalEntryPoint.Announce(); + globalEntryPoint.Tools.CreateWorkflow().DeleteNamespacesStartingWith(TestNamespacePrefix, wait: true); + }); + } + catch (Exception ex) + { + GlobalTestFailure.HasFailed = true; + log.Error($"Global setup cleanup failed with: {ex}"); + throw; + } + } + + public void TearDown() + { + globalEntryPoint.Decommission( + // There shouldn't be any of either, but clean everything up regardless. + deleteKubernetesResources: true, + deleteTrackedFiles: true, + waitTillDone: true + ); + + Trace.Flush(); + } + } +} diff --git a/Tests/DistTestCore/Logs/BaseTestLog.cs b/Tests/DistTestCore/Logs/BaseTestLog.cs index e670e0c1..87df6383 100644 --- a/Tests/DistTestCore/Logs/BaseTestLog.cs +++ b/Tests/DistTestCore/Logs/BaseTestLog.cs @@ -8,7 +8,7 @@ namespace DistTestCore.Logs protected BaseTestLog(ILog backingLog, string deployId) { - this.backingLog = backingLog; + this.backingLog = new TimestampPrefixer(backingLog); DeployId = deployId; } @@ -59,24 +59,8 @@ namespace DistTestCore.Logs protected static ILog CreateMainLog(string fullName, string name) { - ILog log = new FileLog(fullName); - log = ApplyConsoleOutput(log); - return log; - } - - private static ILog ApplyConsoleOutput(ILog log) - { - // If we're running as a release test, we'll split the log output - // to the console as well. - - var testType = Environment.GetEnvironmentVariable("TEST_TYPE"); - if (string.IsNullOrEmpty(testType) || testType.ToLowerInvariant() != "release-tests") - { - return log; - } - return new LogSplitter( - log, + new FileLog(fullName), new ConsoleLog() ); } diff --git a/Tests/DistTestCore/Logs/FixtureLog.cs b/Tests/DistTestCore/Logs/FixtureLog.cs index 9d3c77d3..5978a602 100644 --- a/Tests/DistTestCore/Logs/FixtureLog.cs +++ b/Tests/DistTestCore/Logs/FixtureLog.cs @@ -4,19 +4,14 @@ namespace DistTestCore.Logs { public class FixtureLog : BaseTestLog { - private readonly ILog backingLog; - private readonly string deployId; - public FixtureLog(ILog backingLog, string deployId) : base(backingLog, deployId) { - this.backingLog = backingLog; - this.deployId = deployId; } - public TestLog CreateTestLog(string name = "") + public TestLog CreateTestLog(DateTime start, string name = "") { - return TestLog.Create(this, name); + return TestLog.Create(this, start, name); } public static FixtureLog Create(LogConfig config, DateTime start, string deployId, string name = "") diff --git a/Tests/DistTestCore/Logs/TestLog.cs b/Tests/DistTestCore/Logs/TestLog.cs index 0dca1464..5c38f951 100644 --- a/Tests/DistTestCore/Logs/TestLog.cs +++ b/Tests/DistTestCore/Logs/TestLog.cs @@ -1,5 +1,4 @@ using Logging; -using System.Xml.Linq; namespace DistTestCore.Logs { @@ -11,9 +10,9 @@ namespace DistTestCore.Logs backingLog.Log($"*** Begin: {methodName}"); } - public static TestLog Create(FixtureLog parentLog, string name = "") + public static TestLog Create(FixtureLog parentLog, DateTime start, string name = "") { - var methodName = NameUtils.GetTestMethodName(name); + var methodName = NameUtils.GetTestLogFileName(start, name); var fullName = Path.Combine(parentLog.GetFullName(), methodName); var backingLog = CreateMainLog(fullName, name); return new TestLog(backingLog, methodName, parentLog.DeployId); diff --git a/Tests/DistTestCore/NameUtils.cs b/Tests/DistTestCore/NameUtils.cs index 55489db9..44919d51 100644 --- a/Tests/DistTestCore/NameUtils.cs +++ b/Tests/DistTestCore/NameUtils.cs @@ -5,6 +5,11 @@ namespace DistTestCore { public static class NameUtils { + public static string GetTestLogFileName(DateTime start, string name = "") + { + return $"{Pad(start.Hour)}-{Pad(start.Minute)}-{Pad(start.Second)}Z_{GetTestMethodName(name)}"; + } + public static string GetTestMethodName(string name = "") { if (!string.IsNullOrEmpty(name)) return name; @@ -16,7 +21,7 @@ namespace DistTestCore public static string GetFixtureFullName(LogConfig config, DateTime start, string name) { var folder = DetermineFolder(config, start); - var fixtureName = GetFixtureName(name, start); + var fixtureName = GetRawFixtureName(); return Path.Combine(folder, fixtureName); } @@ -85,13 +90,6 @@ namespace DistTestCore Pad(start.Day)); } - private static string GetFixtureName(string name, DateTime start) - { - var fixtureName = GetRawFixtureName(); - if (!string.IsNullOrEmpty(name)) fixtureName = name; - return $"{Pad(start.Hour)}-{Pad(start.Minute)}-{Pad(start.Second)}Z_{fixtureName.Replace('.', '-')}"; - } - private static string Pad(int n) { return n.ToString().PadLeft(2, '0'); diff --git a/Tests/DistTestCore/TestLifecycle.cs b/Tests/DistTestCore/TestLifecycle.cs index 3d642d20..53d0f177 100644 --- a/Tests/DistTestCore/TestLifecycle.cs +++ b/Tests/DistTestCore/TestLifecycle.cs @@ -26,7 +26,7 @@ namespace DistTestCore WebCallTimeSet = webCallTimeSet; K8STimeSet = k8sTimeSet; TestNamespace = testNamespace; - TestStart = DateTime.UtcNow; + TestStartUtc = DateTime.UtcNow; entryPoint = new EntryPoint(log, configuration.GetK8sConfiguration(k8sTimeSet, this, testNamespace), configuration.GetFileManagerFolder(), webCallTimeSet, k8sTimeSet); metadata = entryPoint.GetPluginMetadata(); @@ -36,7 +36,7 @@ namespace DistTestCore log.WriteLogTag(); } - public DateTime TestStart { get; } + public DateTime TestStartUtc { get; } public TestLog Log { get; } public Configuration Configuration { get; } public IWebCallTimeSet WebCallTimeSet { get; } @@ -76,7 +76,7 @@ namespace DistTestCore public TimeSpan GetTestDuration() { - return DateTime.UtcNow - TestStart; + return DateTime.UtcNow - TestStartUtc; } public void OnContainersStarted(RunningPod rc) diff --git a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs index d39c31ec..93fcf6f7 100644 --- a/Tests/ExperimentalTests/AutoBootstrapDistTest.cs +++ b/Tests/ExperimentalTests/AutoBootstrapDistTest.cs @@ -1,47 +1,35 @@ using CodexClient; using CodexPlugin; -using DistTestCore; using NUnit.Framework; namespace CodexTests { public class AutoBootstrapDistTest : CodexDistTest { - private readonly Dictionary bootstrapNodes = new Dictionary(); + private bool isBooting = false; + + public ICodexNode BootstrapNode { get; private set; } = null!; [SetUp] - public void SetUpBootstrapNode() + public void SetupBootstrapNode() { - var tl = Get(); - if (!bootstrapNodes.ContainsKey(tl)) - { - bootstrapNodes.Add(tl, StartCodex(s => s.WithName("BOOTSTRAP_" + tl.TestNamespace))); - } + isBooting = true; + BootstrapNode = StartCodex(s => s.WithName("BOOTSTRAP_" + GetTestNamespace())); + isBooting = false; } [TearDown] public void TearDownBootstrapNode() { - bootstrapNodes.Remove(Get()); + BootstrapNode.Stop(waitTillStopped: false); } protected override void OnCodexSetup(ICodexSetup setup) { + if (isBooting) return; + var node = BootstrapNode; if (node != null) setup.WithBootstrapNode(node); } - - protected ICodexNode? BootstrapNode - { - get - { - var tl = Get(); - if (bootstrapNodes.TryGetValue(tl, out var node)) - { - return node; - } - return null; - } - } } } diff --git a/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs b/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs index 7ad08ad3..803f6d72 100644 --- a/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs +++ b/Tests/ExperimentalTests/BasicTests/MarketplaceTests.cs @@ -31,7 +31,7 @@ namespace ExperimentalTests.BasicTests ); var geth = StartGethNode(s => s.IsMiner().WithName("disttest-geth")); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); var numberOfHosts = 5; var hosts = StartCodex(numberOfHosts, s => s @@ -48,7 +48,7 @@ namespace ExperimentalTests.BasicTests foreach (var host in hosts) { - AssertBalance(contracts, host, Is.EqualTo(hostInitialBalance)); + AssertBalance(contracts, host, Is.EqualTo(hostInitialBalance), "Host initial balance"); var availability = new CreateStorageAvailability( totalSpace: 10.GB(), @@ -66,7 +66,7 @@ namespace ExperimentalTests.BasicTests .EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), clientInitialBalance))); - AssertBalance(contracts, client, Is.EqualTo(clientInitialBalance)); + AssertBalance(contracts, client, Is.EqualTo(clientInitialBalance), "Client initial balance"); var uploadCid = client.UploadFile(testFile); diff --git a/Tests/ExperimentalTests/CodexDistTest.cs b/Tests/ExperimentalTests/CodexDistTest.cs index 78c548f9..81f3d051 100644 --- a/Tests/ExperimentalTests/CodexDistTest.cs +++ b/Tests/ExperimentalTests/CodexDistTest.cs @@ -1,6 +1,5 @@ using BlockchainUtils; using CodexClient; -using CodexClient.Hooks; using CodexContractsPlugin; using CodexNetDeployer; using CodexPlugin; @@ -17,85 +16,14 @@ using Newtonsoft.Json; using NUnit.Framework; using NUnit.Framework.Constraints; using OverwatchTranscript; -using Utils; namespace CodexTests { - public class CodexLogTrackerProvider : ICodexHooksProvider - { - private readonly Action addNode; - - public CodexLogTrackerProvider(Action addNode) - { - this.addNode = addNode; - } - - // See TestLifecycle.cs DownloadAllLogs() - public ICodexNodeHooks CreateHooks(string nodeName) - { - return new CodexLogTracker(addNode); - } - - public class CodexLogTracker : ICodexNodeHooks - { - private readonly Action addNode; - - public CodexLogTracker(Action addNode) - { - this.addNode = addNode; - } - - public void OnFileDownloaded(ByteSize size, ContentId cid) - { - } - - public void OnFileDownloading(ContentId cid) - { - } - - public void OnFileUploaded(string uid, ByteSize size, ContentId cid) - { - } - - public void OnFileUploading(string uid, ByteSize size) - { - } - - public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) - { - addNode(node); - } - - public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) - { - } - - public void OnNodeStopping() - { - } - - public void OnStorageAvailabilityCreated(StorageAvailability response) - { - } - - public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) - { - } - - public void OnStorageContractUpdated(StoragePurchase purchaseStatus) - { - } - } - } - public class CodexDistTest : DistTest { - private static readonly object _lock = new object(); - private static readonly Dictionary writers = new Dictionary(); - private static readonly Dictionary blockCaches = new Dictionary(); - - // this entire structure is not good and needs to be destroyed at the earliest convenience: - private static readonly Dictionary> nodes = new Dictionary>(); + private readonly BlockCache blockCache = new BlockCache(); + private readonly List nodes = new List(); + private CodexTranscriptWriter? writer; public CodexDistTest() { @@ -105,41 +33,25 @@ namespace CodexTests ProjectPlugin.Load(); } + [SetUp] + public void SetupCodexDistTest() + { + writer = SetupTranscript(); + } + + [TearDown] + public void TearDownCodexDistTest() + { + TeardownTranscript(); + } + protected override void Initialize(FixtureLog fixtureLog) { var localBuilder = new LocalCodexBuilder(fixtureLog); localBuilder.Intialize(); localBuilder.Build(); - } - protected override void LifecycleStart(TestLifecycle lifecycle) - { - base.LifecycleStart(lifecycle); - SetupTranscript(lifecycle); - - Ci.AddCodexHooksProvider(new CodexLogTrackerProvider(n => - { - lock (_lock) - { - if (!nodes.ContainsKey(lifecycle)) nodes.Add(lifecycle, new List()); - nodes[lifecycle].Add(n); - } - })); - } - - protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result) - { - base.LifecycleStop(lifecycle, result); - TeardownTranscript(lifecycle, result); - - if (!result.Success) - { - lock (_lock) - { - var codexNodes = nodes[lifecycle]; - foreach (var node in codexNodes) node.DownloadLog(); - } - } + Ci.AddCodexHooksProvider(new CodexLogTrackerProvider(nodes.Add)); } public ICodexNode StartCodex() @@ -170,7 +82,7 @@ namespace CodexTests public IGethNode StartGethNode(Action setup) { - return Ci.StartGethNode(GetBlockCache(), setup); + return Ci.StartGethNode(blockCache, setup); } public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers() @@ -180,10 +92,10 @@ namespace CodexTests public PeerDownloadTestHelpers CreatePeerDownloadTestHelpers() { - return new PeerDownloadTestHelpers(GetTestLog(), Get().GetFileManager()); + return new PeerDownloadTestHelpers(GetTestLog(), GetFileManager()); } - public void AssertBalance(ICodexContracts contracts, ICodexNode codexNode, Constraint constraint, string msg = "") + 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); @@ -259,82 +171,47 @@ namespace CodexTests return null; } - private void SetupTranscript(TestLifecycle lifecycle) + private CodexTranscriptWriter? SetupTranscript() { var attr = GetTranscriptAttributeOfCurrentTest(); - if (attr == null) return; + if (attr == null) return null; var config = new CodexTranscriptWriterConfig( + attr.OutputFilename, attr.IncludeBlockReceivedEvents ); - var log = new LogPrefixer(lifecycle.Log, "(Transcript) "); + var log = new LogPrefixer(GetTestLog(), "(Transcript) "); var writer = new CodexTranscriptWriter(log, config, Transcript.NewWriter(log)); Ci.AddCodexHooksProvider(writer); - lock (_lock) - { - writers.Add(lifecycle, writer); - } + return writer; } - private void TeardownTranscript(TestLifecycle lifecycle, DistTestResult result) + private void TeardownTranscript() { - var attr = GetTranscriptAttributeOfCurrentTest(); - if (attr == null) return; - - var outputFilepath = GetOutputFullPath(lifecycle, attr); - - CodexTranscriptWriter writer = null!; - lock (_lock) - { - writer = writers[lifecycle]; - writers.Remove(lifecycle); - } + if (writer == null) return; + var result = GetTestResult(); + var log = GetTestLog(); writer.AddResult(result.Success, result.Result); - try { - Stopwatch.Measure(lifecycle.Log, "Transcript.ProcessLogs", () => + Stopwatch.Measure(log, "Transcript.ProcessLogs", () => { - writer.ProcessLogs(lifecycle.DownloadAllLogs()); + writer.ProcessLogs(DownloadAllLogs()); }); - Stopwatch.Measure(lifecycle.Log, $"Transcript.Finalize: {outputFilepath}", () => + Stopwatch.Measure(log, $"Transcript.FinalizeWriter", () => { - writer.IncludeFile(lifecycle.Log.GetFullName()); - writer.Finalize(outputFilepath); + writer.IncludeFile(log.GetFullName() + ".log"); + writer.FinalizeWriter(); }); } catch (Exception ex) { - lifecycle.Log.Error("Failure during transcript teardown: " + ex); + log.Error("Failure during transcript teardown: " + ex); } } - - private string GetOutputFullPath(TestLifecycle lifecycle, CreateTranscriptAttribute attr) - { - var outputPath = Path.GetDirectoryName(lifecycle.Log.GetFullName()); - if (outputPath == null) throw new Exception("Logfile path is null"); - var filename = Path.GetFileNameWithoutExtension(lifecycle.Log.GetFullName()); - if (string.IsNullOrEmpty(filename)) throw new Exception("Logfile name is null or empty"); - var outputFile = Path.Combine(outputPath, filename + "_" + attr.OutputFilename); - if (!outputFile.EndsWith(".owts")) outputFile += ".owts"; - return outputFile; - } - - private BlockCache GetBlockCache() - { - var lifecycle = Get(); - lock (_lock) - { - if (!blockCaches.ContainsKey(lifecycle)) - { - blockCaches[lifecycle] = new BlockCache(); - } - } - return blockCaches[lifecycle]; - } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] diff --git a/Tests/ExperimentalTests/CodexLogTrackerProvider.cs b/Tests/ExperimentalTests/CodexLogTrackerProvider.cs new file mode 100644 index 00000000..1b2fdd66 --- /dev/null +++ b/Tests/ExperimentalTests/CodexLogTrackerProvider.cs @@ -0,0 +1,73 @@ +using CodexClient; +using CodexClient.Hooks; +using Utils; + +namespace CodexTests +{ + public class CodexLogTrackerProvider : ICodexHooksProvider + { + private readonly Action addNode; + + public CodexLogTrackerProvider(Action addNode) + { + this.addNode = addNode; + } + + // See TestLifecycle.cs DownloadAllLogs() + public ICodexNodeHooks CreateHooks(string nodeName) + { + return new CodexLogTracker(addNode); + } + + public class CodexLogTracker : ICodexNodeHooks + { + private readonly Action addNode; + + public CodexLogTracker(Action addNode) + { + this.addNode = addNode; + } + + public void OnFileDownloaded(ByteSize size, ContentId cid) + { + } + + public void OnFileDownloading(ContentId cid) + { + } + + public void OnFileUploaded(string uid, ByteSize size, ContentId cid) + { + } + + public void OnFileUploading(string uid, ByteSize size) + { + } + + public void OnNodeStarted(ICodexNode node, string peerId, string nodeId) + { + addNode(node); + } + + public void OnNodeStarting(DateTime startUtc, string image, EthAccount? ethAccount) + { + } + + public void OnNodeStopping() + { + } + + public void OnStorageAvailabilityCreated(StorageAvailability response) + { + } + + public void OnStorageContractSubmitted(StoragePurchaseContract storagePurchaseContract) + { + } + + public void OnStorageContractUpdated(StoragePurchase purchaseStatus) + { + } + } + } +} diff --git a/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs b/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs index ec128154..d0acc991 100644 --- a/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs +++ b/Tests/ExperimentalTests/DownloadConnectivityTests/FullyConnectedDownloadTests.cs @@ -21,7 +21,7 @@ namespace ExperimentalTests.DownloadConnectivityTests public void MarketplaceDoesNotInterfereWithPeerDownload() { var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TstWei()))); diff --git a/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs b/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs index 901504c7..60a9be9a 100644 --- a/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs +++ b/Tests/ExperimentalTests/PeerDiscoveryTests/PeerDiscoveryTests.cs @@ -31,7 +31,7 @@ namespace ExperimentalTests.PeerDiscoveryTests public void MarketplaceDoesNotInterfereWithPeerDiscovery() { var geth = StartGethNode(s => s.IsMiner()); - var contracts = Ci.StartCodexContracts(geth); + var contracts = Ci.StartCodexContracts(geth, BootstrapNode.Version); var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m .WithInitial(10.Eth(), 1000.TstWei()))); diff --git a/Tests/FrameworkTests/Utils/EthAccountEqualityTests.cs b/Tests/FrameworkTests/Utils/EthAccountEqualityTests.cs new file mode 100644 index 00000000..337516ae --- /dev/null +++ b/Tests/FrameworkTests/Utils/EthAccountEqualityTests.cs @@ -0,0 +1,31 @@ +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); + } + } +} diff --git a/Tools/AutoClient/CodexWrapper.cs b/Tools/AutoClient/CodexWrapper.cs index e91c3cfa..02fcca3e 100644 --- a/Tools/AutoClient/CodexWrapper.cs +++ b/Tools/AutoClient/CodexWrapper.cs @@ -7,6 +7,7 @@ namespace AutoClient public class CodexWrapper { private readonly App app; + private static readonly Random r = new Random(); public CodexWrapper(App app, ICodexNode node) { @@ -26,11 +27,11 @@ namespace AutoClient var result = Node.Marketplace.RequestStorage(new StoragePurchaseRequest(cid) { CollateralPerByte = app.Config.CollateralPerByte.TstWei(), - Duration = TimeSpan.FromMinutes(app.Config.ContractDurationMinutes), + Duration = GetDuration(), Expiry = TimeSpan.FromMinutes(app.Config.ContractExpiryMinutes), MinRequiredNumberOfNodes = Convert.ToUInt32(app.Config.NumHosts), NodeFailureTolerance = Convert.ToUInt32(app.Config.HostTolerance), - PricePerBytePerSecond = app.Config.PricePerBytePerSecond.TstWei(), + PricePerBytePerSecond = GetPricePerBytePerSecond(), ProofProbability = 15 }); return result; @@ -40,5 +41,25 @@ namespace AutoClient { return Node.GetPurchaseStatus(pid); } + + private TestToken GetPricePerBytePerSecond() + { + var i = app.Config.PricePerBytePerSecond; + i -= 100; + i += r.Next(0, 1000); + + return i.TstWei(); + } + + private TimeSpan GetDuration() + { + var i = app.Config.ContractDurationMinutes; + var day = 60 * 24; + i -= day; + i -= 10; // We don't want to accidentally hit exactly 7 days because that's the limit of the storage node availabilities. + i += r.Next(0, day * 2); + + return TimeSpan.FromMinutes(i); + } } } diff --git a/Tools/AutoClient/Configuration.cs b/Tools/AutoClient/Configuration.cs index 13aea6fd..65996333 100644 --- a/Tools/AutoClient/Configuration.cs +++ b/Tools/AutoClient/Configuration.cs @@ -59,6 +59,9 @@ namespace AutoClient "/root/codex-testnet-starter/scripts/eth_7.address" + ";" + "/root/codex-testnet-starter/scripts/eth_8.address"; + [Uniform("slowModeDelayMinutes", "smdm", "SLOWMODEDELAYMINUTES", false, "When contract failure threshold is reached, slow down process for each file by this amount of minutes.")] + public int SlowModeDelayMinutes { get; set; } = 60 * 1; + public string LogPath { get diff --git a/Tools/AutoClient/Modes/FolderStore/FileSaver.cs b/Tools/AutoClient/Modes/FolderStore/FileSaver.cs index 327eb00b..448755f0 100644 --- a/Tools/AutoClient/Modes/FolderStore/FileSaver.cs +++ b/Tools/AutoClient/Modes/FolderStore/FileSaver.cs @@ -7,6 +7,11 @@ namespace AutoClient.Modes.FolderStore public interface IFileSaverEventHandler { void SaveChanges(); + } + + public interface IFileSaverResultHandler + { + void OnSuccess(); void OnFailure(); } @@ -17,16 +22,18 @@ namespace AutoClient.Modes.FolderStore private readonly Stats stats; private readonly string folderFile; private readonly FileStatus entry; - private readonly IFileSaverEventHandler handler; + private readonly IFileSaverEventHandler saveHandler; + private readonly IFileSaverResultHandler resultHandler; - public FileSaver(ILog log, LoadBalancer loadBalancer, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler handler) + public FileSaver(ILog log, LoadBalancer loadBalancer, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler saveHandler, IFileSaverResultHandler resultHandler) { this.log = log; this.loadBalancer = loadBalancer; this.stats = stats; this.folderFile = folderFile; this.entry = entry; - this.handler = handler; + this.saveHandler = saveHandler; + this.resultHandler = resultHandler; } public void Process() @@ -46,9 +53,9 @@ namespace AutoClient.Modes.FolderStore loadBalancer.DispatchOnCodex(instance => { entry.CodexNodeId = instance.Node.GetName(); - handler.SaveChanges(); + saveHandler.SaveChanges(); - var run = new FileSaverRun(log, instance, stats, folderFile, entry, handler); + var run = new FileSaverRun(log, instance, stats, folderFile, entry, saveHandler, resultHandler); run.Process(); }); } @@ -57,7 +64,7 @@ namespace AutoClient.Modes.FolderStore { loadBalancer.DispatchOnSpecificCodex(instance => { - var run = new FileSaverRun(log, instance, stats, folderFile, entry, handler); + var run = new FileSaverRun(log, instance, stats, folderFile, entry, saveHandler, resultHandler); run.Process(); }, entry.CodexNodeId); } @@ -70,17 +77,19 @@ namespace AutoClient.Modes.FolderStore private readonly Stats stats; private readonly string folderFile; private readonly FileStatus entry; - private readonly IFileSaverEventHandler handler; + private readonly IFileSaverEventHandler saveHandler; + private readonly IFileSaverResultHandler resultHandler; private readonly QuotaCheck quotaCheck; - public FileSaverRun(ILog log, CodexWrapper instance, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler handler) + public FileSaverRun(ILog log, CodexWrapper instance, Stats stats, string folderFile, FileStatus entry, IFileSaverEventHandler saveHandler, IFileSaverResultHandler resultHandler) { this.log = log; this.instance = instance; this.stats = stats; this.folderFile = folderFile; this.entry = entry; - this.handler = handler; + this.saveHandler = saveHandler; + this.resultHandler = resultHandler; quotaCheck = new QuotaCheck(log, folderFile, instance); } @@ -127,7 +136,7 @@ namespace AutoClient.Modes.FolderStore Thread.Sleep(TimeSpan.FromMinutes(1.0)); } Log("Could not upload: Insufficient local storage quota."); - handler.OnFailure(); + resultHandler.OnFailure(); return false; } @@ -206,9 +215,9 @@ namespace AutoClient.Modes.FolderStore entry.BasicCid = string.Empty; stats.FailedUploads++; log.Error("Failed to upload: " + exc); - handler.OnFailure(); + resultHandler.OnFailure(); } - handler.SaveChanges(); + saveHandler.SaveChanges(); } private void CreateNewPurchase() @@ -224,17 +233,18 @@ namespace AutoClient.Modes.FolderStore WaitForStarted(request); stats.StorageRequestStats.SuccessfullyStarted++; - handler.SaveChanges(); + saveHandler.SaveChanges(); Log($"Successfully started new purchase: '{entry.PurchaseId}' for {Time.FormatDuration(request.Purchase.Duration)}"); + resultHandler.OnSuccess(); } catch (Exception exc) { - entry.ClearPurchase(); - handler.SaveChanges(); - + entry.EncodedCid = string.Empty; + entry.PurchaseId = string.Empty; + saveHandler.SaveChanges(); log.Error("Failed to start new purchase: " + exc); - handler.OnFailure(); + resultHandler.OnFailure(); } } @@ -253,7 +263,7 @@ namespace AutoClient.Modes.FolderStore throw new Exception("CID received from storage request was not protected."); } - handler.SaveChanges(); + saveHandler.SaveChanges(); Log("Saved new purchaseId: " + entry.PurchaseId); return request; } @@ -287,9 +297,9 @@ namespace AutoClient.Modes.FolderStore else if (!update.IsSubmitted) { Log("Request failed to start. State: " + update.State); - - entry.ClearPurchase(); - handler.SaveChanges(); + entry.EncodedCid = string.Empty; + entry.PurchaseId = string.Empty; + saveHandler.SaveChanges(); return; } } @@ -297,7 +307,7 @@ namespace AutoClient.Modes.FolderStore } catch (Exception exc) { - handler.OnFailure(); + resultHandler.OnFailure(); Log($"Exception in {nameof(WaitForSubmittedToStarted)}: {exc}"); throw; } diff --git a/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs b/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs index 4d090e6d..d951aaee 100644 --- a/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs +++ b/Tools/AutoClient/Modes/FolderStore/FolderSaver.cs @@ -11,14 +11,16 @@ namespace AutoClient.Modes.FolderStore private readonly JsonFile statusFile; private readonly FolderStatus status; private readonly BalanceChecker balanceChecker; + private readonly SlowModeHandler slowModeHandler; private int changeCounter = 0; - private int failureCount = 0; + private int saveFolderJsonCounter = 0; public FolderSaver(App app, LoadBalancer loadBalancer) { this.app = app; this.loadBalancer = loadBalancer; balanceChecker = new BalanceChecker(app); + slowModeHandler = new SlowModeHandler(app); statusFile = new JsonFile(app, Path.Combine(app.Config.FolderToStore, FolderSaverFilename)); status = statusFile.Load(); @@ -26,10 +28,11 @@ namespace AutoClient.Modes.FolderStore public void Run() { + saveFolderJsonCounter = 0; + var folderFiles = Directory.GetFiles(app.Config.FolderToStore); if (!folderFiles.Any()) throw new Exception("No files found in " + app.Config.FolderToStore); - var saveFolderJsonCounter = 0; balanceChecker.Check(); foreach (var folderFile in folderFiles) { @@ -41,35 +44,30 @@ namespace AutoClient.Modes.FolderStore SaveFile(folderFile); } - if (failureCount > 3) - { - app.Log.Error("Failure count reached threshold. Stopping..."); - app.Cts.Cancel(); - return; - } - - if (changeCounter > 1) - { - changeCounter = 0; - saveFolderJsonCounter++; - if (saveFolderJsonCounter > 5) - { - saveFolderJsonCounter = 0; - if (failureCount > 0) - { - app.Log.Log($"Failure count is reset. (Was: {failureCount})"); - failureCount = 0; - } - balanceChecker.Check(); - SaveFolderSaverJsonFile(); - } - } + slowModeHandler.Check(); + + CheckAndSaveChanges(); statusFile.Save(status); Thread.Sleep(100); } } + private void CheckAndSaveChanges() + { + if (changeCounter > 1) + { + changeCounter = 0; + saveFolderJsonCounter++; + if (saveFolderJsonCounter > 5) + { + saveFolderJsonCounter = 0; + balanceChecker.Check(); + SaveFolderSaverJsonFile(); + } + } + } + private void SaveFile(string folderFile) { var localFilename = Path.GetFileName(folderFile); @@ -114,7 +112,6 @@ namespace AutoClient.Modes.FolderStore } private const int MinCodexStorageFilesize = 262144; - private readonly Random random = new Random(); private readonly string paddingMessage = $"Codex currently requires a minimum filesize of {MinCodexStorageFilesize} bytes for datasets used in storage contracts. " + $"Anything smaller, and the erasure-coding algorithms used for data durability won't function. Therefore, we apply this padding field to make sure this " + $"file is larger than the minimal size. The following is pseudo-random: "; @@ -135,7 +132,7 @@ namespace AutoClient.Modes.FolderStore { var fixedLength = entry.Filename.PadRight(35); var prefix = $"[{fixedLength}] "; - return new FileSaver(new LogPrefixer(app.Log, prefix), loadBalancer, status.Stats, folderFile, entry, this); + return new FileSaver(new LogPrefixer(app.Log, prefix), loadBalancer, status.Stats, folderFile, entry, this, slowModeHandler); } public void SaveChanges() @@ -143,10 +140,5 @@ namespace AutoClient.Modes.FolderStore statusFile.Save(status); changeCounter++; } - - public void OnFailure() - { - failureCount++; - } } } diff --git a/Tools/AutoClient/Modes/FolderStore/SlowModeHandler.cs b/Tools/AutoClient/Modes/FolderStore/SlowModeHandler.cs new file mode 100644 index 00000000..37d3f7ee --- /dev/null +++ b/Tools/AutoClient/Modes/FolderStore/SlowModeHandler.cs @@ -0,0 +1,54 @@ +namespace AutoClient.Modes.FolderStore +{ + public class SlowModeHandler : IFileSaverResultHandler + { + private readonly App app; + private int failureCount = 0; + private bool slowMode = false; + private int recoveryCount = 0; + + public SlowModeHandler(App app) + { + this.app = app; + } + + public void OnSuccess() + { + failureCount = 0; + if (slowMode) + { + recoveryCount++; + if (recoveryCount > 3) + { + Log("Recovery limit reached. Exiting slow mode."); + slowMode = false; + failureCount = 0; + } + } + } + + public void OnFailure() + { + failureCount++; + if (failureCount > 3 && !slowMode) + { + Log("Failure limit reached. Entering slow mode."); + slowMode = true; + recoveryCount = 0; + } + } + + public void Check() + { + if (slowMode) + { + Thread.Sleep(TimeSpan.FromMinutes(app.Config.SlowModeDelayMinutes)); + } + } + + private void Log(string msg) + { + app.Log.Log(msg); + } + } +} diff --git a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs index c5b9361a..ac83ccd9 100644 --- a/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs +++ b/Tools/BiblioTech/CodexChecking/CodexTwoWayChecker.cs @@ -8,7 +8,7 @@ namespace BiblioTech.CodexChecking public interface ICheckResponseHandler { Task CheckNotStarted(); - Task NowCompleted(ulong userId, string checkName); + Task NowCompleted(string checkName); Task GiveRoleReward(); Task InvalidData(); @@ -37,7 +37,7 @@ namespace BiblioTech.CodexChecking public async Task StartDownloadCheck(ICheckResponseHandler handler, ulong userId) { var check = repo.GetOrCreate(userId).DownloadCheck; - if (string.IsNullOrEmpty(check.UniqueData)) + if (IsUniqueDataStale(check)) { check.UniqueData = GenerateUniqueData(); repo.SaveChanges(); @@ -69,7 +69,7 @@ namespace BiblioTech.CodexChecking public async Task StartUploadCheck(ICheckResponseHandler handler, ulong userId) { var check = repo.GetOrCreate(userId).UploadCheck; - if (string.IsNullOrEmpty(check.UniqueData)) + if (IsUniqueDataStale(check)) { check.UniqueData = GenerateUniqueData(); repo.SaveChanges(); @@ -111,6 +111,15 @@ namespace BiblioTech.CodexChecking return $"{RandomBusyMessage.Get().Substring(5)}{RandomUtils.GenerateRandomString(12)}"; } + private bool IsUniqueDataStale(TransferCheck check) + { + var expiry = DateTime.UtcNow - TimeSpan.FromMinutes(10.0); + + return + string.IsNullOrEmpty(check.UniqueData) || + check.CompletedUtc < expiry; + } + private string UploadData(string uniqueData) { var filePath = Path.Combine(config.ChecksDataPath, Guid.NewGuid().ToString()); @@ -192,7 +201,7 @@ namespace BiblioTech.CodexChecking private async Task CheckNowCompleted(ICheckResponseHandler handler, TransferCheck check, ulong userId, string checkName) { - await handler.NowCompleted(userId, checkName); + await handler.NowCompleted(checkName); check.CompletedUtc = DateTime.UtcNow; repo.SaveChanges(); diff --git a/Tools/BiblioTech/Commands/CheckResponseHandler.cs b/Tools/BiblioTech/Commands/CheckResponseHandler.cs index 5428e86b..f7b1c4f0 100644 --- a/Tools/BiblioTech/Commands/CheckResponseHandler.cs +++ b/Tools/BiblioTech/Commands/CheckResponseHandler.cs @@ -74,10 +74,24 @@ namespace BiblioTech.Commands await context.Followup("The received data didn't match. Check has failed."); } - public async Task NowCompleted(ulong userId, string checkName) + public async Task NowCompleted(string checkName) { - await context.Followup("Successfully completed the check!"); - await Program.AdminChecker.SendInAdminChannel($"User <@{userId}> has completed check: {checkName}"); + // check if eth address is known for user. + var data = Program.UserRepo.GetUser(user); + if (data.CurrentAddress == null) + { + await context.Followup($"Successfully completed the check!{Environment.NewLine}" + + $"You haven't yet set your ethereum address. Consider using '/set' to set it.{Environment.NewLine}" + + $"(You can find your address in the 'eth.address' file of your Codex node.)"); + + await Program.AdminChecker.SendInAdminChannel($"User <@{user.Id}> has completed check: {checkName}" + + $" - EthAddress not set for user. User was reminded."); + } + else + { + await context.Followup("Successfully completed the check!"); + await Program.AdminChecker.SendInAdminChannel($"User <@{user.Id}> has completed check: {checkName}"); + } } public async Task ToAdminChannel(string msg) diff --git a/Tools/CodexNetDeployer/Deployer.cs b/Tools/CodexNetDeployer/Deployer.cs index 753a1371..0d78962e 100644 --- a/Tools/CodexNetDeployer/Deployer.cs +++ b/Tools/CodexNetDeployer/Deployer.cs @@ -64,8 +64,12 @@ namespace CodexNetDeployer var gethDeployment = DeployGeth(ci); var gethNode = ci.WrapGethDeployment(gethDeployment, new BlockCache()); + var bootNode = ci.StartCodexNode(); + var versionInfo = bootNode.GetDebugInfo().Version; + bootNode.Stop(waitTillStopped: true); + Log("Geth started. Deploying Codex contracts..."); - var contractsDeployment = ci.DeployCodexContracts(gethNode); + var contractsDeployment = ci.DeployCodexContracts(gethNode, versionInfo); var contracts = ci.WrapCodexContractsDeployment(gethNode, contractsDeployment); Log("Codex contracts deployed."); diff --git a/Tools/TestNetRewarder/Configuration.cs b/Tools/TestNetRewarder/Configuration.cs index 5dbeac30..b3aa84cb 100644 --- a/Tools/TestNetRewarder/Configuration.cs +++ b/Tools/TestNetRewarder/Configuration.cs @@ -33,6 +33,9 @@ namespace TestNetRewarder [Uniform("proof-submitted-events", "pse", "PROOFSUBMITTEDEVENTS", false, "When greater than zero, chain event summary will include proof-submitted events.")] public int ShowProofSubmittedEvents { get; set; } = 0; // Defaulted to zero, aprox 7 to 10 such events every 2 minutes in testnet (from autoclient alone!) + [Uniform("proof-period-report-hours", "pprh", "PROOFPERIODREPORTHOURS", false, "Frequency in hours with which proof period reports are created.")] + public int ProofReportHours { get; set; } = 24; + public string LogPath { get diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index eed26962..649ebf47 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -22,6 +22,8 @@ namespace TestNetRewarder this.log = log; lastPeriodUpdateUtc = DateTime.UtcNow; + if (config.ProofReportHours < 1) throw new Exception("ProofReportHours must be one or greater"); + builder = new RequestBuilder(); eventsFormatter = new EventsFormatter(config); @@ -79,7 +81,7 @@ namespace TestNetRewarder private void ProcessPeriodUpdate() { if (config.ShowProofPeriodReports < 1) return; - if (DateTime.UtcNow < (lastPeriodUpdateUtc + TimeSpan.FromHours(1.0))) return; + if (DateTime.UtcNow < (lastPeriodUpdateUtc + TimeSpan.FromHours(config.ProofReportHours))) return; lastPeriodUpdateUtc = DateTime.UtcNow; eventsFormatter.ProcessPeriodReports(chainState.PeriodMonitor.GetAndClearReports()); diff --git a/Tools/TraceContract/ChainRequestTracker.cs b/Tools/TraceContract/ChainRequestTracker.cs new file mode 100644 index 00000000..2e313ae8 --- /dev/null +++ b/Tools/TraceContract/ChainRequestTracker.cs @@ -0,0 +1,103 @@ +using System.Numerics; +using BlockchainUtils; +using CodexContractsPlugin.ChainMonitor; +using Utils; + +namespace TraceContract +{ + public class ChainRequestTracker : IChainStateChangeHandler + { + private readonly string requestId; + private readonly Output output; + + public ChainRequestTracker(Output output, string requestId) + { + this.requestId = requestId.ToLowerInvariant(); + this.output = output; + } + + public bool IsFinished { get; private set; } = false; + public DateTime FinishUtc { get; private set; } = DateTime.MinValue; + + public void OnError(string msg) + { + } + + public void OnNewRequest(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) output.LogRequestCreated(requestEvent); + } + + public void OnProofSubmitted(BlockTimeEntry block, string id) + { + } + + public void OnRequestCancelled(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + IsFinished = true; + FinishUtc = requestEvent.Block.Utc; + output.LogRequestCancelled(requestEvent); + } + } + + public void OnRequestFailed(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + IsFinished = true; + FinishUtc = requestEvent.Block.Utc; + output.LogRequestFailed(requestEvent); + } + } + + public void OnRequestFinished(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + IsFinished = true; + FinishUtc = requestEvent.Block.Utc; + output.LogRequestFinished(requestEvent); + } + } + + public void OnRequestFulfilled(RequestEvent requestEvent) + { + if (IsMyRequest(requestEvent)) + { + output.LogRequestStarted(requestEvent); + } + } + + public void OnSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) + { + if (IsMyRequest(requestEvent)) + { + output.LogSlotFilled(requestEvent, host, slotIndex); + } + } + + public void OnSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) + { + if (IsMyRequest(requestEvent)) + { + output.LogSlotFreed(requestEvent, slotIndex); + } + } + + public void OnSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex) + { + if (IsMyRequest(requestEvent)) + { + output.LogSlotReservationsFull(requestEvent, slotIndex); + } + } + + private bool IsMyRequest(RequestEvent requestEvent) + { + return requestId == requestEvent.Request.Request.Id.ToLowerInvariant(); + } + } + +} diff --git a/Tools/TraceContract/ChainTracer.cs b/Tools/TraceContract/ChainTracer.cs new file mode 100644 index 00000000..fc69dd14 --- /dev/null +++ b/Tools/TraceContract/ChainTracer.cs @@ -0,0 +1,125 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using CodexContractsPlugin.Marketplace; +using Logging; +using Nethereum.Hex.HexConvertors.Extensions; +using Utils; + +namespace TraceContract +{ + public class ChainTracer + { + private readonly ILog log; + private readonly ICodexContracts contracts; + private readonly Input input; + private readonly Output output; + + public ChainTracer(ILog log, ICodexContracts contracts, Input input, Output output) + { + this.log = log; + this.contracts = contracts; + this.input = input; + this.output = output; + } + + public TimeRange TraceChainTimeline() + { + log.Log("Querying blockchain..."); + var request = GetRequest(); + if (request == null) throw new Exception("Failed to find the purchase in the last week of transactions."); + + log.Log($"Request started at {request.Block.Utc}"); + var contractEnd = RunToContractEnd(request); + + var requestTimeline = new TimeRange(request.Block.Utc.AddMinutes(-1.0), contractEnd.AddMinutes(1.0)); + log.Log($"Request timeline: {requestTimeline.From} -> {requestTimeline.To}"); + + // For this timeline, we log all the calls to reserve-slot. + var events = contracts.GetEvents(requestTimeline); + + events.GetReserveSlotCalls(call => + { + if (IsThisRequest(call.RequestId)) + { + output.LogReserveSlotCall(call); + log.Log("Found reserve-slot call for slotIndex " + call.SlotIndex); + } + }); + + log.Log("Writing blockchain output..."); + output.WriteContractEvents(); + + return requestTimeline; + } + + private DateTime RunToContractEnd(Request request) + { + var utc = request.Block.Utc.AddMinutes(-1.0); + var tracker = new ChainRequestTracker(output, input.PurchaseId); + var ignoreLog = new NullLog(); + var chainState = new ChainState(ignoreLog, contracts, tracker, utc, false); + + var atNow = false; + while (!tracker.IsFinished && !atNow) + { + utc += TimeSpan.FromHours(1.0); + if (utc > DateTime.UtcNow) + { + log.Log("Caught up to present moment without finding contract end."); + utc = DateTime.UtcNow; + atNow = true; + } + + log.Log($"Querying up to {utc}"); + chainState.Update(utc); + } + + if (atNow) return utc; + return tracker.FinishUtc; + } + + private Request? GetRequest() + { + var request = FindRequest(LastHour()); + if (request == null) request = FindRequest(LastDay()); + if (request == null) request = FindRequest(LastWeek()); + return request; + } + + private Request? FindRequest(TimeRange timeRange) + { + var events = contracts.GetEvents(timeRange); + var requests = events.GetStorageRequests(); + + foreach (var r in requests) + { + if (IsThisRequest(r.RequestId)) + { + return r; + } + } + + return null; + } + + private bool IsThisRequest(byte[] requestId) + { + return requestId.ToHex().ToLowerInvariant() == input.PurchaseId.ToLowerInvariant(); + } + + private static TimeRange LastHour() + { + return new TimeRange(DateTime.UtcNow.AddHours(-1.0), DateTime.UtcNow); + } + + private static TimeRange LastDay() + { + return new TimeRange(DateTime.UtcNow.AddDays(-1.0), DateTime.UtcNow); + } + + private static TimeRange LastWeek() + { + return new TimeRange(DateTime.UtcNow.AddDays(-7.0), DateTime.UtcNow); + } + } +} diff --git a/Tools/TraceContract/Config.cs b/Tools/TraceContract/Config.cs new file mode 100644 index 00000000..3b49a77a --- /dev/null +++ b/Tools/TraceContract/Config.cs @@ -0,0 +1,76 @@ +namespace TraceContract +{ + public class Config + { + public string RpcEndpoint { get; } = "https://rpc.testnet.codex.storage"; + public int GethPort { get; } = 443; + public string MarketplaceAddress { get; } = "0x7c7a749DE7156305E55775e7Ab3931abd6f7300E"; + public string TokenAddress { get; } = "0x34a22f3911De437307c6f4485931779670f78764"; + public string Abi { get; } = @"[{""inputs"":[{""components"":[{""components"":[{""internalType"":""uint8"",""name"":""repairRewardPercentage"",""type"":""uint8""},{""internalType"":""uint8"",""name"":""maxNumberOfSlashes"",""type"":""uint8""},{""internalType"":""uint16"",""name"":""slashCriterion"",""type"":""uint16""},{""internalType"":""uint8"",""name"":""slashPercentage"",""type"":""uint8""}],""internalType"":""struct CollateralConfig"",""name"":""collateral"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""period"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""timeout"",""type"":""uint256""},{""internalType"":""uint8"",""name"":""downtime"",""type"":""uint8""},{""internalType"":""string"",""name"":""zkeyHash"",""type"":""string""}],""internalType"":""struct ProofConfig"",""name"":""proofs"",""type"":""tuple""}],""internalType"":""struct MarketplaceConfig"",""name"":""configuration"",""type"":""tuple""},{""internalType"":""contract IERC20"",""name"":""token_"",""type"":""address""},{""internalType"":""contract IGroth16Verifier"",""name"":""verifier"",""type"":""address""}],""stateMutability"":""nonpayable"",""type"":""constructor""},{""anonymous"":false,""inputs"":[{""indexed"":false,""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""ProofSubmitted"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""RequestCancelled"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""RequestFailed"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""RequestFulfilled"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""indexed"":false,""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""}],""name"":""SlotFilled"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":true,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""indexed"":false,""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""}],""name"":""SlotFreed"",""type"":""event""},{""anonymous"":false,""inputs"":[{""indexed"":false,""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""indexed"":false,""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""indexed"":false,""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""}],""name"":""StorageRequested"",""type"":""event""},{""inputs"":[],""name"":""config"",""outputs"":[{""components"":[{""components"":[{""internalType"":""uint8"",""name"":""repairRewardPercentage"",""type"":""uint8""},{""internalType"":""uint8"",""name"":""maxNumberOfSlashes"",""type"":""uint8""},{""internalType"":""uint16"",""name"":""slashCriterion"",""type"":""uint16""},{""internalType"":""uint8"",""name"":""slashPercentage"",""type"":""uint8""}],""internalType"":""struct CollateralConfig"",""name"":""collateral"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""period"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""timeout"",""type"":""uint256""},{""internalType"":""uint8"",""name"":""downtime"",""type"":""uint8""},{""internalType"":""string"",""name"":""zkeyHash"",""type"":""string""}],""internalType"":""struct ProofConfig"",""name"":""proofs"",""type"":""tuple""}],""internalType"":""struct MarketplaceConfig"",""name"":"""",""type"":""tuple""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""},{""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""a"",""type"":""tuple""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""x"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""y"",""type"":""tuple""}],""internalType"":""struct G2Point"",""name"":""b"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""c"",""type"":""tuple""}],""internalType"":""struct Groth16Proof"",""name"":""proof"",""type"":""tuple""}],""name"":""fillSlot"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""freeSlot"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""getActiveSlot"",""outputs"":[{""components"":[{""components"":[{""internalType"":""address"",""name"":""client"",""type"":""address""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""components"":[{""internalType"":""string"",""name"":""cid"",""type"":""string""},{""internalType"":""bytes32"",""name"":""merkleRoot"",""type"":""bytes32""}],""internalType"":""struct Content"",""name"":""content"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""},{""internalType"":""bytes32"",""name"":""nonce"",""type"":""bytes32""}],""internalType"":""struct Request"",""name"":""request"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""slotIndex"",""type"":""uint256""}],""internalType"":""struct Marketplace.ActiveSlot"",""name"":"""",""type"":""tuple""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""getChallenge"",""outputs"":[{""internalType"":""bytes32"",""name"":"""",""type"":""bytes32""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""getHost"",""outputs"":[{""internalType"":""address"",""name"":"""",""type"":""address""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""getPointer"",""outputs"":[{""internalType"":""uint8"",""name"":"""",""type"":""uint8""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""getRequest"",""outputs"":[{""components"":[{""internalType"":""address"",""name"":""client"",""type"":""address""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""components"":[{""internalType"":""string"",""name"":""cid"",""type"":""string""},{""internalType"":""bytes32"",""name"":""merkleRoot"",""type"":""bytes32""}],""internalType"":""struct Content"",""name"":""content"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""},{""internalType"":""bytes32"",""name"":""nonce"",""type"":""bytes32""}],""internalType"":""struct Request"",""name"":"""",""type"":""tuple""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""isProofRequired"",""outputs"":[{""internalType"":""bool"",""name"":"""",""type"":""bool""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""},{""internalType"":""Periods.Period"",""name"":""period"",""type"":""uint256""}],""name"":""markProofAsMissing"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""missingProofs"",""outputs"":[{""internalType"":""uint256"",""name"":"""",""type"":""uint256""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[],""name"":""myRequests"",""outputs"":[{""internalType"":""RequestId[]"",""name"":"""",""type"":""bytes32[]""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[],""name"":""mySlots"",""outputs"":[{""internalType"":""SlotId[]"",""name"":"""",""type"":""bytes32[]""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""requestEnd"",""outputs"":[{""internalType"":""uint256"",""name"":"""",""type"":""uint256""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""requestExpiry"",""outputs"":[{""internalType"":""uint256"",""name"":"""",""type"":""uint256""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""requestState"",""outputs"":[{""internalType"":""enum RequestState"",""name"":"""",""type"":""uint8""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""components"":[{""internalType"":""address"",""name"":""client"",""type"":""address""},{""components"":[{""internalType"":""uint64"",""name"":""slots"",""type"":""uint64""},{""internalType"":""uint256"",""name"":""slotSize"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""duration"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""proofProbability"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""reward"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""collateral"",""type"":""uint256""},{""internalType"":""uint64"",""name"":""maxSlotLoss"",""type"":""uint64""}],""internalType"":""struct Ask"",""name"":""ask"",""type"":""tuple""},{""components"":[{""internalType"":""string"",""name"":""cid"",""type"":""string""},{""internalType"":""bytes32"",""name"":""merkleRoot"",""type"":""bytes32""}],""internalType"":""struct Content"",""name"":""content"",""type"":""tuple""},{""internalType"":""uint256"",""name"":""expiry"",""type"":""uint256""},{""internalType"":""bytes32"",""name"":""nonce"",""type"":""bytes32""}],""internalType"":""struct Request"",""name"":""request"",""type"":""tuple""}],""name"":""requestStorage"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""slotId"",""type"":""bytes32""}],""name"":""slotState"",""outputs"":[{""internalType"":""enum SlotState"",""name"":"""",""type"":""uint8""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""a"",""type"":""tuple""},{""components"":[{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""x"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""real"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""imag"",""type"":""uint256""}],""internalType"":""struct Fp2Element"",""name"":""y"",""type"":""tuple""}],""internalType"":""struct G2Point"",""name"":""b"",""type"":""tuple""},{""components"":[{""internalType"":""uint256"",""name"":""x"",""type"":""uint256""},{""internalType"":""uint256"",""name"":""y"",""type"":""uint256""}],""internalType"":""struct G1Point"",""name"":""c"",""type"":""tuple""}],""internalType"":""struct Groth16Proof"",""name"":""proof"",""type"":""tuple""}],""name"":""submitProof"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""},{""inputs"":[],""name"":""token"",""outputs"":[{""internalType"":""contract IERC20"",""name"":"""",""type"":""address""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""SlotId"",""name"":""id"",""type"":""bytes32""}],""name"":""willProofBeRequired"",""outputs"":[{""internalType"":""bool"",""name"":"""",""type"":""bool""}],""stateMutability"":""view"",""type"":""function""},{""inputs"":[{""internalType"":""RequestId"",""name"":""requestId"",""type"":""bytes32""}],""name"":""withdrawFunds"",""outputs"":[],""stateMutability"":""nonpayable"",""type"":""function""}]"; + + /// + /// Naming things is hard. + /// If the storage request is created at T=0, then fetching of the + /// storage node logs will begin from T=0 minus 'LogStartBeforeStorageContractStarts'. + /// + public TimeSpan LogStartBeforeStorageContractStarts { get; } = TimeSpan.FromMinutes(1.0); + + public string ElasticSearchUrl + { + get + { + return GetEnvVar("ES_HOST", "es_host"); + } + } + + public string[] StorageNodesKubernetesPodNames = [ + "codex-1-1", + "codex-2-1", + "codex-3-1", + "codex-4-1", + "codex-5-1", + "codex-6-1", + "codex-7-1", + "codex-8-1", + "codex-9-1", + "codex-10-1", + // "codex-validator-1-1", + ]; + + public Dictionary LogReplacements = new() + { + { "0x3620ec38d88e9f0cf7feceebf97864f27676aa3e", "codex-01" }, + { "0xd80dc50af2a826f2cddc13840d05aed4ee6536c3", "codex-02" }, + { "0x2d1cd0fa0c7e0d29e7b2482b9ff87d5e7b76b905", "codex-03" }, + { "0xd47063bb6e56c9a6edb7612d33ad7d49eeb55ee0", "codex-04" }, + { "0x069da63e29b12a3828984379fcbd7dd3ee3774aa", "codex-05" }, + { "0x43fcceb2a9ce4761ccaa4c9f8d390c7581c190aa", "codex-06" }, + { "0x1a30cef06dbbf8ec25062e4e8d22e8df292f5054", "codex-07" }, + { "0xe169b5dcbae9a7392072323aaf5a677a33d67ecd", "codex-08" }, + { "0x21f7428619ef9f53addc5dab6723c822a8a96b42", "codex-09" }, + { "0xf9bd20512de2d5ca0dcfd8d3cd08a2821917797a", "codex-10" } + }; + + public string GetElasticSearchUsername() + { + return GetEnvVar("ES_USERNAME", "username"); + } + + public string GetElasticSearchPassword() + { + return GetEnvVar("ES_PASSWORD", "password"); + } + + public string GetOuputFolder() + { + return GetEnvVar("OUTPUT_FOLDER", "/output"); + } + + private string GetEnvVar(string name, string defaultValue) + { + var v = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(v)) return defaultValue; + return v; + } + } +} diff --git a/Tools/TraceContract/ElasticSearchLogDownloader.cs b/Tools/TraceContract/ElasticSearchLogDownloader.cs new file mode 100644 index 00000000..a55d85ca --- /dev/null +++ b/Tools/TraceContract/ElasticSearchLogDownloader.cs @@ -0,0 +1,253 @@ +using System.Text; +using Core; +using Logging; +using Utils; +using WebUtils; + +namespace TraceContract +{ + public class ElasticSearchLogDownloader + { + private readonly ILog log; + private readonly IPluginTools tools; + private readonly Config config; + + public ElasticSearchLogDownloader(ILog log, IPluginTools tools, Config config) + { + this.log = log; + this.tools = tools; + this.config = config; + } + + public void Download(LogFile targetFile, string podName, DateTime startUtc, DateTime endUtc) + { + try + { + DownloadLog(targetFile, podName, startUtc, endUtc); + } + catch (Exception ex) + { + log.Error("Failed to download log: " + ex); + } + } + + private void DownloadLog(LogFile targetFile, string podName, DateTime startUtc, DateTime endUtc) + { + log.Log($"Downloading log (from ElasticSearch) for pod '{podName}' within time range: " + + $"{startUtc.ToString("o")} - {endUtc.ToString("o")}"); + + var endpoint = CreateElasticSearchEndpoint(); + var queryTemplate = CreateQueryTemplate(podName, startUtc, endUtc); + + targetFile.Write($"Downloading '{podName}' to '{targetFile.Filename}'."); + var reconstructor = new LogReconstructor(targetFile, endpoint, queryTemplate); + reconstructor.DownloadFullLog(); + + log.Log("Log download finished."); + } + + private string CreateQueryTemplate(string podName, DateTime startUtc, DateTime endUtc) + { + var start = startUtc.ToString("o"); + var end = endUtc.ToString("o"); + + //container_name : codex3-5 - deploymentName as stored in pod + // pod_namespace : codex - continuous - nolimits - tests - 1 + + //var source = "{ \"sort\": [ { \"@timestamp\": { \"order\": \"asc\" } } ], \"fields\": [ { \"field\": \"@timestamp\", \"format\": \"strict_date_optional_time\" }, { \"field\": \"pod_name\" }, { \"field\": \"message\" } ], \"size\": , \"_source\": false, \"query\": { \"bool\": { \"must\": [], \"filter\": [ { \"range\": { \"@timestamp\": { \"format\": \"strict_date_optional_time\", \"gte\": \"\", \"lte\": \"\" } } }, { \"match_phrase\": { \"pod_name\": \"\" } } ] } } }"; + var source = "{ \"sort\": [ { \"@timestamp\": { \"order\": \"asc\" } } ], \"fields\": [ { \"field\": \"@timestamp\", \"format\": \"strict_date_optional_time\" }, { \"field\": \"message\" } ], \"size\": , \"_source\": false, \"query\": { \"bool\": { \"must\": [], \"filter\": [ { \"range\": { \"@timestamp\": { \"format\": \"strict_date_optional_time\", \"gte\": \"\", \"lte\": \"\" } } }, { \"match_phrase\": { \"pod_name\": \"\" } } ] } } }"; + return source + .Replace("", start) + .Replace("", end) + .Replace("", podName); + //.Replace("", config.StorageNodesKubernetesNamespace); + } + + private IEndpoint CreateElasticSearchEndpoint() + { + //var serviceName = "elasticsearch"; + //var k8sNamespace = "monitoring"; + //var address = new Address("ElasticSearchEndpoint", $"http://{serviceName}.{k8sNamespace}.svc.cluster.local", 9200); + + var address = new Address("TestnetElasticSearchEndpoint", config.ElasticSearchUrl, 443); + var baseUrl = ""; + + var username = config.GetElasticSearchUsername(); + var password = config.GetElasticSearchPassword(); + + var base64Creds = Convert.ToBase64String( + Encoding.ASCII.GetBytes($"{username}:{password}") + ); + + var http = tools.CreateHttp(address.ToString(), client => + { + client.DefaultRequestHeaders.Add("kbn-xsrf", "reporting"); + client.DefaultRequestHeaders.Add("Authorization", "Basic " + base64Creds); + }); + + return http.CreateEndpoint(address, baseUrl); + } + + public class LogReconstructor + { + private readonly List queue = new List(); + private readonly LogFile targetFile; + private readonly IEndpoint endpoint; + private readonly string queryTemplate; + private const int sizeOfPage = 2000; + private string searchAfter = ""; + private int lastHits = 1; + private ulong? lastLogLine; + + public LogReconstructor(LogFile targetFile, IEndpoint endpoint, string queryTemplate) + { + this.targetFile = targetFile; + this.endpoint = endpoint; + this.queryTemplate = queryTemplate; + } + + public void DownloadFullLog() + { + while (lastHits > 0) + { + QueryElasticSearch(); + ProcessQueue(); + } + } + + private void QueryElasticSearch() + { + var query = queryTemplate + .Replace("", sizeOfPage.ToString()) + .Replace("", searchAfter); + + var response = endpoint.HttpPostString("/_search", query); + + lastHits = response.hits.hits.Length; + if (lastHits > 0) + { + UpdateSearchAfter(response); + foreach (var hit in response.hits.hits) + { + AddHitToQueue(hit); + } + } + } + + private void AddHitToQueue(SearchHitEntry hit) + { + var message = hit.fields.message.Single(); + var number = ParseCountNumber(message); + if (number != null) + { + queue.Add(new LogQueueEntry(message, number.Value)); + } + } + + private ulong? ParseCountNumber(string message) + { + if (string.IsNullOrEmpty(message)) return null; + var tokens = message.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (!tokens.Any()) return null; + var countToken = tokens.SingleOrDefault(t => t.StartsWith("count=")); + if (countToken == null) return null; + var number = countToken.Substring(6); + if (ulong.TryParse(number, out ulong value)) + { + return value; + } + return null; + } + + private void UpdateSearchAfter(SearchResponse response) + { + var uniqueSearchNumbers = response.hits.hits.Select(h => h.sort.Single()).Distinct().ToList(); + uniqueSearchNumbers.Reverse(); + + var searchNumber = GetSearchNumber(uniqueSearchNumbers); + searchAfter = $"\"search_after\": [{searchNumber}],"; + } + + private long GetSearchNumber(List uniqueSearchNumbers) + { + if (uniqueSearchNumbers.Count == 1) return uniqueSearchNumbers.First(); + return uniqueSearchNumbers.Skip(1).First(); + } + + private void ProcessQueue() + { + if (lastLogLine == null) + { + lastLogLine = queue.Min(q => q.Number) - 1; + } + + while (queue.Any()) + { + ulong wantedNumber = lastLogLine.Value + 1; + + DeleteOldEntries(wantedNumber); + + var currentEntry = queue.FirstOrDefault(e => e.Number == wantedNumber); + + if (currentEntry != null) + { + WriteEntryToFile(currentEntry); + queue.Remove(currentEntry); + lastLogLine = currentEntry.Number; + } + else + { + // The line number we want is not in the queue. + // It will be returned by the elastic search query, some time in the future. + // Stop processing the queue for now. + return; + } + } + } + + private void WriteEntryToFile(LogQueueEntry currentEntry) + { + targetFile.Write(currentEntry.Message); + } + + private void DeleteOldEntries(ulong wantedNumber) + { + queue.RemoveAll(e => e.Number < wantedNumber); + } + + public class LogQueueEntry + { + public LogQueueEntry(string message, ulong number) + { + Message = message; + Number = number; + } + + public string Message { get; } + public ulong Number { get; } + } + + public class SearchResponse + { + public SearchHits hits { get; set; } = new SearchHits(); + } + + public class SearchHits + { + public SearchHitEntry[] hits { get; set; } = Array.Empty(); + } + + public class SearchHitEntry + { + public SearchHitFields fields { get; set; } = new SearchHitFields(); + public long[] sort { get; set; } = Array.Empty(); + } + + public class SearchHitFields + { + public string[] @timestamp { get; set; } = Array.Empty(); + public string[] message { get; set; } = Array.Empty(); + } + } + } +} diff --git a/Tools/TraceContract/Input.cs b/Tools/TraceContract/Input.cs new file mode 100644 index 00000000..a0c63a03 --- /dev/null +++ b/Tools/TraceContract/Input.cs @@ -0,0 +1,21 @@ +namespace TraceContract +{ + public class Input + { + public string PurchaseId + { + get + { + var v = Environment.GetEnvironmentVariable("PURCHASE_ID"); + if (!string.IsNullOrEmpty(v)) return v; + + return + // expired: + "a7fe97dc32216aba0cbe74b87beb3f919aa116090dd5e0d48085a1a6b0080e82"; + + // started: + //"066df09a3a2e2587cfd577a0e96186c915b113d02b331b06e56f808494cff2b4"; + } + } + } +} diff --git a/Tools/TraceContract/Output.cs b/Tools/TraceContract/Output.cs new file mode 100644 index 00000000..be89d3f6 --- /dev/null +++ b/Tools/TraceContract/Output.cs @@ -0,0 +1,127 @@ +using System.Numerics; +using CodexContractsPlugin.ChainMonitor; +using CodexContractsPlugin.Marketplace; +using Logging; +using Utils; + +namespace TraceContract +{ + public class Output + { + private class Entry + { + public Entry(DateTime utc, string msg) + { + Utc = utc; + Msg = msg; + } + + public DateTime Utc { get; } + public string Msg { get; } + } + + private readonly ILog log; + private readonly List entries = new(); + private readonly string folder; + private readonly Input input; + private readonly Config config; + + public Output(ILog log, Input input, Config config) + { + this.input = input; + this.config = config; + + folder = config.GetOuputFolder(); + Directory.CreateDirectory(folder); + + var filename = Path.Combine(folder, $"contract_{input.PurchaseId}"); + var fileLog = new FileLog(filename); + log.Log($"Logging to '{filename}'"); + + this.log = new LogSplitter(fileLog, log); + foreach (var pair in config.LogReplacements) + { + this.log.AddStringReplace(pair.Key, pair.Value); + this.log.AddStringReplace(pair.Key.ToLowerInvariant(), pair.Value); + } + } + + public void LogRequestCreated(RequestEvent requestEvent) + { + Add(requestEvent.Block.Utc, $"Storage request created: '{requestEvent.Request.Request.Id}'"); + } + + public void LogRequestCancelled(RequestEvent requestEvent) + { + Add(requestEvent.Block.Utc, "Expired"); + } + + public void LogRequestFailed(RequestEvent requestEvent) + { + Add(requestEvent.Block.Utc, "Failed"); + } + + public void LogRequestFinished(RequestEvent requestEvent) + { + Add(requestEvent.Block.Utc, "Finished"); + } + + public void LogRequestStarted(RequestEvent requestEvent) + { + Add(requestEvent.Block.Utc, "Started"); + } + + public void LogSlotFilled(RequestEvent requestEvent, EthAddress host, BigInteger slotIndex) + { + Add(requestEvent.Block.Utc, $"Slot filled. Index: {slotIndex} Host: '{host}'"); + } + + public void LogSlotFreed(RequestEvent requestEvent, BigInteger slotIndex) + { + Add(requestEvent.Block.Utc, $"Slot freed. Index: {slotIndex}"); + } + + public void LogSlotReservationsFull(RequestEvent requestEvent, BigInteger slotIndex) + { + Add(requestEvent.Block.Utc, $"Slot reservations full. Index: {slotIndex}"); + } + + public void LogReserveSlotCalls(ReserveSlotFunction[] reserveSlotFunctions) + { + foreach (var call in reserveSlotFunctions) LogReserveSlotCall(call); + } + + public void WriteContractEvents() + { + var sorted = entries.OrderBy(e => e.Utc).ToArray(); + foreach (var e in sorted) Write(e); + } + + public LogFile CreateNodeLogTargetFile(string node) + { + return log.CreateSubfile(node); + } + + public void ShowOutputFiles(ILog console) + { + console.Log("Files in output folder:"); + var files = Directory.GetFiles(folder); + foreach (var file in files) console.Log(file); + } + + private void Write(Entry e) + { + log.Log($"[{Time.FormatTimestamp(e.Utc)}] {e.Msg}"); + } + + public void LogReserveSlotCall(ReserveSlotFunction call) + { + Add(call.Block.Utc, $"Reserve-slot called. Index: {call.SlotIndex} Host: '{call.FromAddress}'"); + } + + private void Add(DateTime utc, string msg) + { + entries.Add(new Entry(utc, msg)); + } + } +} diff --git a/Tools/TraceContract/Program.cs b/Tools/TraceContract/Program.cs new file mode 100644 index 00000000..50ade56d --- /dev/null +++ b/Tools/TraceContract/Program.cs @@ -0,0 +1,98 @@ +using BlockchainUtils; +using CodexContractsPlugin; +using CodexContractsPlugin.Marketplace; +using Core; +using GethPlugin; +using Logging; +using Utils; + +namespace TraceContract +{ + public class Program + { + public static void Main(string[] args) + { + ProjectPlugin.Load(); + ProjectPlugin.Load(); + + var p = new Program(); + p.Run(); + } + + private readonly ILog log = new ConsoleLog(); + private readonly Input input = new(); + private readonly Config config = new(); + private readonly Output output; + + public Program() + { + output = new(log, input, config); + } + + private void Run() + { + try + { + TracePurchase(); + } + catch (Exception exc) + { + log.Error(exc.ToString()); + } + } + + private void TracePurchase() + { + Log("Setting up..."); + var entryPoint = new EntryPoint(log, new KubernetesWorkflow.Configuration(null, TimeSpan.FromMinutes(1.0), TimeSpan.FromSeconds(10.0), "_Unused!_"), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); + entryPoint.Announce(); + var ci = entryPoint.CreateInterface(); + var contracts = ConnectCodexContracts(ci); + + var chainTracer = new ChainTracer(log, contracts, input, output); + var requestTimeRange = chainTracer.TraceChainTimeline(); + + Log("Downloading storage nodes logs for the request timerange..."); + DownloadStorageNodeLogs(requestTimeRange, entryPoint.Tools); + + output.ShowOutputFiles(log); + + entryPoint.Decommission(false, false, false); + Log("Done"); + } + + private ICodexContracts ConnectCodexContracts(CoreInterface ci) + { + var account = EthAccountGenerator.GenerateNew(); + var blockCache = new BlockCache(); + var geth = new CustomGethNode(log, blockCache, config.RpcEndpoint, config.GethPort, account.PrivateKey); + + var deployment = new CodexContractsDeployment( + config: new MarketplaceConfig(), + marketplaceAddress: config.MarketplaceAddress, + abi: config.Abi, + tokenAddress: config.TokenAddress + ); + return ci.WrapCodexContractsDeployment(geth, deployment); + } + + private void DownloadStorageNodeLogs(TimeRange requestTimeRange, IPluginTools tools) + { + var start = requestTimeRange.From - config.LogStartBeforeStorageContractStarts; + + foreach (var node in config.StorageNodesKubernetesPodNames) + { + Log($"Downloading logs from '{node}'..."); + + var targetFile = output.CreateNodeLogTargetFile(node); + var downloader = new ElasticSearchLogDownloader(log, tools, config); + downloader.Download(targetFile, node, start, requestTimeRange.To); + } + } + + private void Log(string msg) + { + log.Log(msg); + } + } +} diff --git a/Tools/TraceContract/TraceContract.csproj b/Tools/TraceContract/TraceContract.csproj new file mode 100644 index 00000000..51fd39c6 --- /dev/null +++ b/Tools/TraceContract/TraceContract.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index 9b1c0977..a10e32d6 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -86,6 +86,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodexClient", "ProjectPlugi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUtils", "Framework\WebUtils\WebUtils.csproj", "{372C9E5D-5453-4D45-9948-E9324E21AD65}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TraceContract", "Tools\TraceContract\TraceContract.csproj", "{58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -232,6 +234,10 @@ Global {372C9E5D-5453-4D45-9948-E9324E21AD65}.Debug|Any CPU.Build.0 = Debug|Any CPU {372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.ActiveCfg = Release|Any CPU {372C9E5D-5453-4D45-9948-E9324E21AD65}.Release|Any CPU.Build.0 = Release|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -272,6 +278,7 @@ Global {4648B5AA-A0A7-44BA-89BC-2FD57370943C} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} {9AF12703-29AF-416D-9781-204223D6D0E5} = {8F1F1C2A-E313-4E0C-BE40-58FB0BA91124} {372C9E5D-5453-4D45-9948-E9324E21AD65} = {81AE04BC-CBFA-4E6F-B039-8208E9AFAAE7} + {58CDACE0-8F8D-2BB7-EA3A-0CB6A994A7F8} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C} diff --git a/docs/TraceContract_HowTo.png b/docs/TraceContract_HowTo.png new file mode 100644 index 00000000..eaed80f0 Binary files /dev/null and b/docs/TraceContract_HowTo.png differ