Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa58a7aee4 | ||
|
|
7b45a1171e | ||
|
|
b1cb61b51c | ||
|
|
768bfcc8eb | ||
|
|
8af91ea74b | ||
|
|
c785df3adf | ||
|
|
bb3f0fccd0 |
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
<Application x:Class="DevconBoothImages.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:DevconBoothImages"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Windows;
|
||||
|
||||
namespace DevconBoothImages
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,77 @@
|
||||
using CodexOpenApi;
|
||||
using IdentityModel.Client;
|
||||
using System.Net.Http;
|
||||
using System.Windows;
|
||||
using Utils;
|
||||
|
||||
namespace DevconBoothImages
|
||||
{
|
||||
public class Codexes
|
||||
{
|
||||
public Codexes(CodexApi local, CodexApi testnet)
|
||||
{
|
||||
Local = local;
|
||||
Testnet = testnet;
|
||||
}
|
||||
|
||||
public CodexApi Local { get; }
|
||||
public CodexApi Testnet { get; }
|
||||
}
|
||||
|
||||
public class CodexWrapper
|
||||
{
|
||||
public async Task<Codexes> GetCodexes()
|
||||
{
|
||||
var config = new Configuration();
|
||||
return new Codexes(
|
||||
await GetCodexWithPort(config.CodexLocalEndpoint),
|
||||
await GetCodexWithoutPort(config.CodexPublicEndpoint, config.AuthUser, config.AuthPw)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<CodexApi> GetCodexWithPort(string endpoint)
|
||||
{
|
||||
var splitIndex = endpoint.LastIndexOf(':');
|
||||
var host = endpoint.Substring(0, splitIndex);
|
||||
var port = Convert.ToInt32(endpoint.Substring(splitIndex + 1));
|
||||
|
||||
var address = new Address(
|
||||
host: host,
|
||||
port: port
|
||||
);
|
||||
|
||||
var client = new HttpClient();
|
||||
var codex = new CodexApi(client);
|
||||
codex.BaseUrl = $"{address.Host}:{address.Port}/api/codex/v1";
|
||||
|
||||
await CheckCodex(codex, endpoint);
|
||||
return codex;
|
||||
}
|
||||
|
||||
private async Task<CodexApi> GetCodexWithoutPort(string endpoint, string user, string pw)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
client.SetBasicAuthentication(user, pw);
|
||||
var codex = new CodexApi(client);
|
||||
codex.BaseUrl = $"{endpoint}/api/codex/v1";
|
||||
|
||||
await CheckCodex(codex, endpoint);
|
||||
return codex;
|
||||
}
|
||||
|
||||
private async Task CheckCodex(CodexApi codex, string endpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = await codex.GetDebugInfoAsync();
|
||||
if (string.IsNullOrEmpty(info.Id)) throw new Exception("Failed to fetch Codex node id");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Failed to connect to codex '{endpoint}': {ex}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace DevconBoothImages
|
||||
{
|
||||
public class Configuration
|
||||
{
|
||||
public string CodexLocalEndpoint { get; } = "http://localhost:8080";
|
||||
public string CodexPublicEndpoint { get; } = "https://api.testnet.codex.storage/storage/node-9";
|
||||
|
||||
public string AuthUser { get; } = "";
|
||||
public string AuthPw { get; } = "";
|
||||
public string LocalNodeBootstrapInfo { get; } = "";
|
||||
public string WorkingDir { get; } = "D:\\DevconBoothApp";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ProjectPlugins\CodexPlugin\CodexPlugin.csproj" />
|
||||
<ProjectReference Include="..\Tools\AutoClient\AutoClient.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Update="App.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="MainWindow.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,38 @@
|
||||
<Window x:Class="DevconBoothImages.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:DevconBoothImages"
|
||||
mc:Ignorable="d"
|
||||
Title="CodexBoothImages" Height="450" Width="800" WindowState="Maximized">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Column="0" Name="Img" />
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock Text="Instructions:"/>
|
||||
<Image Name="ImgInstructions" />
|
||||
<TextBlock Text="Local CID:"/>
|
||||
<Image Name="ImgLocalCid" />
|
||||
<TextBlock Text="TestNet CID:"/>
|
||||
<Image Name="ImgTestnetCid" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="1">
|
||||
<TextBlock Name="Txt" HorizontalAlignment="Center" />
|
||||
<Button Content="Check Codex connections" Click="Button_Click_2" />
|
||||
<Button Content="Generate image -> Upload to Codex -> Put CID info in clipboard" Padding="10" Click="Button_Click"/>
|
||||
<Button Content="Put last CID info in clipboard" Padding="10" Click="Button_Click_1"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,160 @@
|
||||
using AutoClient;
|
||||
using CodexOpenApi;
|
||||
using Logging;
|
||||
using QRCoder;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace DevconBoothImages
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly Configuration config = new Configuration();
|
||||
private readonly CodexWrapper codexWrapper = new CodexWrapper();
|
||||
private readonly ImageGenerator imageGenerator = new ImageGenerator(new NullLog());
|
||||
private string currentLocalCid = string.Empty;
|
||||
private string currentPublicCid = string.Empty;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
|
||||
}
|
||||
|
||||
private void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
MessageBox.Show("Unhandled exception: " + e.Exception);
|
||||
}
|
||||
|
||||
private async void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// image
|
||||
Log("Getting image...");
|
||||
var file = await imageGenerator.Generate();
|
||||
var filename = Path.Combine(config.WorkingDir, file);
|
||||
File.Copy(file, filename);
|
||||
|
||||
var bmp = new BitmapImage();
|
||||
bmp.BeginInit();
|
||||
bmp.UriSource = new Uri(filename);
|
||||
bmp.EndInit();
|
||||
Img.Source = bmp;
|
||||
|
||||
Log("Uploading...");
|
||||
// upload
|
||||
await UploadToCodexes(filename, file);
|
||||
|
||||
// clipboard info
|
||||
InfoToClipboard();
|
||||
}
|
||||
|
||||
private BitmapImage GenerateQr(string text)
|
||||
{
|
||||
using (QRCodeGenerator qrGenerator = new QRCodeGenerator())
|
||||
using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(text, QRCodeGenerator.ECCLevel.Default))
|
||||
using (PngByteQRCode qrCode = new PngByteQRCode(qrCodeData))
|
||||
{
|
||||
byte[] qrCodeImage = qrCode.GetGraphic(7);
|
||||
using (var ms = new MemoryStream(qrCodeImage))
|
||||
{
|
||||
var img = Image.FromStream(ms);
|
||||
using (var ms2 = new MemoryStream())
|
||||
{
|
||||
img.Save(ms2, ImageFormat.Png);
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = ms2;
|
||||
bitmapImage.EndInit();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void Button_Click_1(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InfoToClipboard();
|
||||
}
|
||||
|
||||
private async void Button_Click_2(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// check codexes
|
||||
Log("Checking Codex connections...");
|
||||
await codexWrapper.GetCodexes();
|
||||
Log("Connections OK");
|
||||
}
|
||||
|
||||
private async Task UploadToCodexes(string filename, string shortName)
|
||||
{
|
||||
var codexes = await codexWrapper.GetCodexes();
|
||||
try
|
||||
{
|
||||
currentLocalCid = await UploadFile(filename, shortName, codexes.Local);
|
||||
currentPublicCid = await UploadFile(filename, shortName, codexes.Testnet);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Upload failed: " + ex);
|
||||
}
|
||||
Log($"Generated CIDs");
|
||||
}
|
||||
|
||||
private async Task<string> UploadFile(string filename, string shortName, CodexApi codex)
|
||||
{
|
||||
using (var fileStream = File.OpenRead(filename))
|
||||
{
|
||||
var response = await codex.UploadAsync(
|
||||
"image/jpeg",
|
||||
$"attachment; filename=\"{shortName}\"",
|
||||
fileStream);
|
||||
|
||||
if (string.IsNullOrEmpty(response) ||
|
||||
response.ToLowerInvariant().Contains("unable to store block"))
|
||||
{
|
||||
throw new Exception("Unable to upload image. Response empty or error message.");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private void InfoToClipboard()
|
||||
{
|
||||
Clipboard.Clear();
|
||||
if (string.IsNullOrEmpty(currentLocalCid) || string.IsNullOrEmpty(currentPublicCid))
|
||||
{
|
||||
Log("No CIDs were generated! Clipboard cleared.");
|
||||
return;
|
||||
}
|
||||
|
||||
var nl = Environment.NewLine;
|
||||
var msg =
|
||||
$"** Codex@Devcon 💻 Raspberry Pi Challenge **{nl}" +
|
||||
$"📢 A new image is available. Download it and bring it to the booth!{nl}" +
|
||||
$"Public Testnet CID: `{currentPublicCid}`{nl}" +
|
||||
$"Local Devcon network CID: `{currentLocalCid}`{nl}" +
|
||||
$"Setup instructions: [Here](https://docs.codex.storage){nl}" +
|
||||
$"Local Devcon network information: [Here](https://github.com/codex-storage/codex-testnet-starter/blob/master/SETUP_DEVCONNET.md)";
|
||||
|
||||
Clipboard.SetText(msg);
|
||||
Log("CID info copied to clipboard. Paste it in Discord plz!");
|
||||
|
||||
ImgLocalCid.Source = GenerateQr(currentLocalCid);
|
||||
ImgTestnetCid.Source = GenerateQr(currentPublicCid);
|
||||
ImgInstructions.Source = GenerateQr("https://github.com/codex-storage/codex-testnet-starter/blob/master/SETUP_DEVCONNET.md");
|
||||
}
|
||||
|
||||
private void Log(string v)
|
||||
{
|
||||
Txt.Text = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,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);
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
namespace BlockchainUtils
|
||||
namespace NethereumWorkflow.BlockUtils
|
||||
{
|
||||
public class BlockCache
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace BlockchainUtils
|
||||
namespace NethereumWorkflow.BlockUtils
|
||||
{
|
||||
public class BlockTimeEntry
|
||||
{
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Logging;
|
||||
|
||||
namespace BlockchainUtils
|
||||
namespace NethereumWorkflow.BlockUtils
|
||||
{
|
||||
public class BlockTimeFinder
|
||||
{
|
||||
+1
-7
@@ -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
-1
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = "09-53-C3-A6-31-A5-0C-8B-53-1C-3D-C7-2B-1E-85-C7-17-60-54-43-01-C4-49-4E-D9-68-35-7D-F7-41-13-B5";
|
||||
private const string OpenApiFilePath = "/codex/openapi.yaml";
|
||||
private const string DisableEnvironmentVariable = "CODEXPLUGIN_DISABLE_APICHECK";
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,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.
|
||||
|
||||
@@ -14,23 +14,13 @@ 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 +120,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);
|
||||
@@ -158,7 +140,7 @@ namespace CodexPlugin
|
||||
|
||||
public ContentId UploadFile(TrackedFile file, Action<Failure> onFailure)
|
||||
{
|
||||
return UploadFile(file, "application/octet-stream", $"attachment; filename=\"{Path.GetFileName(file.Filename)}\"", onFailure);
|
||||
return UploadFile(file, "application/x-binary", $"attachment; filename=\"{file.Filename}\"", onFailure);
|
||||
}
|
||||
|
||||
public ContentId UploadFile(TrackedFile file, string contentType, string contentDisposition, Action<Failure> onFailure)
|
||||
@@ -191,20 +173,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 +189,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 +300,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.");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
[assembly: LevelOfParallelism(1)]
|
||||
namespace CodexReleaseTests.DataTests
|
||||
{
|
||||
}
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+2
-2
@@ -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));
|
||||
+25
-6
@@ -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)]
|
||||
+1
-1
@@ -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())));
|
||||
+1
-1
@@ -4,7 +4,7 @@ using Utils;
|
||||
namespace CodexTests.DownloadConnectivityTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DetectBlockRetransmitTest : AutoBootstrapDistTest
|
||||
public class SwarmTests : AutoBootstrapDistTest
|
||||
{
|
||||
[Test]
|
||||
[Combinatorial]
|
||||
+1
-1
@@ -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())));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user