Compare commits

..
Author SHA1 Message Date
Ben 8b24de94ab wip tracking download fails 2024-10-29 15:32:43 +01:00
Ben a1833c52cc Merge branch 'master' into blockexc/experiments
# Conflicts:
#	ProjectPlugins/CodexPlugin/CodexContainerRecipe.cs
2024-10-29 14:02:32 +01:00
Ben 922e2dad52 results of different images 2024-10-25 12:39:51 +02:00
140 changed files with 677 additions and 3541 deletions
+2 -2
View File
@@ -80,7 +80,7 @@ env:
TESTS_TARGET_DURATION: 2d
TESTS_FILTER: ""
TESTS_CLEANUP: true
JOB_MANIFEST: docker/job-continuous-tests.yaml
JOB_MANIFEST: docker/continuous-tests-job.yaml
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
KUBE_VERSION: v1.28.2
@@ -146,7 +146,7 @@ jobs:
if: false
run: |
# Variables
# We need more than 300 seconds because Auto Scaler may take 3 minutes to run a node
# We need more than 300 seconds because Auto Scaler may take 3 minutes to tun a node
duration=600
namespace="${{ env.NAMESPACE }}"
pod=$(kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -o jsonpath="{.items[0].metadata.name}")
+1 -1
View File
@@ -32,7 +32,7 @@ env:
NAMEPREFIX: d-tests-runner
NAMESPACE: default
COMMAND: dotnet test Tests/CodexTests
JOB_MANIFEST: docker/job-dist-tests.yaml
JOB_MANIFEST: docker/dist-tests-job.yaml
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
KUBE_VERSION: v1.28.2
-122
View File
@@ -1,122 +0,0 @@
name: Run Release Tests
on:
workflow_dispatch:
inputs:
codexdockerimage:
description: "Codex Docker image (example: 'codexstorage/nim-codex:0.1.8-dist-tests')"
required: true
type: string
workflow_call:
inputs:
source:
description: Repository with tests (current)
required: false
type: string
branch:
description: Branch with tests (master)
required: false
type: string
codexdockerimage:
description: "Codex Docker image (example: 'codexstorage/nim-codex:0.1.8-dist-tests')"
required: true
type: string
workflow_source:
description: Workflow source
required: false
type: string
default: ''
env:
SOURCE: ${{ format('{0}/{1}', github.server_url, github.repository) }}
BRANCH: ${{ github.ref_name }}
CODEXDOCKERIMAGE: codexstorage/nim-codex:latest-dist-tests
TEST_TYPE: release-tests
NAMEPREFIX: r-tests
NAMESPACE: default
JOB_MANIFEST: docker/job-release-tests.yaml
COMMAND: dotnet test Tests/CodexReleaseTests
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
KUBE_VERSION: v1.30.5
jobs:
run_tests:
name: Run Release Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
repository: ${{ inputs.workflow_source }}
- name: Variables
run: |
RUNID=$(date +%Y%m%d-%H%M%S)
echo "RUNID=${RUNID}" >> $GITHUB_ENV
echo "TESTID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
[[ -n "${{ inputs.source }}" ]] && echo "SOURCE=${{ inputs.source }}" >>"$GITHUB_ENV" || echo "SOURCE=${{ env.SOURCE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.branch }}" ]] && echo "BRANCH=${{ inputs.branch }}" >>"$GITHUB_ENV" || echo "BRANCH=${{ env.BRANCH }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.codexdockerimage }}" ]] && echo "CODEXDOCKERIMAGE=${{ inputs.codexdockerimage }}" >>"$GITHUB_ENV" || echo "CODEXDOCKERIMAGE=${{ env.CODEXDOCKERIMAGE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.nameprefix }}" ]] && NAMEPREFIX="`awk '{ print tolower($0) }' <<< ${{ inputs.nameprefix }}`" || NAMEPREFIX="`awk '{ print tolower($0) }' <<< ${{ env.NAMEPREFIX }}`"
echo "NAMEPREFIX=${NAMEPREFIX}-${RUNID}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.namespace }}" ]] && echo "NAMESPACE=${{ inputs.namespace }}" >>"$GITHUB_ENV" || echo "NAMESPACE=${{ env.NAMESPACE }}" >>"$GITHUB_ENV"
[[ -n "${{ inputs.command }}" ]] && COMMAND="${{ inputs.command }}" || COMMAND="${{ env.COMMAND }}"
echo "COMMAND=$(jq -c 'split(" ")' <<< '"'${COMMAND}'"')" >>"$GITHUB_ENV"
- name: Kubectl - Install ${{ env.KUBE_VERSION }}
uses: azure/setup-kubectl@v4
with:
version: ${{ env.KUBE_VERSION }}
- name: Kubectl - Kubeconfig
run: |
mkdir -p "${HOME}"/.kube
echo "${{ env.KUBE_CONFIG }}" | base64 -d > "${HOME}"/.kube/config
- name: Kubectl - Create Job to run tests
run: |
envsubst < ${{ env.JOB_MANIFEST }} | kubectl apply -f -
- name: Tests Identification
run: |
echo "----"
echo "Repository: ${{ env.SOURCE }}"
echo "Branch: ${{ env.BRANCH }}"
echo "Runner job: ${{ env.NAMEPREFIX }}"
echo "Runner pod: $(kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -ojsonpath='{.items[0].metadata.name}')"
echo "Runner namespace: ${{ env.NAMESPACE }}"
echo "----"
- name: Show Runner logs
run: |
# Variables
# We need more than 300 seconds because Auto Scaler may take 3 minutes to tun a node
namespace="${{ env.NAMESPACE }}"
pod=$(kubectl get pod --selector job-name=${{ env.NAMEPREFIX }} -o jsonpath='{.items[0].metadata.name}')
# Check Pod status
WAIT=120
SECONDS=0
sleep=1
while (( SECONDS < WAIT )); do
phase=$(kubectl get pod ${pod} -n ${namespace} -o jsonpath='{.status.phase}')
[[ "${phase}" == "Running" ]] && { echo "Pod $pod is in the $phase state - Get the logs"; break; } || { echo "Pod $pod is in the $phase state - Retry in $sleep second(s) / $((WAIT - SECONDS))"; }
sleep $sleep
done
# Get logs
while [[ $(kubectl get pod ${pod} -n ${namespace} -o jsonpath='{.status.phase}') == "Running" ]]; do
echo "Show ${pod} logs ..."
echo "----"
kubectl logs $pod -n $namespace -f || true
sleep 1
done
- name: Set workflow status from job status
run: |
sleep 5
job_status=$(kubectl get jobs ${{ env.NAMEPREFIX }} -n ${{ env.NAMESPACE }} -o jsonpath='{.status.conditions[0].type}')
echo "Job status: $job_status"
if [[ "${job_status}" != "Complete" ]]; then exit 1; fi
@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nethereum.Web3" Version="4.14.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Logging\Logging.csproj" />
</ItemGroup>
</Project>
+2 -15
View File
@@ -1,6 +1,4 @@
using BlockchainUtils;
using CodexContractsPlugin;
using CodexContractsPlugin.Marketplace;
using CodexContractsPlugin;
using GethPlugin;
using Logging;
@@ -20,29 +18,18 @@ namespace GethConnector
return null;
}
var gethNode = new CustomGethNode(log, new BlockCache(), GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey);
var config = GetCodexMarketplaceConfig(gethNode, GethInput.MarketplaceAddress);
var contractsDeployment = new CodexContractsDeployment(
config: config,
marketplaceAddress: GethInput.MarketplaceAddress,
abi: GethInput.ABI,
tokenAddress: GethInput.TokenAddress
);
var gethNode = new CustomGethNode(log, GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey);
var contracts = new CodexContractsAccess(log, gethNode, contractsDeployment);
return new GethConnector(gethNode, contracts);
}
private static MarketplaceConfig GetCodexMarketplaceConfig(IGethNode gethNode, string marketplaceAddress)
{
var func = new ConfigurationFunctionBase();
var response = gethNode.Call<ConfigurationFunctionBase, ConfigurationOutputDTO>(marketplaceAddress, func);
return response.ReturnValue1;
}
private GethConnector(IGethNode gethNode, ICodexContracts codexContracts)
{
GethNode = gethNode;
+1 -1
View File
@@ -91,7 +91,7 @@ namespace KubernetesWorkflow
private void DownloadCrashedContainerLogs(Kubernetes client)
{
using var stream = client.ReadNamespacedPodLog(podName, k8sNamespace, recipeName, previous: true);
var handler = new WriteToFileLogHandler(log, "Crash detected for " + containerName, containerName);
var handler = new WriteToFileLogHandler(log, "Crash detected for " + containerName);
handler.Log(stream);
}
}
+32 -14
View File
@@ -6,7 +6,6 @@ namespace KubernetesWorkflow
{
string ContainerName { get; }
void IterateLines(Action<string> action);
void IterateLines(Action<string> action, params string[] thatContain);
string[] GetLinesContaining(string expectedString);
string[] FindLinesThatContain(params string[] tags);
@@ -26,39 +25,58 @@ namespace KubernetesWorkflow
public string ContainerName { get; }
public void IterateLines(Action<string> action)
public void IterateLines(Action<string> action, params string[] thatContain)
{
using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file);
var line = streamReader.ReadLine();
while (line != null)
{
action(line);
line = streamReader.ReadLine();
}
}
public void IterateLines(Action<string> action, params string[] thatContain)
{
IterateLines(line =>
{
if (thatContain.All(line.Contains))
{
action(line);
}
});
line = streamReader.ReadLine();
}
}
public string[] GetLinesContaining(string expectedString)
{
return FindLinesThatContain([expectedString]);
using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file);
var lines = new List<string>();
var line = streamReader.ReadLine();
while (line != null)
{
if (line.Contains(expectedString))
{
lines.Add(line);
}
line = streamReader.ReadLine();
}
return lines.ToArray(); ;
}
public string[] FindLinesThatContain(params string[] tags)
{
var result = new List<string>();
IterateLines(result.Add, tags);
using var file = File.OpenRead(logFile.FullFilename);
using var streamReader = new StreamReader(file);
var line = streamReader.ReadLine();
while (line != null)
{
if (tags.All(line.Contains))
{
result.Add(line);
}
line = streamReader.ReadLine();
}
return result.ToArray();
}
@@ -906,7 +906,7 @@ namespace KubernetesWorkflow
var msg = $"Pod crash detected for deployment {deploymentName} (pod:{podName})";
log.Error(msg);
DownloadPodLog(container, new WriteToFileLogHandler(log, msg, deploymentName), tailLines: null, previous: true);
DownloadPodLog(container, new WriteToFileLogHandler(log, msg), tailLines: null, previous: true);
throw new Exception(msg);
}
+8 -2
View File
@@ -1,4 +1,5 @@
using Logging;
using Utils;
namespace KubernetesWorkflow
{
@@ -25,9 +26,9 @@ namespace KubernetesWorkflow
public class WriteToFileLogHandler : LogHandler, ILogHandler
{
public WriteToFileLogHandler(ILog sourceLog, string description, string addFileName)
public WriteToFileLogHandler(ILog sourceLog, string description)
{
LogFile = sourceLog.CreateSubfile(addFileName);
LogFile = sourceLog.CreateSubfile();
var msg = $"{description} -->> {LogFile.FullFilename}";
sourceLog.Log(msg);
@@ -40,6 +41,11 @@ namespace KubernetesWorkflow
protected override void ProcessLine(string line)
{
foreach (var replacement in BaseLog.replacements)
{
line = replacement.Apply(line);
}
LogFile.WriteRaw(line);
}
}
@@ -127,7 +127,7 @@ namespace KubernetesWorkflow
{
var msg = $"Downloading container log for '{container.Name}'";
log.Log(msg);
var logHandler = new WriteToFileLogHandler(log, msg, container.Name);
var logHandler = new WriteToFileLogHandler(log, msg);
K8s(controller =>
{
+4 -8
View File
@@ -8,7 +8,7 @@ namespace Logging
void Debug(string message = "", int skipFrames = 0);
void Error(string message);
void AddStringReplace(string from, string to);
LogFile CreateSubfile(string addName, string ext = "log");
LogFile CreateSubfile(string ext = "log");
}
public abstract class BaseLog : ILog
@@ -16,7 +16,7 @@ namespace Logging
public static bool EnableDebugLogging { get; set; } = false;
private readonly NumberSource subfileNumberSource = new NumberSource(0);
private readonly List<BaseLogStringReplacement> replacements = new List<BaseLogStringReplacement>();
public static List<BaseLogStringReplacement> replacements = new List<BaseLogStringReplacement>();
private LogFile? logFile;
public BaseLog()
@@ -72,13 +72,9 @@ namespace Logging
File.Delete(LogFile.FullFilename);
}
public LogFile CreateSubfile(string addName, string ext = "log")
public LogFile CreateSubfile(string ext = "log")
{
addName = addName
.Replace("<", "")
.Replace(">", "");
return new LogFile($"{GetFullName()}_{GetSubfileNumber()}_{addName}", ext);
return new LogFile($"{GetFullName()}_{GetSubfileNumber()}", ext);
}
protected string ApplyReplacements(string str)
+2 -4
View File
@@ -1,6 +1,4 @@
using Utils;
namespace Logging
namespace Logging
{
public class LogFile
{
@@ -51,7 +49,7 @@ namespace Logging
private static string GetTimestamp()
{
return $"[{Time.FormatTimestamp(DateTime.UtcNow)}]";
return $"[{DateTime.UtcNow.ToString("o")}]";
}
private void EnsurePathExists(string filename)
+2 -2
View File
@@ -18,9 +18,9 @@
public string Prefix { get; set; } = string.Empty;
public LogFile CreateSubfile(string addName, string ext = "log")
public LogFile CreateSubfile(string ext = "log")
{
return backingLog.CreateSubfile(addName, ext);
return backingLog.CreateSubfile(ext);
}
public void Debug(string message = "", int skipFrames = 0)
+2 -2
View File
@@ -14,9 +14,9 @@
OnAll(l => l.AddStringReplace(from, to));
}
public LogFile CreateSubfile(string addName, string ext = "log")
public LogFile CreateSubfile(string ext = "log")
{
return targetLogs.First().CreateSubfile(addName, ext);
return targetLogs.First().CreateSubfile(ext);
}
public void Debug(string message = "", int skipFrames = 0)
@@ -1,4 +1,4 @@
namespace BlockchainUtils
namespace NethereumWorkflow.BlockUtils
{
public class BlockCache
{
@@ -1,4 +1,4 @@
namespace BlockchainUtils
namespace NethereumWorkflow.BlockUtils
{
public class BlockTimeEntry
{
@@ -1,6 +1,6 @@
using Logging;
namespace BlockchainUtils
namespace NethereumWorkflow.BlockUtils
{
public class BlockTimeFinder
{
@@ -1,11 +1,5 @@
namespace BlockchainUtils
namespace NethereumWorkflow.BlockUtils
{
public interface IWeb3Blocks
{
ulong GetCurrentBlockNumber();
DateTime? GetTimestampForBlock(ulong blockNumber);
}
public class BlockchainBounds
{
private readonly BlockCache cache;
@@ -1,7 +1,7 @@
using Nethereum.Hex.HexTypes;
using System.Numerics;
namespace BlockchainUtils
namespace NethereumWorkflow
{
public static class ConversionExtensions
{
@@ -1,25 +1,25 @@
using BlockchainUtils;
using Logging;
using Logging;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Contracts;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Web3;
using NethereumWorkflow.BlockUtils;
using Utils;
namespace NethereumWorkflow
{
public class NethereumInteraction
{
private readonly BlockCache blockCache;
// BlockCache is a static instance: It stays alive for the duration of the application runtime.
private readonly static BlockCache blockCache = new BlockCache();
private readonly ILog log;
private readonly Web3 web3;
internal NethereumInteraction(ILog log, Web3 web3, BlockCache blockCache)
internal NethereumInteraction(ILog log, Web3 web3)
{
this.log = log;
this.web3 = web3;
this.blockCache = blockCache;
}
public string SendEth(string toAddress, decimal ethAmount)
@@ -1,5 +1,4 @@
using BlockchainUtils;
using Logging;
using Logging;
using Nethereum.Web3;
namespace NethereumWorkflow
@@ -7,15 +6,13 @@ namespace NethereumWorkflow
public class NethereumInteractionCreator
{
private readonly ILog log;
private readonly BlockCache blockCache;
private readonly string ip;
private readonly int port;
private readonly string privateKey;
public NethereumInteractionCreator(ILog log, BlockCache blockCache, string ip, int port, string privateKey)
public NethereumInteractionCreator(ILog log, string ip, int port, string privateKey)
{
this.log = log;
this.blockCache = blockCache;
this.ip = ip;
this.port = port;
this.privateKey = privateKey;
@@ -24,7 +21,7 @@ namespace NethereumWorkflow
public NethereumInteraction CreateWorkflow()
{
log.Debug("Starting interaction to " + ip + ":" + port);
return new NethereumInteraction(log, CreateWeb3(), blockCache);
return new NethereumInteraction(log, CreateWeb3());
}
private Web3 CreateWeb3()
@@ -12,7 +12,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlockchainUtils\BlockchainUtils.csproj" />
<ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
+7 -2
View File
@@ -1,11 +1,16 @@
using BlockchainUtils;
using Logging;
using Logging;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Web3;
using Utils;
namespace NethereumWorkflow
{
public interface IWeb3Blocks
{
ulong GetCurrentBlockNumber();
DateTime? GetTimestampForBlock(ulong blockNumber);
}
public class Web3Wrapper : IWeb3Blocks
{
private readonly Web3 web3;
+1 -2
View File
@@ -15,13 +15,12 @@
To = from;
}
TimeRange = timeRange;
NumberOfBlocks = (To - From) + 1;
}
public ulong From { get; }
public ulong To { get; }
public TimeRange TimeRange { get; }
public ulong NumberOfBlocks { get; }
public ulong NumberOfBlocks => To - From;
public override string ToString()
{
+1 -1
View File
@@ -98,7 +98,7 @@
private void Fail()
{
throw new TimeoutException($"Retry '{description}' timed out after {tryNumber} tries over {Time.FormatDuration(Duration())}: {GetFailureReport()}",
throw new TimeoutException($"Retry '{description}' timed out after {tryNumber} tries over {Time.FormatDuration(Duration())}: {GetFailureReport}",
new AggregateException(failures.Select(f => f.Exception)));
}
-5
View File
@@ -33,11 +33,6 @@
result += $"{d.Seconds} secs";
return result;
}
public static string FormatTimestamp(DateTime d)
{
return d.ToString("o");
}
public static TimeSpan ParseTimespan(string span)
{
@@ -1,7 +1,7 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using NethereumWorkflow.BlockUtils;
using System.Numerics;
using Utils;
@@ -75,12 +75,11 @@ namespace CodexContractsPlugin.ChainMonitor
throw new Exception(msg);
}
log.Log($"ChainState updating: {events.BlockInterval} = {events.All.Length} events.");
log.Log($"ChainState updating: {events.BlockInterval}");
// Run through each block and apply the events to the state in order.
var span = events.BlockInterval.TimeRange.Duration;
var numBlocks = events.BlockInterval.NumberOfBlocks;
if (numBlocks == 0) return;
var spanPerBlock = span / numBlocks;
var eventUtc = events.BlockInterval.TimeRange.From;
@@ -1,9 +1,7 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.ABI;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Util;
using NethereumWorkflow;
using Newtonsoft.Json;
@@ -26,7 +24,6 @@ namespace CodexContractsPlugin
ICodexContractsEvents GetEvents(BlockInterval blockInterval);
EthAddress? GetSlotHost(Request storageRequest, decimal slotIndex);
RequestState GetRequestState(Request request);
void WaitUntilNextPeriod();
}
[JsonConverter(typeof(StringEnumConverter))]
@@ -117,15 +114,6 @@ namespace CodexContractsPlugin
return gethNode.Call<RequestStateFunction, RequestState>(Deployment.MarketplaceAddress, func);
}
public void WaitUntilNextPeriod()
{
log.Log("Waiting until next proof period...");
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var periodSeconds = (int)Deployment.Config.Proofs.Period;
var secondsLeft = now % periodSeconds;
Thread.Sleep(TimeSpan.FromSeconds(secondsLeft + 1));
}
private ContractInteractions StartInteraction()
{
return new ContractInteractions(log, gethNode);
@@ -1,18 +1,14 @@
using CodexContractsPlugin.Marketplace;
namespace CodexContractsPlugin
namespace CodexContractsPlugin
{
public class CodexContractsDeployment
{
public CodexContractsDeployment(MarketplaceConfig config, string marketplaceAddress, string abi, string tokenAddress)
public CodexContractsDeployment(string marketplaceAddress, string abi, string tokenAddress)
{
Config = config;
MarketplaceAddress = marketplaceAddress;
Abi = abi;
TokenAddress = tokenAddress;
}
public MarketplaceConfig Config { get; }
public string MarketplaceAddress { get; }
public string Abi { get; }
public string TokenAddress { get; }
@@ -1,9 +1,9 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.Contracts;
using Nethereum.Hex.HexTypes;
using NethereumWorkflow.BlockUtils;
using Utils;
namespace CodexContractsPlugin
@@ -4,7 +4,6 @@ using GethPlugin;
using KubernetesWorkflow;
using KubernetesWorkflow.Types;
using Logging;
using Newtonsoft.Json;
using Utils;
namespace CodexContractsPlugin
@@ -35,7 +34,6 @@ namespace CodexContractsPlugin
try
{
var result = DeployContract(container, workflow, gethNode);
workflow.Stop(containers, waitTillStopped: false);
Log("Container stopped.");
return result;
@@ -77,20 +75,9 @@ namespace CodexContractsPlugin
Time.WaitUntil(() => interaction.IsSynced(marketplaceAddress, abi), nameof(DeployContract));
Log("Synced. Codex SmartContracts deployed. Getting configuration...");
Log("Synced. Codex SmartContracts deployed.");
var config = GetMarketplaceConfiguration(marketplaceAddress, gethNode);
Log("Got config: " + JsonConvert.SerializeObject(config));
return new CodexContractsDeployment(config, marketplaceAddress, abi, tokenAddress);
}
private MarketplaceConfig GetMarketplaceConfiguration(string marketplaceAddress, IGethNode gethNode)
{
var func = new ConfigurationFunctionBase();
var response = gethNode.Call<ConfigurationFunctionBase, ConfigurationOutputDTO>(marketplaceAddress, func);
return response.ReturnValue1;
return new CodexContractsDeployment(marketplaceAddress, abi, tokenAddress);
}
private void EnsureCompatbility(string abi, string bytecode)
@@ -1,5 +1,4 @@
using BlockchainUtils;
using CodexContractsPlugin.Marketplace;
using CodexContractsPlugin.Marketplace;
using GethPlugin;
using Logging;
using Nethereum.ABI.FunctionEncoding.Attributes;
@@ -24,8 +23,9 @@ namespace CodexContractsPlugin
public string GetTokenAddress(string marketplaceAddress)
{
log.Debug(marketplaceAddress);
var function = new TokenFunctionBase();
return gethNode.Call<TokenFunctionBase, string>(marketplaceAddress, function);
var function = new GetTokenFunction();
return gethNode.Call<GetTokenFunction, string>(marketplaceAddress, function);
}
public string GetTokenName(string tokenAddress)
@@ -111,6 +111,11 @@ namespace CodexContractsPlugin
}
}
[Function("token", "address")]
public class GetTokenFunction : FunctionMessage
{
}
[Function("name", "string")]
public class GetTokenNameFunction : FunctionMessage
{
@@ -1,6 +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 GethPlugin;
using NethereumWorkflow.BlockUtils;
using Newtonsoft.Json;
namespace CodexContractsPlugin.Marketplace
File diff suppressed because one or more lines are too long
@@ -46,16 +46,6 @@ namespace CodexContractsPlugin
return new TestToken(a.TstWei + b.TstWei);
}
public static TestToken operator -(TestToken a, TestToken b)
{
return new TestToken(a.TstWei - b.TstWei);
}
public static TestToken operator *(TestToken a, int b)
{
return new TestToken(a.TstWei * b);
}
public static bool operator <(TestToken a, TestToken b)
{
return a.TstWei < b.TstWei;
+1 -1
View File
@@ -10,7 +10,7 @@ namespace CodexPlugin
public class ApiChecker
{
// <INSERT-OPENAPI-YAML-HASH>
private const string OpenApiYamlHash = "34-B5-DA-26-40-76-B8-D8-8E-7D-9C-17-85-C6-B0-63-55-8D-C6-01-0B-96-BB-7C-BD-53-E5-32-07-ED-29-92";
private const string OpenApiYamlHash = "39-0C-32-A3-EA-90-4F-29-1C-67-12-F1-D5-BE-31-67-8D-90-43-1E-F2-02-63-5B-0C-49-F7-1E-E5-EC-F7-00";
private const string OpenApiFilePath = "/codex/openapi.yaml";
private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK";
+3 -65
View File
@@ -32,22 +32,6 @@ namespace CodexPlugin
return mapper.Map(OnCodex(api => api.GetDebugInfoAsync()));
}
public string GetSpr()
{
return CrashCheck(() =>
{
var endpoint = GetEndpoint();
var json = endpoint.HttpGetString("spr");
var response = JsonConvert.DeserializeObject<SprResponse>(json);
return response!.Spr;
});
}
private class SprResponse
{
public string Spr { get; set; } = string.Empty;
}
public DebugPeer GetDebugPeer(string peerId)
{
// Cannot use openAPI: debug/peer endpoint is not specified there.
@@ -79,10 +63,10 @@ namespace CodexPlugin
});
}
public string UploadFile(UploadInput uploadInput, Action<Failure> onFailure)
public string UploadFile(FileStream fileStream, Action<Failure> onFailure)
{
return OnCodex(
api => api.UploadAsync(uploadInput.ContentType, uploadInput.ContentDisposition, uploadInput.FileStream),
api => api.UploadAsync(fileStream),
CreateRetryConfig(nameof(UploadFile), onFailure));
}
@@ -96,34 +80,9 @@ namespace CodexPlugin
return fileResponse.Stream;
}
public LocalDataset DownloadStreamless(ContentId cid)
{
var response = OnCodex(api => api.DownloadNetworkAsync(cid.Id));
return mapper.Map(response);
}
public LocalDataset DownloadManifestOnly(ContentId cid)
{
var response = OnCodex(api => api.DownloadNetworkManifestAsync(cid.Id));
return mapper.Map(response);
}
public LocalDatasetList LocalFiles()
{
// API for listData mismatches.
//return mapper.Map(OnCodex(api => api.ListDataAsync()));
return mapper.Map(CrashCheck(() =>
{
var endpoint = GetEndpoint();
return Time.Retry(() =>
{
var str = endpoint.HttpGetString("data");
if (string.IsNullOrEmpty(str)) throw new Exception("Empty response.");
return JsonConvert.DeserializeObject<LocalDatasetListJson>(str)!;
}, nameof(LocalFiles));
}));
return mapper.Map(OnCodex(api => api.ListDataAsync("", "")));
}
public StorageAvailability SalesAvailability(StorageAvailability request)
@@ -234,15 +193,8 @@ namespace CodexPlugin
.CreateEndpoint(GetAddress(), "/api/codex/v1/", Container.Name);
}
public static Address? UploaderOverride { get; set; } = null;
public static Address? DownloaderOverride { get; set; } = null;
private Address GetAddress()
{
if (GetName().ToLowerInvariant().Contains("upload") && UploaderOverride != null) return UploaderOverride;
if (GetName().ToLowerInvariant().Contains("download") && DownloaderOverride != null) return DownloaderOverride;
return Container.Containers.Single().GetAddress(CodexContainerRecipe.ApiPortTag);
}
@@ -309,18 +261,4 @@ namespace CodexPlugin
log.Log($"{GetName()} {msg}");
}
}
public class UploadInput
{
public UploadInput(string contentType, string contentDisposition, FileStream fileStream)
{
ContentType = contentType;
ContentDisposition = contentDisposition;
FileStream = fileStream;
}
public string ContentType { get; }
public string ContentDisposition { get; }
public FileStream FileStream { get; }
}
}
@@ -7,7 +7,20 @@ namespace CodexPlugin
{
public class CodexContainerRecipe : ContainerRecipeFactory
{
private const string DefaultDockerImage = "codexstorage/nim-codex:latest-dist-tests";
private const string DefaultDockerImage =
//"codexstorage/nim-codex:0.1.7-dist-tests"; // => 20/20: 17 seconds 10/10: 3 seconds
//"codexstorage/nim-codex:sha-2a25460-dist-tests"; // PR => 20/20: 17 seconds
//"thatbenbierens/nim-codex:blockexcpr1"; // PR with revert of "Fixes issue where only wants of type block are stored in peerContext" => 20/20: 17 seconds
//"thatbenbierens/nim-codex:blockexprecreate"; // v0.1.7 with patch => 20/20: 19 seconds
//"thatbenbierens/nim-codex:blockexprecreate016"; // v0.1.6 with patch => 20/20: 19 seconds 10/10: 2 seconds
//"thatbenbierens/nim-codex:blockexchprtinker7";
//"thatbenbierens/nim-codex:blkexc9"; // wow-fast
"thatbenbierens/nim-codex:asyncprofile5break"; // asynced trees.
//blocks are stored, blocks are resolved
//store-stream does not continue. node too busy???
public const string ApiPortTag = "codex_api_port";
public const string ListenPortTag = "codex_listen_port";
public const string MetricsPortTag = "codex_metrics_port";
@@ -109,7 +122,7 @@ namespace CodexPlugin
// Custom scripting in the Codex test image will write this variable to a private-key file,
// and pass the correct filename to Codex.
var account = marketplaceSetup.EthAccountSetup.GetNew();
AddEnvVar("ETH_PRIVATE_KEY", account.PrivateKey);
AddEnvVar("PRIV_KEY", account.PrivateKey);
Additional(account);
SetCommandOverride(marketplaceSetup);
@@ -48,11 +48,6 @@ namespace CodexPlugin
public string Message { get; set; } = string.Empty;
public Dictionary<string, string> Attributes { get; private set; } = new Dictionary<string, string>();
public override string ToString()
{
return Message;
}
/// <summary>
/// After too much time spent cursing at regexes, here's what I got:
/// Parses input string into 'key=value' pair, considerate of quoted (") values.
+8 -103
View File
@@ -14,23 +14,12 @@ namespace CodexPlugin
{
string GetName();
string GetPeerId();
DebugInfo GetDebugInfo(bool log = false);
string GetSpr();
DebugInfo GetDebugInfo();
DebugPeer GetDebugPeer(string peerId);
ContentId UploadFile(TrackedFile file);
ContentId UploadFile(TrackedFile file, Action<Failure> onFailure);
ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action<Failure> onFailure);
TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "");
TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "");
(TrackedFile?, TimeSpan) DownloadContentT(ContentId contentId, string fileLabel = "");
(TrackedFile?, TimeSpan) DownloadContentT(ContentId contentId, Action<Failure> onFailure, string fileLabel = "");
LocalDataset DownloadStreamless(ContentId cid);
/// <summary>
/// TODO: This will monitor the quota-used of the node until 'size' bytes are added. That's a very bad way
/// to track the streamless download progress. Replace it once we have a good API for this.
/// </summary>
LocalDataset DownloadStreamlessWait(ContentId cid, ByteSize size);
LocalDataset DownloadManifestOnly(ContentId cid);
LocalDatasetList LocalFiles();
CodexSpace Space();
void ConnectToPeer(ICodexNode node);
@@ -130,22 +119,14 @@ namespace CodexPlugin
return peerId;
}
public DebugInfo GetDebugInfo(bool log = false)
public DebugInfo GetDebugInfo()
{
var debugInfo = CodexAccess.GetDebugInfo();
if (log)
{
var known = string.Join(",", debugInfo.Table.Nodes.Select(n => n.PeerId));
Log($"Got DebugInfo with id: {debugInfo.Id}. This node knows: [{known}]");
}
var known = string.Join(",", debugInfo.Table.Nodes.Select(n => n.PeerId));
Log($"Got DebugInfo with id: {debugInfo.Id}. This node knows: [{known}]");
return debugInfo;
}
public string GetSpr()
{
return CodexAccess.GetSpr();
}
public DebugPeer GetDebugPeer(string peerId)
{
return CodexAccess.GetDebugPeer(peerId);
@@ -157,11 +138,6 @@ namespace CodexPlugin
}
public ContentId UploadFile(TrackedFile file, Action<Failure> onFailure)
{
return UploadFile(file, "application/octet-stream", $"attachment; filename=\"{Path.GetFileName(file.Filename)}\"", onFailure);
}
public ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action<Failure> onFailure)
{
using var fileStream = File.OpenRead(file.Filename);
var uniqueId = Guid.NewGuid().ToString();
@@ -169,11 +145,10 @@ namespace CodexPlugin
hooks.OnFileUploading(uniqueId, size);
var input = new UploadInput(contentType, contentDisposition, fileStream);
var logMessage = $"Uploading file {file.Describe()} with contentType: '{input.ContentType}' and disposition: '{input.ContentDisposition}'...";
var logMessage = $"Uploading file {file.Describe()}...";
var measurement = Stopwatch.Measure(log, logMessage, () =>
{
return CodexAccess.UploadFile(input, onFailure);
return CodexAccess.UploadFile(fileStream, onFailure);
});
var response = measurement.Value;
@@ -191,20 +166,10 @@ namespace CodexPlugin
public TrackedFile? DownloadContent(ContentId contentId, string fileLabel = "")
{
return DownloadContentT(contentId, fileLabel).Item1;
return DownloadContent(contentId, DoNothing, fileLabel);
}
public TrackedFile? DownloadContent(ContentId contentId, Action<Failure> onFailure, string fileLabel = "")
{
return DownloadContentT(contentId, onFailure, fileLabel).Item1;
}
public (TrackedFile?, TimeSpan) DownloadContentT(ContentId contentId, string fileLabel = "")
{
return DownloadContentT(contentId, DoNothing, fileLabel);
}
public (TrackedFile?, TimeSpan) DownloadContentT(ContentId contentId, Action<Failure> onFailure, string fileLabel = "")
{
var file = tools.GetFileManager().CreateEmptyFile(fileLabel);
hooks.OnFileDownloading(contentId);
@@ -217,34 +182,7 @@ namespace CodexPlugin
transferSpeeds.AddDownloadSample(size, measurement);
hooks.OnFileDownloaded(size, contentId);
return (file, measurement);
}
public LocalDataset DownloadStreamless(ContentId cid)
{
Log($"Downloading streamless '{cid}' (no-wait)");
return CodexAccess.DownloadStreamless(cid);
}
public LocalDataset DownloadStreamlessWait(ContentId cid, ByteSize size)
{
Log($"Downloading streamless '{cid}' (wait till finished)");
var sw = Stopwatch.Measure(log, nameof(DownloadStreamlessWait), () =>
{
var startSpace = Space();
var result = CodexAccess.DownloadStreamless(cid);
WaitUntilQuotaUsedIncreased(startSpace, size);
return result;
});
return sw.Value;
}
public LocalDataset DownloadManifestOnly(ContentId cid)
{
Log($"Downloading manifest-only '{cid}'");
return CodexAccess.DownloadManifestOnly(cid);
return file;
}
public LocalDatasetList LocalFiles()
@@ -355,39 +293,6 @@ namespace CodexPlugin
}
}
public void WaitUntilQuotaUsedIncreased(CodexSpace startSpace, ByteSize expectedIncreaseOfQuotaUsed)
{
WaitUntilQuotaUsedIncreased(startSpace, expectedIncreaseOfQuotaUsed, TimeSpan.FromMinutes(2));
}
public void WaitUntilQuotaUsedIncreased(
CodexSpace startSpace,
ByteSize expectedIncreaseOfQuotaUsed,
TimeSpan maxTimeout)
{
Log($"Waiting until quotaUsed " +
$"(start: {startSpace.QuotaUsedBytes}) " +
$"increases by {expectedIncreaseOfQuotaUsed} " +
$"to reach {startSpace.QuotaUsedBytes + expectedIncreaseOfQuotaUsed.SizeInBytes}");
var retry = new Retry($"Checking local space for quotaUsed increase of {expectedIncreaseOfQuotaUsed}",
maxTimeout: maxTimeout,
sleepAfterFail: TimeSpan.FromSeconds(3),
onFail: f => { });
retry.Run(() =>
{
var space = Space();
var increase = space.QuotaUsedBytes - startSpace.QuotaUsedBytes;
if (increase < expectedIncreaseOfQuotaUsed.SizeInBytes)
throw new Exception($"Expected quota-used not reached. " +
$"Expected increase: {expectedIncreaseOfQuotaUsed.SizeInBytes} " +
$"Actual increase: {increase} " +
$"Actual used: {space.QuotaUsedBytes}");
});
}
private void EnsureMarketplace()
{
if (ethAccount == null) throw new Exception("Marketplace is not enabled for this Codex node. Please start it with the option '.EnableMarketplace(...)' to enable it.");
+1 -10
View File
@@ -20,7 +20,7 @@ namespace CodexPlugin
public void Announce()
{
Log($"Loaded with Codex ID: '{codexStarter.GetCodexId()}' - Revision: {codexStarter.GetCodexRevision()}");
tools.GetLog().Log($"Loaded with Codex ID: '{codexStarter.GetCodexId()}' - Revision: {codexStarter.GetCodexRevision()}");
}
public void AddMetadata(IAddMetadata metadata)
@@ -55,10 +55,6 @@ namespace CodexPlugin
{
mconfig.GethNode.SendEth(node, mconfig.MarketplaceSetup.InitialEth);
mconfig.CodexContracts.MintTestTokens(node, mconfig.MarketplaceSetup.InitialTestTokens);
Log($"Send {mconfig.MarketplaceSetup.InitialEth} and " +
$"minted {mconfig.MarketplaceSetup.InitialTestTokens} for " +
$"{node.GetName()} (address: {node.EthAddress})");
}
}
@@ -74,10 +70,5 @@ namespace CodexPlugin
setup(codexSetup);
return codexSetup;
}
private void Log(string msg)
{
tools.GetLog().Log(msg);
}
}
}
+2 -69
View File
@@ -21,14 +21,6 @@ namespace CodexPlugin
};
}
public LocalDatasetList Map(LocalDatasetListJson json)
{
return new LocalDatasetList
{
Content = json.Content.Select(Map).ToArray()
};
}
public LocalDatasetList Map(CodexOpenApi.DataList dataList)
{
return new LocalDatasetList
@@ -46,15 +38,6 @@ namespace CodexPlugin
};
}
public LocalDataset Map(LocalDatasetListJsonItem item)
{
return new LocalDataset
{
Cid = new ContentId(item.Cid),
Manifest = MapManifest(item.Manifest)
};
}
public CodexOpenApi.SalesAvailabilityCREATE Map(StorageAvailability availability)
{
return new CodexOpenApi.SalesAvailabilityCREATE
@@ -199,20 +182,8 @@ namespace CodexPlugin
return new Manifest
{
BlockSize = new ByteSize(Convert.ToInt64(manifest.BlockSize)),
OriginalBytes = new ByteSize(Convert.ToInt64(manifest.DatasetSize)),
RootHash = manifest.TreeCid,
Protected = manifest.Protected
};
}
public Manifest MapManifest(LocalDatasetListJsonItemManifest manifest)
{
return new Manifest
{
// needs update
BlockSize = new ByteSize(Convert.ToInt64(manifest.BlockSize)),
OriginalBytes = new ByteSize(Convert.ToInt64(manifest.DatasetSize)),
RootHash = manifest.TreeCid,
OriginalBytes = new ByteSize(Convert.ToInt64(manifest.OriginalBytes)),
RootHash = manifest.RootHash,
Protected = manifest.Protected
};
}
@@ -272,42 +243,4 @@ namespace CodexPlugin
return new ByteSize(Convert.ToInt64(size));
}
}
//"content": [
// {
// "cid": "zDvZRwzkxLxVaGces3kpkHjo8EcTPXudvYMfNxdoH21Ask1Js5fJ",
// "manifest": {
// "treeCid": "zDzSvJTf8GBRyEDNuAzXS9VnRfh8cNuYuRPwTLW6RUQReSgKnhCt",
// "datasetSize": 5242880,
// "blockSize": 65536,
// "filename": null,
// "mimetype": "application/octet-stream",
// "uploadedAt": 1731426230,
// "protected": false
// }
// }
// ]
public class LocalDatasetListJson
{
public LocalDatasetListJsonItem[] Content { get; set; } = Array.Empty<LocalDatasetListJsonItem>();
}
public class LocalDatasetListJsonItem
{
public string Cid { get; set; } = string.Empty;
public LocalDatasetListJsonItemManifest Manifest { get; set; } = new();
}
public class LocalDatasetListJsonItemManifest
{
public string TreeCid { get; set; } = string.Empty;
public int DatasetSize { get; set; }
public int BlockSize { get; set; }
public string? Filename { get; set; } = string.Empty;
public string? MimeType { get; set; } = string.Empty;
public int? UploadedAt { get; set; }
public bool Protected { get; set; }
}
}
@@ -1,5 +1,7 @@
using CodexContractsPlugin;
using CodexOpenApi;
using Logging;
using System.Data;
using Utils;
namespace CodexPlugin
@@ -38,12 +40,6 @@ namespace CodexPlugin
public string State { get; set; } = string.Empty;
public string Error { get; set; } = string.Empty;
public StorageRequest Request { get; set; } = null!;
public bool IsCancelled => State.ToLowerInvariant().Contains("cancel");
public bool IsError => State.ToLowerInvariant().Contains("error");
public bool IsFinished => State.ToLowerInvariant().Contains("finished");
public bool IsStarted => State.ToLowerInvariant().Contains("started");
public bool IsSubmitted => State.ToLowerInvariant().Contains("submitted");
}
public class StorageRequest
@@ -1,6 +1,4 @@
using CodexContractsPlugin;
using CodexPlugin.Hooks;
using GethPlugin;
using CodexPlugin.Hooks;
using Logging;
using Newtonsoft.Json;
using Utils;
@@ -14,8 +12,7 @@ namespace CodexPlugin
ContentId ContentId { get; }
void WaitForStorageContractSubmitted();
void WaitForStorageContractStarted();
void WaitForStorageContractFinished(ICodexContracts contracts);
void WaitForContractFailed();
void WaitForStorageContractFinished();
}
public class StoragePurchaseContract : IStoragePurchaseContract
@@ -65,7 +62,7 @@ namespace CodexPlugin
AssertDuration(SubmittedToStarted, timeout, nameof(SubmittedToStarted));
}
public void WaitForStorageContractFinished(ICodexContracts contracts)
public void WaitForStorageContractFinished()
{
if (!contractStartedUtc.HasValue)
{
@@ -77,24 +74,6 @@ namespace CodexPlugin
contractFinishedUtc = DateTime.UtcNow;
LogFinishedDuration();
AssertDuration(SubmittedToFinished, timeout, nameof(SubmittedToFinished));
contracts.WaitUntilNextPeriod();
contracts.WaitUntilNextPeriod();
var blocks = 3;
Log($"Waiting {blocks} blocks for nodes to process payouts...");
Thread.Sleep(GethContainerRecipe.BlockInterval * blocks);
}
public void WaitForContractFailed()
{
if (!contractStartedUtc.HasValue)
{
WaitForStorageContractStarted();
}
var currentContractTime = DateTime.UtcNow - contractSubmittedUtc!.Value;
var timeout = (Purchase.Duration - currentContractTime) + gracePeriod;
WaitForStorageContractState(timeout, "failed");
}
public StoragePurchase GetPurchaseStatus(string purchaseId)
+18 -21
View File
@@ -344,10 +344,10 @@ components:
ManifestItem:
type: object
properties:
treeCid:
rootHash:
$ref: "#/components/schemas/Cid"
description: "Unique data identifier"
datasetSize:
description: "Root hash of the content"
originalBytes:
type: integer
format: int64
description: "Length of original content in bytes"
@@ -359,18 +359,15 @@ components:
description: "Indicates if content is protected by erasure-coding"
filename:
type: string
nullable: true
description: "The original name of the uploaded content (optional)"
example: codex.png
mimetype:
type: string
nullable: true
description: "The original mimetype of the uploaded content (optional)"
example: image/png
uploadedAt:
type: integer
format: int64
nullable: true
description: "The UTC upload timestamp in seconds"
example: 1729244192
@@ -446,6 +443,21 @@ paths:
summary: "Lists manifest CIDs stored locally in node."
tags: [ Data ]
operationId: listData
parameters:
- name: content-type
in: header
required: false
description: The content type of the file. Must be valid.
schema:
type: string
example: "image/png"
- name: content-disposition
in: header
required: false
description: The content disposition used to send the filename.
schema:
type: string
example: "attachment; filename=\"codex.png\""
responses:
"200":
description: Retrieved list of content CIDs
@@ -466,21 +478,6 @@ paths:
summary: "Upload a file in a streaming manner. Once finished, the file is stored in the node and can be retrieved by any node in the network using the returned CID."
tags: [ Data ]
operationId: upload
parameters:
- name: content-type
in: header
required: false
description: The content type of the file. Must be valid.
schema:
type: string
example: "image/png"
- name: content-disposition
in: header
required: false
description: The content disposition used to send the filename.
schema:
type: string
example: "attachment; filename=\"codex.png\""
requestBody:
content:
application/octet-stream:
@@ -1,5 +1,4 @@
using BlockchainUtils;
using Core;
using Core;
namespace GethPlugin
{
@@ -10,15 +9,15 @@ namespace GethPlugin
return Plugin(ci).DeployGeth(setup);
}
public static IGethNode WrapGethDeployment(this CoreInterface ci, GethDeployment deployment, BlockCache blockCache)
public static IGethNode WrapGethDeployment(this CoreInterface ci, GethDeployment deployment)
{
return Plugin(ci).WrapGethDeployment(deployment, blockCache);
return Plugin(ci).WrapGethDeployment(deployment);
}
public static IGethNode StartGethNode(this CoreInterface ci, BlockCache blockCache, Action<IGethSetup> setup)
public static IGethNode StartGethNode(this CoreInterface ci, Action<IGethSetup> setup)
{
var deploy = DeployGeth(ci, setup);
return WrapGethDeployment(ci, deploy, blockCache);
return WrapGethDeployment(ci, deploy);
}
private static GethPlugin Plugin(CoreInterface ci)
@@ -6,7 +6,6 @@ namespace GethPlugin
public class GethContainerRecipe : ContainerRecipeFactory
{
public static string DockerImage { get; } = "codexstorage/dist-tests-geth:latest";
public static TimeSpan BlockInterval { get; } = TimeSpan.FromSeconds(1.0);
private const string defaultArgs = "--ipcdisable --syncmode full";
public const string HttpPortTag = "http_port";
+6 -10
View File
@@ -1,11 +1,11 @@
using BlockchainUtils;
using Core;
using Core;
using KubernetesWorkflow.Types;
using Logging;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.Contracts;
using Nethereum.RPC.Eth.DTOs;
using NethereumWorkflow;
using NethereumWorkflow.BlockUtils;
using Utils;
namespace GethPlugin
@@ -34,12 +34,10 @@ namespace GethPlugin
public class DeploymentGethNode : BaseGethNode, IGethNode
{
private readonly ILog log;
private readonly BlockCache blockCache;
public DeploymentGethNode(ILog log, BlockCache blockCache, GethDeployment startResult)
public DeploymentGethNode(ILog log, GethDeployment startResult)
{
this.log = log;
this.blockCache = blockCache;
StartResult = startResult;
}
@@ -62,7 +60,7 @@ namespace GethPlugin
var address = StartResult.Container.GetAddress(GethContainerRecipe.HttpPortTag);
var account = StartResult.Account;
var creator = new NethereumInteractionCreator(log, blockCache, address.Host, address.Port, account.PrivateKey);
var creator = new NethereumInteractionCreator(log, address.Host, address.Port, account.PrivateKey);
return creator.CreateWorkflow();
}
}
@@ -70,7 +68,6 @@ namespace GethPlugin
public class CustomGethNode : BaseGethNode, IGethNode
{
private readonly ILog log;
private readonly BlockCache blockCache;
private readonly string gethHost;
private readonly int gethPort;
private readonly string privateKey;
@@ -78,10 +75,9 @@ namespace GethPlugin
public GethDeployment StartResult => throw new NotImplementedException();
public RunningContainer Container => throw new NotImplementedException();
public CustomGethNode(ILog log, BlockCache blockCache, string gethHost, int gethPort, string privateKey)
public CustomGethNode(ILog log, string gethHost, int gethPort, string privateKey)
{
this.log = log;
this.blockCache = blockCache;
this.gethHost = gethHost;
this.gethPort = gethPort;
this.privateKey = privateKey;
@@ -94,7 +90,7 @@ namespace GethPlugin
protected override NethereumInteraction StartInteraction()
{
var creator = new NethereumInteractionCreator(log, blockCache, gethHost, gethPort, privateKey);
var creator = new NethereumInteractionCreator(log, gethHost, gethPort, privateKey);
return creator.CreateWorkflow();
}
}
+3 -4
View File
@@ -1,5 +1,4 @@
using BlockchainUtils;
using Core;
using Core;
namespace GethPlugin
{
@@ -37,10 +36,10 @@ namespace GethPlugin
return starter.StartGeth(startupConfig);
}
public IGethNode WrapGethDeployment(GethDeployment startResult, BlockCache blockCache)
public IGethNode WrapGethDeployment(GethDeployment startResult)
{
startResult = SerializeGate.Gate(startResult);
return starter.WrapGethContainer(startResult, blockCache);
return starter.WrapGethContainer(startResult);
}
}
}
+3 -4
View File
@@ -1,5 +1,4 @@
using BlockchainUtils;
using Core;
using Core;
using KubernetesWorkflow;
namespace GethPlugin
@@ -42,10 +41,10 @@ namespace GethPlugin
return new GethDeployment(containers, discoveryPort, httpPort, wsPort, account, pubKey);
}
public IGethNode WrapGethContainer(GethDeployment startResult, BlockCache blockCache)
public IGethNode WrapGethContainer(GethDeployment startResult)
{
startResult = SerializeGate.Gate(startResult);
return new DeploymentGethNode(tools.GetLog(), blockCache, startResult);
return new DeploymentGethNode(tools.GetLog(), startResult);
}
private void Log(string msg)
-19
View File
@@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
<ProjectReference Include="..\Tests\DistTestCore\DistTestCore.csproj" />
<ProjectReference Include="..\Tests\ExperimentalTests\ExperimentalTests.csproj" />
</ItemGroup>
</Project>
-107
View File
@@ -1,107 +0,0 @@
using CodexPlugin;
using CodexTests;
using NUnit.Framework;
using System.Diagnostics;
using System.Drawing;
using Utils;
namespace SpeedCheckTests
{
[TestFixture]
public class SpeedTest : CodexDistTest
{
[Test]
public void Symmetric()
{
// Symmetric: A node always sends a reply to every message it receives.
CodexContainerRecipe.DockerImageOverride = "thatbenbierens/nim-codex:blkex-cancelpresence-27-f";
var uploader = StartCodex(s => s.WithName("SymUploader"));
var downloader = StartCodex(s => s.WithName("SymDownloader").WithBootstrapNode(uploader));
var timeTaken = PerformTest(uploader, downloader);
Console.WriteLine($"Symmetric time: {Time.FormatDuration(timeTaken)}");
Assert.That(timeTaken, Is.LessThan(TimeSpan.FromSeconds(10.0)),
$"Symmetric: Too slow. Expected less than 10 seconds but was: {Time.FormatDuration(timeTaken)}");
}
[Test]
public void Asymmetric()
{
// Asymmetric: A node does not always send a reply when a message is received.
CodexContainerRecipe.DockerImageOverride = "thatbenbierens/nim-codex:blkex-cancelpresence-27-s";
var uploader = StartCodex(s => s.WithName("AsymUploader"));
var downloader = StartCodex(s => s.WithName("AsymDownloader").WithBootstrapNode(uploader));
var timeTaken = PerformTest(uploader, downloader);
Console.WriteLine($"Asymmetric time: {Time.FormatDuration(timeTaken)}");
Assert.That(timeTaken, Is.LessThan(TimeSpan.FromSeconds(10.0)),
$"Asymmetric: Too slow. Expected less than 10 seconds but was: {Time.FormatDuration(timeTaken)}");
}
[Test]
public void Binary()
{
// Docker image not used: Here for api check.
CodexContainerRecipe.DockerImageOverride = "thatbenbierens/nim-codex:blkex-cancelpresence-27-f";
var binary = "C:\\Projects\\nim-codex\\build\\codex.exe";
if (!File.Exists(binary)) throw new Exception("TODO: Update binary path");
var uploadInfo = new ProcessStartInfo
{
FileName = binary,
Arguments = "--data-dir=upload_data " +
"--api-port=8081 " +
"--nat=127.0.0.1 " +
"--disc-ip=127.0.0.1 " +
"--disc-port=8091 " +
"--listen-addrs=/ip4/127.0.0.1/tcp/8071",
UseShellExecute = true,
};
var uploadProcess = Process.Start(uploadInfo);
Thread.Sleep(5000);
if (uploadProcess == null || uploadProcess.HasExited) throw new Exception("Node exited.");
CodexAccess.UploaderOverride = new Address("http://localhost", 8081);
var uploader = StartCodex(s => s.WithName("BinaryUploader"));
var spr = uploader.GetSpr();
var downloadProcess = Process.Start(binary,
"--data-dir=download_data " +
"--api-port=8082 " +
"--nat=127.0.0.1 " +
"--disc-ip=127.0.0.1 " +
"--disc-port=8092 " +
"--listen-addrs=/ip4/127.0.0.1/tcp/8072 " +
"--bootstrap-node=" + spr
);
CodexAccess.DownloaderOverride = new Address("http://localhost", 8082);
var downloader = StartCodex(s => s.WithName("BinaryDownloader"));
var timeTaken = PerformTest(uploader, downloader);
uploadProcess.Kill();
downloadProcess.Kill();
Console.WriteLine($"Binary time: {Time.FormatDuration(timeTaken)}");
Assert.That(timeTaken, Is.LessThan(TimeSpan.FromSeconds(10.0)),
$"Binary: Too slow. Expected less than 10 seconds but was: {Time.FormatDuration(timeTaken)}");
}
private TimeSpan PerformTest(ICodexNode uploader, ICodexNode downloader)
{
var testFile = GenerateTestFile(100.MB());
var contentId = uploader.UploadFile(testFile);
var (downloadedFile, timeTaken) = downloader.DownloadContentT(contentId);
return timeTaken;
}
}
}
@@ -14,8 +14,8 @@
<ItemGroup>
<ProjectReference Include="..\..\Framework\ArgsUniform\ArgsUniform.csproj" />
<ProjectReference Include="..\..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
<ProjectReference Include="..\CodexTests\CodexTests.csproj" />
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
<ProjectReference Include="..\ExperimentalTests\ExperimentalTests.csproj" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -130,7 +130,7 @@ namespace ContinuousTests
var namespaceName = container.RunningPod.StartResult.Cluster.Configuration.KubernetesNamespace;
var openingLine =
$"{namespaceName} - {deploymentName} = {node.Container.Name} = {node.GetDebugInfo().Id}";
elasticSearchLogDownloader.Download(fixtureLog.CreateSubfile(node.GetName()), node.Container, effectiveStart,
elasticSearchLogDownloader.Download(fixtureLog.CreateSubfile(), node.Container, effectiveStart,
effectiveEnd, openingLine);
}
}
+1 -1
View File
@@ -14,8 +14,8 @@
<ItemGroup>
<ProjectReference Include="..\..\Framework\Core\Core.csproj" />
<ProjectReference Include="..\CodexTests\CodexTests.csproj" />
<ProjectReference Include="..\DistTestCore\DistTestCore.csproj" />
<ProjectReference Include="..\ExperimentalTests\ExperimentalTests.csproj" />
</ItemGroup>
</Project>
@@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExperimentalTests\ExperimentalTests.csproj" />
</ItemGroup>
</Project>
@@ -1,33 +0,0 @@
using CodexTests;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.DataTests
{
[TestFixture]
public class ManifestOnlyDownloadTest : CodexDistTest
{
[Test]
public void ManifestOnlyTest()
{
var uploader = StartCodex();
var downloader = StartCodex(s => s.WithBootstrapNode(uploader));
var file = GenerateTestFile(2.GB());
var size = file.GetFilesize().SizeInBytes;
var cid = uploader.UploadFile(file);
var startSpace = downloader.Space();
var localDataset = downloader.DownloadManifestOnly(cid);
Thread.Sleep(1000);
var spaceDiff = startSpace.FreeBytes - downloader.Space().FreeBytes;
Assert.That(spaceDiff, Is.LessThan(64.KB().SizeInBytes));
Assert.That(localDataset.Cid, Is.EqualTo(cid));
Assert.That(localDataset.Manifest.OriginalBytes.SizeInBytes, Is.EqualTo(file.GetFilesize().SizeInBytes));
}
}
}
@@ -1,39 +0,0 @@
using CodexPlugin;
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.DataTests
{
[TestFixture]
public class OneClientTest : CodexDistTest
{
[Test]
public void OneClient()
{
var node = StartCodex();
PerformOneClientTest(node);
LogNodeStatus(node);
}
private void PerformOneClientTest(ICodexNode primary)
{
var testFile = GenerateTestFile(1.MB());
var contentId = primary.UploadFile(testFile);
AssertNodesContainFile(contentId, primary);
var downloadedFile = primary.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
}
}
}
@@ -1,35 +0,0 @@
using CodexPlugin;
using CodexTests;
using NUnit.Framework;
using System.Drawing;
using Utils;
namespace CodexReleaseTests.DataTests
{
[TestFixture]
public class StreamlessDownloadTest : CodexDistTest
{
[Test]
public void StreamlessTest()
{
var uploader = StartCodex();
var downloader = StartCodex(s => s.WithBootstrapNode(uploader));
var size = 10.MB();
var file = GenerateTestFile(size);
var cid = uploader.UploadFile(file);
var startSpace = downloader.Space();
var start = DateTime.UtcNow;
var localDataset = downloader.DownloadStreamlessWait(cid, size);
Assert.That(localDataset.Cid, Is.EqualTo(cid));
Assert.That(localDataset.Manifest.OriginalBytes.SizeInBytes, Is.EqualTo(file.GetFilesize().SizeInBytes));
// Stop the uploader node and verify that the downloader has the data.
uploader.Stop(waitTillStopped: true);
var downloaded = downloader.DownloadContent(cid);
file.AssertIsEqual(downloaded);
}
}
}
@@ -1,177 +0,0 @@
using CodexPlugin;
using CodexTests;
using FileUtils;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.DataTests
{
[TestFixture]
public class SwarmTests : AutoBootstrapDistTest
{
[Test]
[Combinatorial]
public void SmallSwarm(
[Values(2)] int numberOfNodes,
[Values(10)] int filesizeMb
)
{
var filesize = filesizeMb.MB();
var nodes = StartCodex(numberOfNodes);
var files = nodes.Select(n => UploadUniqueFilePerNode(n, filesize)).ToArray();
var tasks = ParallelDownloadEachFile(nodes, files);
Task.WaitAll(tasks);
AssertAllFilesDownloadedCorrectly(files);
}
[Test]
[Combinatorial]
public void StreamlessSmallSwarm(
[Values(2)] int numberOfNodes,
[Values(10)] int filesizeMb
)
{
var filesize = filesizeMb.MB();
var nodes = StartCodex(numberOfNodes);
var files = nodes.Select(n => UploadUniqueFilePerNode(n, filesize)).ToArray();
var tasks = ParallelStreamlessDownloadEachFile(nodes, files);
Task.WaitAll(tasks);
AssertAllFilesStreamlesslyDownloadedCorrectly(nodes, files);
}
private SwarmTestNetworkFile UploadUniqueFilePerNode(ICodexNode node, ByteSize fileSize)
{
var file = GenerateTestFile(fileSize);
var cid = node.UploadFile(file);
return new SwarmTestNetworkFile(node, fileSize, file, cid);
}
private Task[] ParallelDownloadEachFile(ICodexNodeGroup nodes, SwarmTestNetworkFile[] files)
{
var tasks = new List<Task>();
foreach (var node in nodes)
{
tasks.Add(StartDownload(node, files));
}
return tasks.ToArray();
}
private Task[] ParallelStreamlessDownloadEachFile(ICodexNodeGroup nodes, SwarmTestNetworkFile[] files)
{
var tasks = new List<Task>();
foreach (var node in nodes)
{
tasks.Add(StartStreamlessDownload(node, files));
}
return tasks.ToArray();
}
private Task StartDownload(ICodexNode node, SwarmTestNetworkFile[] files)
{
return Task.Run(() =>
{
var remaining = files.ToList();
while (remaining.Count > 0)
{
var file = remaining.PickOneRandom();
try
{
var dl = node.DownloadContent(file.Cid);
lock (file.Lock)
{
file.Downloaded.Add(dl);
}
}
catch (Exception ex)
{
file.Error = ex;
}
}
});
}
private Task StartStreamlessDownload(ICodexNode node, SwarmTestNetworkFile[] files)
{
return Task.Run(() =>
{
var remaining = files.ToList();
while (remaining.Count > 0)
{
var file = remaining.PickOneRandom();
if (file.Uploader.GetName() != node.GetName())
{
try
{
var startSpace = node.Space();
node.DownloadStreamlessWait(file.Cid, file.OriginalSize);
}
catch (Exception ex)
{
file.Error = ex;
}
}
}
});
}
private void AssertAllFilesDownloadedCorrectly(SwarmTestNetworkFile[] files)
{
foreach (var file in files)
{
if (file.Error != null) throw file.Error;
lock (file.Lock)
{
foreach (var dl in file.Downloaded)
{
file.Original.AssertIsEqual(dl);
}
}
}
}
private void AssertAllFilesStreamlesslyDownloadedCorrectly(ICodexNodeGroup nodes, SwarmTestNetworkFile[] files)
{
var totalFilesSpace = 0.Bytes();
foreach (var file in files)
{
if (file.Error != null) throw file.Error;
totalFilesSpace = new ByteSize(totalFilesSpace.SizeInBytes + file.Original.GetFilesize().SizeInBytes);
}
foreach (var node in nodes)
{
var currentSpace = node.Space();
Assert.That(currentSpace.QuotaUsedBytes, Is.GreaterThanOrEqualTo(totalFilesSpace.SizeInBytes));
}
}
private class SwarmTestNetworkFile
{
public SwarmTestNetworkFile(ICodexNode uploader, ByteSize originalSize, TrackedFile original, ContentId cid)
{
Uploader = uploader;
OriginalSize = originalSize;
Original = original;
Cid = cid;
}
public ICodexNode Uploader { get; }
public ByteSize OriginalSize { get; }
public TrackedFile Original { get; }
public ContentId Cid { get; }
public object Lock { get; } = new object();
public List<TrackedFile?> Downloaded { get; } = new List<TrackedFile?>();
public Exception? Error { get; set; } = null;
}
}
}
@@ -1,32 +0,0 @@
using CodexTests;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utils;
namespace CodexReleaseTests.DataTests
{
public class ThreeClientTest : AutoBootstrapDistTest
{
[Test]
public void ThreeClient()
{
var primary = StartCodex();
var secondary = StartCodex();
var testFile = GenerateTestFile(10.MB());
var contentId = primary.UploadFile(testFile);
AssertNodesContainFile(contentId, primary);
var downloadedFile = secondary.DownloadContent(contentId);
AssertNodesContainFile(contentId, primary, secondary);
testFile.AssertIsEqual(downloadedFile);
}
}
}
@@ -1,207 +0,0 @@
using CodexPlugin;
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.DataTests
{
[TestFixture]
public class TwoClientTests : CodexDistTest
{
[Test]
[Combinatorial]
public void TwoClientTest(
[Values(
//"thatbenbierens/nim-codex:blkex-cancelpresence-2", // S don't send cancel-presence messages
//"thatbenbierens/nim-codex:blkex-cancelpresence-1", // F ignore cancel-presence messages
//"codexstorage/nim-codex:sha-4b5c355-dist-tests", // F unmodified
//"thatbenbierens/nim-codex:blkex-cancelpresence-3", // F same as 1 but logging
//"thatbenbierens/nim-codex:blkex-cancelpresence-4", // S no cancel-presence-msg, no fromCancel field
//"thatbenbierens/nim-codex:blkex-cancelpresence-5", // F all-presence = cancel? return from handler
//"thatbenbierens/nim-codex:blkex-cancelpresence-6", // F no cancel-presence-msg, but if any cancel send empty presence msg
//"thatbenbierens/nim-codex:blkex-cancelpresence-7", // F same but logs outgoing empty presence message. (msg is empty structure)
//"thatbenbierens/nim-codex:blkex-cancelpresence-8", // crashes F? eventtimelogging
//"thatbenbierens/nim-codex:blkex-cancelpresence-9", // crashes S? eventtimelogging + no cancel-presence-msg (should be slow)
//"thatbenbierens/nim-codex:blkex-cancelpresence-10", // F eventtimelogging (should be fast)
//"thatbenbierens/nim-codex:blkex-cancelpresence-11", // S eventtimelogging + no cancel-presence-msg (should be slow)
//"thatbenbierens/nim-codex:blkex-cancelpresence-12", // F upload and download event logging (should be fast)
//"thatbenbierens/nim-codex:blkex-cancelpresence-13", // S same but with no cancel-presence-msg (should be slow)
//"thatbenbierens/nim-codex:peerselecta-1", // F PR update (yes cancel-presence-msg)
//"thatbenbierens/nim-codex:peerselecta-2", // S PR update (no cancel-presence-msg)
//"thatbenbierens/nim-codex:blkex-cancelpresence-14", // F new logging
//"thatbenbierens/nim-codex:blkex-cancelpresence-15", // S new logging
//"thatbenbierens/nim-codex:blkex-cancelpresence-16-f", // F more logging
//"thatbenbierens/nim-codex:blkex-cancelpresence-16-s", // S more logging
//"thatbenbierens/nim-codex:blkex-cancelpresence-17-f", // F "tick" every 100 milliseconds
//"thatbenbierens/nim-codex:blkex-cancelpresence-17-s", // S same but slow
//"thatbenbierens/nim-codex:blkex-cancelpresence-18-f", // F "tick" every 10 milliseconds
//"thatbenbierens/nim-codex:blkex-cancelpresence-18-s", // S same but slow
//"thatbenbierens/nim-codex:blkex-cancelpresence-19-f", // F sending/sent/received logs
//"thatbenbierens/nim-codex:blkex-cancelpresence-19-s", // S same but slow
//"thatbenbierens/nim-codex:blkex-cancelpresence-20-f", // F sending/sent/received logs + number
//"thatbenbierens/nim-codex:blkex-cancelpresence-20-s", // S same but slow
//"thatbenbierens/nim-codex:blkex-cancelpresence-21-f", // F libp2p lpchannel.write logs
//"thatbenbierens/nim-codex:blkex-cancelpresence-21-s", // S same but slow
//"thatbenbierens/nim-codex:blkex-cancelpresence-22-f", // F chronos stream write logs
//"thatbenbierens/nim-codex:blkex-cancelpresence-22-s", // S same but slow
"thatbenbierens/nim-codex:blkex-cancelpresence-23-f", // F chronos stream write logs in libp2p hand-off
"thatbenbierens/nim-codex:blkex-cancelpresence-23-s", // S same but slow
"thatbenbierens/nim-codex:blkex-cancelpresence-25-f", // F chronos stream write logs in libp2p hand-off with ticks
"thatbenbierens/nim-codex:blkex-cancelpresence-25-s", // S same but slow
"thatbenbierens/nim-codex:blkex-cancelpresence-27-f", // F chronos stream write logs in libp2p hand-off with ticks adds names
"thatbenbierens/nim-codex:blkex-cancelpresence-27-s" // S same but slow
)] string img
)
{
CodexContainerRecipe.DockerImageOverride = img;
var uploader = StartCodex(s => s.WithName("Uploader"));
var downloader = StartCodex(s => s.WithName("Downloader").WithBootstrapNode(uploader));
PerformTwoClientTest(uploader, downloader);
}
[Test]
public void ParseLogs()
{
var path = "d:\\Dev\\cs-codex-dist-tests\\Tests\\CodexReleaseTests\\bin\\Debug\\net8.0\\CodexTestLogs\\2025-01\\09\\13-58-28Z_TwoClientTests\\";
var file1 = Path.Combine(path, "TwoClientTest[thatbenbierens_nim-codex_blkex-cancelpresence-27-f]_000001_Downloader1.log");
var file2 = Path.Combine(path, "TwoClientTest[thatbenbierens_nim-codex_blkex-cancelpresence-27-f]_000000_Uploader0.log");
var file3 = Path.Combine(path, "TwoClientTest[thatbenbierens_nim-codex_blkex-cancelpresence-27-s]_000001_Downloader1.log");
var file4 = Path.Combine(path, "TwoClientTest[thatbenbierens_nim-codex_blkex-cancelpresence-27-s]_000000_Uploader0.log");
var lines = File.ReadAllLines(file3);
var clines = new List<CodexLogLine>();
foreach (var line in lines)
{
var cline = CodexLogLine.Parse(line);
if (cline != null) clines.Add(cline);
}
var gaps = new List<Gap>();
for (var i = 0; i < clines.Count; i++)
{
var line = clines[i];
// todo:
//TRC 2025-01-09 13:59:14.501+00:00 chronosread topics="libp2p chronosstream custom" tid=1 ticks=424485 name=ChronosStream count=32669
//TRC 2025-01-09 13:59:14.501+00:00 chronosread topics="libp2p chronosstream custom" tid=1 ticks=600 name=ChronosStream count=32670
//TRC 2025-01-09 13:59:14.501+00:00 readOnce topics="libp2p mplexchannel custom" tid=1 s=16U*uBBR7j:677fd62fe0c5bd152c675e42:677fd62ff7548faf70a27174 bytes=1 count=32671
//TRC 2025-01-09 13:59:14.501+00:00 readOnce topics="libp2p mplexchannel custom" tid=1 s=16U*uBBR7j:677fd62fe0c5bd152c675e42:677fd62ff7548faf70a27174 bytes=73 count=32672
//TRC 2025-01-09 13:59:14.501+00:00 MsgReceived topics="codex blockexcnetworkpeer" tid=1 num=7 count=32673
// read to received!???
// run in cluster, same effect???
// run native, same effect?
if (line.Message == "MsgSending")
{
// the next line is lpc-write-fast, then chronoswrite
if (i + 2 < clines.Count)
{
var next = clines[i + 2];
if (next.Message == "chronoswrite")
{
// got ya!
gaps.Add(new Gap(line, next));
}
else
{
var aaaa = "what is it?!";
}
}
}
}
gaps = gaps.OrderByDescending(g => g.GapSpan.TotalMilliseconds).ToList();
var iiii = 0;
}
public class Gap
{
public Gap(CodexLogLine line, CodexLogLine next)
{
Line = line;
Next = next;
}
public CodexLogLine Line { get; }
public CodexLogLine Next { get; }
public TimeSpan GapSpan
{
get
{
return Next.TimestampUtc - Line.TimestampUtc;
}
}
public override string ToString()
{
return $"[{GapSpan.TotalMilliseconds} ms]";
}
}
private void ProcessTimes(CodexLogLine cline)
{
// reqCreatedTime
// wantHaveSentTimes
// presenceRecvTimes
// wantBlkSentTimes
// blkRecvTimes
// cancelSentTimes
// resolveTimes
}
public class BlockReqTimes
{
public TimeSpan CreateToWantHaveSent { get; set; }
}
private void PerformTwoClientTest(ICodexNode uploader, ICodexNode downloader)
{
PerformTwoClientTest(uploader, downloader, 100.MB());
}
private void PerformTwoClientTest(ICodexNode uploader, ICodexNode downloader, ByteSize size)
{
var testFile = GenerateTestFile(size);
var contentId = uploader.UploadFile(testFile);
AssertNodesContainFile(contentId, uploader);
var (downloadedFile, timeTaken) = downloader.DownloadContentT(contentId);
AssertNodesContainFile(contentId, uploader, downloader);
Assert.That(timeTaken, Is.LessThan(TimeSpan.FromSeconds(15.0)), "Too slow!");
testFile.AssertIsEqual(downloadedFile);
}
}
}
@@ -1,38 +0,0 @@
using CodexPlugin;
using CodexTests;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodexReleaseTests.DataTests
{
[TestFixture]
public class UnknownCidTest : CodexDistTest
{
[Test]
public void DownloadingUnknownCidDoesNotCauseCrash()
{
var node = StartCodex();
var unknownCid = new ContentId("zDvZRwzkzHsok3Z8yMoiXE9EDBFwgr8WygB8s4ddcLzzSwwXAxLZ");
var localFiles = node.LocalFiles().Content;
CollectionAssert.DoesNotContain(localFiles.Select(f => f.Cid), unknownCid);
try
{
node.DownloadContent(unknownCid);
}
catch (Exception ex)
{
var expectedMessage = $"Download of '{unknownCid.Id}' timed out";
if (!ex.Message.StartsWith(expectedMessage)) throw;
}
WaitAndCheckNodesStaysAlive(TimeSpan.FromMinutes(2), node);
}
}
}
@@ -1,111 +0,0 @@
using CodexContractsPlugin;
using CodexContractsPlugin.Marketplace;
using CodexPlugin;
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.MarketTests
{
public class ContractFailedTest : 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);
private readonly TestToken pricePerSlotPerSecond = 10.TstWei();
[Test]
public void ContractFailed()
{
var hosts = StartHosts();
var client = StartClients().Single();
StartValidator();
var request = CreateStorageRequest(client);
request.WaitForStorageContractSubmitted();
AssertContractIsOnChain(request);
request.WaitForStorageContractStarted();
AssertContractSlotsAreFilledByHosts(request, hosts);
hosts.BringOffline(waitTillStopped: true);
WaitForSlotFreedEvents();
request.WaitForContractFailed();
}
private void WaitForSlotFreedEvents()
{
Log(nameof(WaitForSlotFreedEvents));
var start = DateTime.UtcNow;
var timeout = CalculateContractFailTimespan();
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 TimeSpan CalculateContractFailTimespan()
{
var config = GetContracts().Deployment.Config;
var maxSlashesBeforeSlotFreed = Convert.ToInt32(config.Collateral.MaxNumberOfSlashes);
var numProofsMissedBeforeSlash = Convert.ToInt32(config.Collateral.SlashCriterion);
var periodDuration = GetPeriodDuration();
var requiredNumMissedProofs = maxSlashesBeforeSlotFreed * numProofsMissedBeforeSlash;
// 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(5.MB()));
return client.Marketplace.RequestStorage(new StoragePurchaseRequest(cid)
{
Duration = TimeSpan.FromHours(1.0),
Expiry = TimeSpan.FromHours(0.2),
MinRequiredNumberOfNodes = (uint)NumberOfHosts,
NodeFailureTolerance = (uint)(NumberOfHosts / 2),
PricePerSlotPerSecond = pricePerSlotPerSecond,
ProofProbability = 1, // Require a proof every period
RequiredCollateral = 1.Tst()
});
}
}
}
@@ -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()
{
}
}
}
@@ -1,75 +0,0 @@
using CodexContractsPlugin;
using CodexPlugin;
using GethPlugin;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
[TestFixture]
public class ContractSuccessfulTest : MarketplaceAutoBootstrapDistTest
{
private const int FilesizeMb = 10;
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 pricePerSlotPerSecond = 10.TstWei();
[Test]
public void ContractSuccessful()
{
var hosts = StartHosts();
var client = StartClients().Single();
var request = CreateStorageRequest(client);
request.WaitForStorageContractSubmitted();
AssertContractIsOnChain(request);
request.WaitForStorageContractStarted();
AssertContractSlotsAreFilledByHosts(request, hosts);
request.WaitForStorageContractFinished(GetContracts());
AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts);
AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts);
AssertHostsCollateralsAreUnchanged(hosts);
}
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(),
// 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,
PricePerSlotPerSecond = pricePerSlotPerSecond,
ProofProbability = 20,
RequiredCollateral = 1.Tst()
});
}
private TimeSpan GetContractExpiry()
{
return GetContractDuration() / 2;
}
private TimeSpan GetContractDuration()
{
return Get8TimesConfiguredPeriodDuration() / 2;
}
private TimeSpan Get8TimesConfiguredPeriodDuration()
{
return GetPeriodDuration() * 8.0;
}
}
}
@@ -1,279 +0,0 @@
using CodexContractsPlugin;
using CodexContractsPlugin.Marketplace;
using CodexPlugin;
using CodexTests;
using DistTestCore;
using GethPlugin;
using Nethereum.Hex.HexConvertors.Extensions;
using NUnit.Framework;
using Utils;
namespace CodexReleaseTests.MarketTests
{
public abstract class MarketplaceAutoBootstrapDistTest : AutoBootstrapDistTest
{
private readonly Dictionary<TestLifecycle, MarketplaceHandle> handles = new Dictionary<TestLifecycle, MarketplaceHandle>();
protected const int StartingBalanceTST = 1000;
protected const int StartingBalanceEth = 10;
protected override void LifecycleStart(TestLifecycle lifecycle)
{
base.LifecycleStart(lifecycle);
var geth = StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth);
handles.Add(lifecycle, new MarketplaceHandle(geth, contracts));
}
protected override void LifecycleStop(TestLifecycle lifecycle, DistTestResult result)
{
base.LifecycleStop(lifecycle, result);
handles.Remove(lifecycle);
}
protected IGethNode GetGeth()
{
return handles[Get()].Geth;
}
protected ICodexContracts GetContracts()
{
return handles[Get()].Contracts;
}
protected TimeSpan GetPeriodDuration()
{
var config = GetContracts().Deployment.Config;
return TimeSpan.FromSeconds(((double)config.Proofs.Period));
}
protected abstract int NumberOfHosts { get; }
protected abstract int NumberOfClients { get; }
protected abstract ByteSize HostAvailabilitySize { get; }
protected abstract TimeSpan HostAvailabilityMaxDuration { get; }
public ICodexNodeGroup StartHosts()
{
var hosts = StartCodex(NumberOfHosts, s => s
.WithName("host")
.EnableMarketplace(GetGeth(), GetContracts(), m => m
.WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst())
.AsStorageNode()
)
);
var config = GetContracts().Deployment.Config;
foreach (var host in hosts)
{
Assert.That(GetTstBalance(host).TstWei, Is.EqualTo(StartingBalanceTST.Tst().TstWei));
Assert.That(GetEthBalance(host).Wei, Is.EqualTo(StartingBalanceEth.Eth().Wei));
host.Marketplace.MakeStorageAvailable(new CodexPlugin.StorageAvailability(
totalSpace: HostAvailabilitySize,
maxDuration: HostAvailabilityMaxDuration,
minPriceForTotalSpace: 1.TstWei(),
maxCollateral: 999999.Tst())
);
}
return hosts;
}
public TestToken GetTstBalance(ICodexNode node)
{
return GetContracts().GetTestTokenBalance(node);
}
public TestToken GetTstBalance(EthAddress address)
{
return GetContracts().GetTestTokenBalance(address);
}
public Ether GetEthBalance(ICodexNode node)
{
return GetGeth().GetEthBalance(node);
}
public Ether GetEthBalance(EthAddress address)
{
return GetGeth().GetEthBalance(address);
}
public ICodexNodeGroup StartClients()
{
return StartCodex(NumberOfClients, s => s
.WithName("client")
.EnableMarketplace(GetGeth(), GetContracts(), m => m
.WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst())
)
);
}
public ICodexNode StartValidator()
{
return StartCodex(s => s
.WithName("validator")
.EnableMarketplace(GetGeth(), GetContracts(), m => m
.WithInitial(StartingBalanceEth.Eth(), StartingBalanceTST.Tst())
.AsValidator()
)
);
}
public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts, string purchaseId)
{
var fills = GetOnChainSlotFills(possibleHosts);
return fills.Where(f => f
.SlotFilledEvent.RequestId.ToHex(false).ToLowerInvariant() == purchaseId.ToLowerInvariant())
.ToArray();
}
public SlotFill[] GetOnChainSlotFills(ICodexNodeGroup possibleHosts)
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
var fills = events.GetSlotFilledEvents();
return fills.Select(f =>
{
var host = possibleHosts.Single(h => h.EthAddress.Address == f.Host.Address);
return new SlotFill(f, host);
}).ToArray();
}
protected void AssertClientHasPaidForContract(TestToken pricePerSlotPerSecond, ICodexNode client, IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var balance = GetTstBalance(client);
var expectedBalance = StartingBalanceTST.Tst() - GetContractFinalCost(pricePerSlotPerSecond, contract, hosts);
Assert.That(balance, Is.EqualTo(expectedBalance), "Client balance incorrect.");
}
protected void AssertHostsWerePaidForContract(TestToken pricePerSlotPerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var fills = GetOnChainSlotFills(hosts);
var submitUtc = GetContractOnChainSubmittedUtc(contract);
var finishUtc = submitUtc + contract.Purchase.Duration;
var expectedBalances = new Dictionary<EthAddress, TestToken>();
foreach (var host in hosts) expectedBalances.Add(host.EthAddress, StartingBalanceTST.Tst());
foreach (var fill in fills)
{
var slotDuration = finishUtc - fill.SlotFilledEvent.Block.Utc;
expectedBalances[fill.Host.EthAddress] += GetContractCostPerSlot(pricePerSlotPerSecond, slotDuration);
}
foreach (var pair in expectedBalances)
{
var balance = GetTstBalance(pair.Key);
Assert.That(balance, Is.EqualTo(pair.Value), "Host was not paid for storage.");
}
}
protected void AssertHostsCollateralsAreUnchanged(ICodexNodeGroup hosts)
{
// There is no separate collateral location yet.
// All host balances should be equal to or greater than the starting balance.
foreach (var host in hosts)
{
Assert.That(GetTstBalance(host), Is.GreaterThanOrEqualTo(StartingBalanceTST.Tst()));
}
}
private TestToken GetContractFinalCost(TestToken pricePerSlotPerSecond, IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var fills = GetOnChainSlotFills(hosts);
var result = 0.Tst();
var submitUtc = GetContractOnChainSubmittedUtc(contract);
var finishUtc = submitUtc + contract.Purchase.Duration;
foreach (var fill in fills)
{
var slotDuration = finishUtc - fill.SlotFilledEvent.Block.Utc;
result += GetContractCostPerSlot(pricePerSlotPerSecond, slotDuration);
}
return result;
}
private DateTime GetContractOnChainSubmittedUtc(IStoragePurchaseContract contract)
{
return Time.Retry<DateTime>(() =>
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
var submitEvent = events.GetStorageRequests().SingleOrDefault(e => e.RequestId.ToHex(false) == contract.PurchaseId);
if (submitEvent == null)
{
// We're too early.
throw new TimeoutException(nameof(GetContractOnChainSubmittedUtc) + "StorageRequest not found on-chain.");
}
return submitEvent.Block.Utc;
}, nameof(GetContractOnChainSubmittedUtc));
}
private TestToken GetContractCostPerSlot(TestToken pricePerSlotPerSecond, TimeSpan slotDuration)
{
return pricePerSlotPerSecond * (int)slotDuration.TotalSeconds;
}
protected void AssertContractSlotsAreFilledByHosts(IStoragePurchaseContract contract, ICodexNodeGroup hosts)
{
var activeHosts = new Dictionary<int, SlotFill>();
Time.Retry(() =>
{
var fills = GetOnChainSlotFills(hosts, contract.PurchaseId);
foreach (var fill in fills)
{
var index = (int)fill.SlotFilledEvent.SlotIndex;
if (!activeHosts.ContainsKey(index))
{
activeHosts.Add(index, fill);
}
}
if (activeHosts.Count != contract.Purchase.MinRequiredNumberOfNodes) throw new Exception("Not all slots were filled...");
}, nameof(AssertContractSlotsAreFilledByHosts));
}
protected void AssertContractIsOnChain(IStoragePurchaseContract contract)
{
AssertOnChainEvents(events =>
{
var onChainRequests = events.GetStorageRequests();
if (onChainRequests.Any(r => r.Id == contract.PurchaseId)) return;
throw new Exception($"OnChain request {contract.PurchaseId} not found...");
}, nameof(AssertContractIsOnChain));
}
protected void AssertOnChainEvents(Action<ICodexContractsEvents> onEvents, string description)
{
Time.Retry(() =>
{
var events = GetContracts().GetEvents(GetTestRunTimeRange());
onEvents(events);
}, description);
}
public class SlotFill
{
public SlotFill(SlotFilledEventDTO slotFilledEvent, ICodexNode host)
{
SlotFilledEvent = slotFilledEvent;
Host = host;
}
public SlotFilledEventDTO SlotFilledEvent { get; }
public ICodexNode Host { get; }
}
private class MarketplaceHandle
{
public MarketplaceHandle(IGethNode geth, ICodexContracts contracts)
{
Geth = geth;
Contracts = contracts;
}
public IGethNode Geth { get; }
public ICodexContracts Contracts { get; }
}
}
}
@@ -1,83 +0,0 @@
using CodexContractsPlugin;
using CodexPlugin;
using GethPlugin;
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 pricePerSlotPerSecond = 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(GetContracts()));
// todo:
//AssertClientHasPaidForContract(pricePerSlotPerSecond, client, request, hosts);
//AssertHostsWerePaidForContract(pricePerSlotPerSecond, request, hosts);
//AssertHostsCollateralsAreUnchanged(hosts);
}
private void All(IStoragePurchaseContract[] requests, Action<IStoragePurchaseContract> 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),
PricePerSlotPerSecond = pricePerSlotPerSecond,
ProofProbability = 20,
RequiredCollateral = 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);
}
}
}
@@ -1,62 +0,0 @@
using CodexPlugin;
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
{
[TestFixture]
public class BasicInfoTests : CodexDistTest
{
[Test]
public void QuotaTest()
{
var size = 3.GB();
var node = StartCodex(s => s.WithStorageQuota(size));
var space = node.Space();
Assert.That(space.QuotaMaxBytes, Is.EqualTo(size.SizeInBytes));
}
[Test]
public void Spr()
{
var node = StartCodex();
var info = node.GetDebugInfo();
Assert.That(!string.IsNullOrEmpty(info.Spr));
var spr = node.GetSpr();
Assert.That(!string.IsNullOrEmpty(spr));
Assert.That(info.Spr, Is.EqualTo(spr));
}
[Test]
public void VersionInfo()
{
var node = StartCodex();
var info = node.GetDebugInfo();
Assert.That(!string.IsNullOrEmpty(info.Version.Version));
Assert.That(!string.IsNullOrEmpty(info.Version.Revision));
}
[Test]
public void AnnounceAddress()
{
var node = StartCodex();
var addr = node.Container.GetInternalAddress(CodexContainerRecipe.ListenPortTag);
var info = node.GetDebugInfo();
Assert.That(info.AnnounceAddresses.Count, Is.GreaterThan(0));
// Ideally we'd assert the pod IP is in the announce address, but we can't access it from here.
}
}
}
@@ -1,31 +0,0 @@
using CodexPlugin;
using CodexTests;
using CodexTests.Helpers;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Utils;
namespace CodexReleaseTests.NodeTests
{
[TestFixture]
public class PeerTableTests : AutoBootstrapDistTest
{
[Test]
public void PeerTableCompleteness()
{
var nodes = StartCodex(10);
AssertAllNodesSeeEachOther(nodes.Concat([BootstrapNode!]));
}
private void AssertAllNodesSeeEachOther(IEnumerable<ICodexNode> nodes)
{
var helper = new PeerConnectionTestHelpers(GetTestLog());
helper.AssertFullyConnected(nodes);
}
}
}
-6
View File
@@ -1,6 +0,0 @@
using NUnit.Framework;
[assembly: LevelOfParallelism(1)]
namespace CodexReleaseTests.DataTests
{
}
@@ -53,9 +53,9 @@ namespace CodexTests.BasicTests
[Test]
public void GethBootstrapTest()
{
var boot = StartGethNode(s => s.WithName("boot").IsMiner());
var disconnected = StartGethNode(s => s.WithName("disconnected"));
var follow = StartGethNode(s => s.WithBootstrapNode(boot).WithName("follow"));
var boot = Ci.StartGethNode(s => s.WithName("boot").IsMiner());
var disconnected = Ci.StartGethNode(s => s.WithName("disconnected"));
var follow = Ci.StartGethNode(s => s.WithBootstrapNode(boot).WithName("follow"));
Thread.Sleep(12000);
@@ -28,7 +28,7 @@ namespace CodexTests.BasicTests
plusSizeBytes
);
var geth = StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
var contracts = Ci.StartCodexContracts(geth);
var numberOfHosts = 5;
@@ -107,7 +107,7 @@ namespace CodexTests.BasicTests
AssertStorageRequest(request, purchase, contracts, client);
AssertContractSlot(contracts, request, 0);
purchaseContract.WaitForStorageContractFinished(contracts);
purchaseContract.WaitForStorageContractFinished();
AssertBalance(contracts, client, Is.LessThan(clientInitialBalance), "Buyer was not charged for storage.");
Assert.That(contracts.GetRequestState(request), Is.EqualTo(RequestState.Finished));
@@ -1,16 +1,26 @@
using CodexPlugin;
using CodexTests;
using FileUtils;
using NUnit.Framework;
using System.Diagnostics;
using Utils;
namespace CodexReleaseTests.DataTests
namespace CodexTests.BasicTests
{
public class InterruptUploadTest : CodexDistTest
[TestFixture]
public class OneClientTests : CodexDistTest
{
[Test]
public void UploadInterruptTest()
public void OneClientTest()
{
var node = StartCodex();
PerformOneClientTest(node);
LogNodeStatus(node);
}
[Test]
public void InterruptUploadTest()
{
var nodes = StartCodex(10);
@@ -18,8 +28,6 @@ namespace CodexReleaseTests.DataTests
Task.WaitAll(tasks.ToArray());
Assert.That(tasks.Select(t => t.Result).All(r => r == true));
WaitAndCheckNodesStaysAlive(TimeSpan.FromMinutes(2), nodes);
}
private bool RunInterruptUploadTest(ICodexNode node)
@@ -43,5 +51,16 @@ namespace CodexReleaseTests.DataTests
var filePath = file.Filename;
return Process.Start("curl", $"-X POST {codexUrl} -H \"Content-Type: application/octet-stream\" -T {filePath}");
}
private void PerformOneClientTest(ICodexNode primary)
{
var testFile = GenerateTestFile(1.MB());
var contentId = primary.UploadFile(testFile);
var downloadedFile = primary.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
}
}
}
@@ -0,0 +1,54 @@
using CodexPlugin;
using NUnit.Framework;
using Utils;
namespace CodexTests.BasicTests
{
[TestFixture]
public class ThreeClientTest : AutoBootstrapDistTest
{
[Test]
public void ThreeClient()
{
var primary = StartCodex();
var secondary = StartCodex();
var testFile = GenerateTestFile(10.MB());
var contentId = primary.UploadFile(testFile);
var downloadedFile = secondary.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
}
[Test]
public void DownloadingUnknownCidDoesNotCauseCrash()
{
var node = StartCodex(2).First();
var unknownCid = new ContentId("zDvZRwzkzHsok3Z8yMoiXE9EDBFwgr8WygB8s4ddcLzzSwwXAxLZ");
try
{
node.DownloadContent(unknownCid);
}
catch (Exception ex)
{
if (!ex.Message.StartsWith("Retry 'DownloadFile' timed out"))
{
throw;
}
}
// Check that the node stays alive for at least another 5 minutes.
var start = DateTime.UtcNow;
while ((DateTime.UtcNow - start) < TimeSpan.FromMinutes(5))
{
Thread.Sleep(5000);
var info = node.GetDebugInfo();
Assert.That(!string.IsNullOrEmpty(info.Id));
}
}
}
}
@@ -0,0 +1,52 @@
using CodexPlugin;
using NUnit.Framework;
using Utils;
namespace CodexTests.BasicTests
{
[TestFixture]
public class TwoClientTests : CodexDistTest
{
[Test]
public void TwoClientTest()
{
var uploader = StartCodex(s => s.WithName("Uploader"));
var downloader = StartCodex(s => s.WithName("Downloader").WithBootstrapNode(uploader));
PerformTwoClientTest(uploader, downloader);
}
[Test]
public void TwoClientsTwoLocationsTest()
{
var locations = Ci.GetKnownLocations();
if (locations.NumberOfLocations < 2)
{
Assert.Inconclusive("Two-locations test requires 2 nodes to be available in the cluster.");
return;
}
var uploader = Ci.StartCodexNode(s => s.WithName("Uploader").At(locations.Get(0)));
var downloader = Ci.StartCodexNode(s => s.WithName("Downloader").WithBootstrapNode(uploader).At(locations.Get(1)));
PerformTwoClientTest(uploader, downloader);
}
private void PerformTwoClientTest(ICodexNode uploader, ICodexNode downloader)
{
PerformTwoClientTest(uploader, downloader, 10.MB());
}
private void PerformTwoClientTest(ICodexNode uploader, ICodexNode downloader, ByteSize size)
{
var testFile = GenerateTestFile(size);
var contentId = uploader.UploadFile(testFile);
var downloadedFile = downloader.DownloadContent(contentId);
testFile.AssertIsEqual(downloadedFile);
CheckLogForErrors(uploader, downloader);
}
}
}
@@ -1,5 +1,4 @@
using BlockchainUtils;
using CodexContractsPlugin;
using CodexContractsPlugin;
using CodexNetDeployer;
using CodexPlugin;
using CodexPlugin.OverwatchSupport;
@@ -8,21 +7,18 @@ using Core;
using DistTestCore;
using DistTestCore.Helpers;
using DistTestCore.Logs;
using GethPlugin;
using Logging;
using MetricsPlugin;
using Newtonsoft.Json;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using OverwatchTranscript;
using Utils;
namespace CodexTests
{
public class CodexDistTest : DistTest
{
private static readonly Dictionary<TestLifecycle, CodexTranscriptWriter> writers = new Dictionary<TestLifecycle, CodexTranscriptWriter>();
private static readonly Dictionary<TestLifecycle, BlockCache> blockCaches = new Dictionary<TestLifecycle, BlockCache>();
public CodexDistTest()
{
@@ -77,11 +73,6 @@ namespace CodexTests
return group;
}
public IGethNode StartGethNode(Action<IGethSetup> setup)
{
return Ci.StartGethNode(GetBlockCache(), setup);
}
public PeerConnectionTestHelpers CreatePeerConnectionTestHelpers()
{
return new PeerConnectionTestHelpers(GetTestLog());
@@ -108,7 +99,7 @@ namespace CodexTests
var log = Ci.DownloadLog(node);
log.AssertLogDoesNotContain("Block validation failed");
log.AssertLogDoesNotContainLinesStartingWith("ERR ");
log.AssertLogDoesNotContain("ERR ");
}
public void LogNodeStatus(ICodexNode node, IMetricsAccess? metrics = null)
@@ -117,45 +108,21 @@ namespace CodexTests
GetBasicNodeStatus(node));
}
public void WaitAndCheckNodesStaysAlive(TimeSpan duration, ICodexNodeGroup nodes)
{
WaitAndCheckNodesStaysAlive(duration, nodes.ToArray());
}
public void WaitAndCheckNodesStaysAlive(TimeSpan duration, params ICodexNode[] nodes)
{
var start = DateTime.UtcNow;
while ((DateTime.UtcNow - start) < duration)
{
Thread.Sleep(5000);
foreach (var node in nodes)
{
var info = node.GetDebugInfo();
Assert.That(!string.IsNullOrEmpty(info.Id));
}
}
}
public void AssertNodesContainFile(ContentId cid, ICodexNodeGroup nodes)
{
AssertNodesContainFile(cid, nodes.ToArray());
}
public void AssertNodesContainFile(ContentId cid, params ICodexNode[] nodes)
{
foreach (var node in nodes)
{
var localDatasets = node.LocalFiles();
CollectionAssert.Contains(localDatasets.Content.Select(c => c.Cid), cid);
}
}
private string GetBasicNodeStatus(ICodexNode node)
{
return JsonConvert.SerializeObject(node.GetDebugInfo(), Formatting.Indented) + Environment.NewLine +
node.Space().ToString() + Environment.NewLine;
}
// Disabled for now: Makes huge log files!
//private string GetNodeMetrics(IMetricsAccess? metrics)
//{
// if (metrics == null) return "No metrics enabled";
// var m = metrics.GetAllMetrics();
// if (m == null) return "No metrics received";
// return m.AsCsv();
//}
protected virtual void OnCodexSetup(ICodexSetup setup)
{
}
@@ -223,16 +190,6 @@ namespace CodexTests
if (!outputFile.EndsWith(".owts")) outputFile += ".owts";
return outputFile;
}
private BlockCache GetBlockCache()
{
var lifecycle = Get();
if (!blockCaches.ContainsKey(lifecycle))
{
blockCaches[lifecycle] = new BlockCache();
}
return blockCaches[lifecycle];
}
}
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
@@ -20,7 +20,7 @@ namespace CodexTests.DownloadConnectivityTests
[Test]
public void MarketplaceDoesNotInterfereWithPeerDownload()
{
var geth = StartGethNode(s => s.IsMiner());
var geth = Ci.StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth);
var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), 1000.TstWei())));
@@ -4,7 +4,7 @@ using Utils;
namespace CodexTests.DownloadConnectivityTests
{
[TestFixture]
public class DetectBlockRetransmitTest : AutoBootstrapDistTest
public class SwarmTests : AutoBootstrapDistTest
{
[Test]
[Combinatorial]
@@ -29,7 +29,7 @@ namespace CodexTests.PeerDiscoveryTests
[Test]
public void MarketplaceDoesNotInterfereWithPeerDiscovery()
{
var geth = StartGethNode(s => s.IsMiner());
var geth = Ci.StartGethNode(s => s.IsMiner());
var contracts = Ci.StartCodexContracts(geth);
var nodes = StartCodex(2, s => s.EnableMarketplace(geth, contracts, m => m
.WithInitial(10.Eth(), 1000.TstWei())));
@@ -30,7 +30,7 @@ namespace CodexTests.UtilityTests
[Ignore("Used to debug testnet bots.")]
public void BotRewardTest()
{
var geth = StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
var geth = Ci.StartGethNode(s => s.IsMiner().WithName("disttest-geth"));
var contracts = Ci.StartCodexContracts(geth);
var gethInfo = CreateGethInfo(geth, contracts);
@@ -45,7 +45,7 @@ namespace CodexTests.UtilityTests
var purchaseContract = ClientPurchasesStorage(client);
purchaseContract.WaitForStorageContractStarted();
purchaseContract.WaitForStorageContractFinished(contracts);
purchaseContract.WaitForStorageContractFinished();
Thread.Sleep(rewarderInterval * 3);
apiCalls.Stop();
@@ -23,21 +23,5 @@ namespace DistTestCore
}
CollectionAssert.IsEmpty(errors);
}
public static void AssertLogDoesNotContainLinesStartingWith(this IDownloadedLog log, params string[] unexpectedStrings)
{
var errors = new List<string>();
log.IterateLines(line =>
{
foreach (var str in unexpectedStrings)
{
if (line.StartsWith(str))
{
errors.Add($"Found '{str}' at start of line '{line}'.");
}
}
});
CollectionAssert.IsEmpty(errors);
}
}
}
+2 -13
View File
@@ -55,23 +55,12 @@ namespace DistTestCore
private static string FormatArguments(TestContext.TestAdapter test)
{
if (test.Arguments == null || !test.Arguments.Any()) return "";
return $"[{string.Join(',', test.Arguments.Select(FormatArgument).ToArray())}]";
}
private static string FormatArgument(object? obj)
{
if (obj == null) return "";
var str = obj.ToString();
if (string.IsNullOrEmpty(str)) return "";
return ReplaceInvalidCharacters(str);
return $"[{string.Join(',', test.Arguments)}]";
}
private static string ReplaceInvalidCharacters(string name)
{
return name
.Replace(":", "_")
.Replace("/", "_")
.Replace("\\", "_");
return name.Replace(":", "_");
}
private static string DetermineFolder(LogConfig config, DateTime start)
-6
View File
@@ -1,6 +0,0 @@
using NUnit.Framework;
[assembly: LevelOfParallelism(1)]
namespace CodexTests
{
}
@@ -1,7 +1,7 @@
using BlockchainUtils;
using Logging;
using Logging;
using Moq;
using NethereumWorkflow;
using NethereumWorkflow.BlockUtils;
using NUnit.Framework;
namespace FrameworkTests.NethereumWorkflow

Some files were not shown because too many files have changed in this diff Show More