diff --git a/CodexPlugin/CodexPlugin.csproj b/CodexPlugin/CodexPlugin.csproj index 4619e26..a00f24f 100644 --- a/CodexPlugin/CodexPlugin.csproj +++ b/CodexPlugin/CodexPlugin.csproj @@ -14,6 +14,7 @@ + diff --git a/CodexPlugin/GethStarter.cs b/CodexPlugin/GethStarter.cs deleted file mode 100644 index f6381b3..0000000 --- a/CodexPlugin/GethStarter.cs +++ /dev/null @@ -1,88 +0,0 @@ -//using DistTestCore.Marketplace; - -//namespace CodexPlugin -//{ -// public class GethStarter : BaseStarter -// { -// private readonly MarketplaceNetworkCache marketplaceNetworkCache; -// private readonly GethCompanionNodeStarter companionNodeStarter; - -// public GethStarter(TestLifecycle lifecycle) -// : base(lifecycle) -// { -// marketplaceNetworkCache = new MarketplaceNetworkCache( -// new GethBootstrapNodeStarter(lifecycle), -// new CodexContractsStarter(lifecycle)); -// companionNodeStarter = new GethCompanionNodeStarter(lifecycle); -// } - -// public GethStartResult BringOnlineMarketplaceFor(CodexSetup codexSetup) -// { -// if (codexSetup.MarketplaceConfig == null) return CreateMarketplaceUnavailableResult(); - -// var marketplaceNetwork = marketplaceNetworkCache.Get(); -// var companionNode = StartCompanionNode(codexSetup, marketplaceNetwork); - -// LogStart("Setting up initial balance..."); -// TransferInitialBalance(marketplaceNetwork, codexSetup.MarketplaceConfig, companionNode); -// LogEnd($"Initial balance of {codexSetup.MarketplaceConfig.InitialTestTokens} set for {codexSetup.NumberOfNodes} nodes."); - -// return CreateGethStartResult(marketplaceNetwork, companionNode); -// } - -// private void TransferInitialBalance(MarketplaceNetwork marketplaceNetwork, MarketplaceInitialConfig marketplaceConfig, GethCompanionNodeInfo companionNode) -// { -// if (marketplaceConfig.InitialTestTokens.Amount == 0) return; - -// var interaction = marketplaceNetwork.StartInteraction(lifecycle); -// var tokenAddress = marketplaceNetwork.Marketplace.TokenAddress; - -// var accounts = companionNode.Accounts.Select(a => a.Account).ToArray(); -// interaction.MintTestTokens(accounts, marketplaceConfig.InitialTestTokens.Amount, tokenAddress); -// } - -// private GethStartResult CreateGethStartResult(MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode) -// { -// return new GethStartResult(CreateMarketplaceAccessFactory(marketplaceNetwork), marketplaceNetwork, companionNode); -// } - -// private GethStartResult CreateMarketplaceUnavailableResult() -// { -// return new GethStartResult(new MarketplaceUnavailableAccessFactory(), null!, null!); -// } - -// private IMarketplaceAccessFactory CreateMarketplaceAccessFactory(MarketplaceNetwork marketplaceNetwork) -// { -// return new GethMarketplaceAccessFactory(lifecycle, marketplaceNetwork); -// } - -// private GethCompanionNodeInfo StartCompanionNode(CodexSetup codexSetup, MarketplaceNetwork marketplaceNetwork) -// { -// return companionNodeStarter.StartCompanionNodeFor(codexSetup, marketplaceNetwork); -// } -// } - -// public class MarketplaceNetworkCache -// { -// private readonly GethBootstrapNodeStarter bootstrapNodeStarter; -// private readonly CodexContractsStarter codexContractsStarter; -// private MarketplaceNetwork? network; - -// public MarketplaceNetworkCache(GethBootstrapNodeStarter bootstrapNodeStarter, CodexContractsStarter codexContractsStarter) -// { -// this.bootstrapNodeStarter = bootstrapNodeStarter; -// this.codexContractsStarter = codexContractsStarter; -// } - -// public MarketplaceNetwork Get() -// { -// if (network == null) -// { -// var bootstrapInfo = bootstrapNodeStarter.StartGethBootstrapNode(); -// var marketplaceInfo = codexContractsStarter.Start(bootstrapInfo); -// network = new MarketplaceNetwork(bootstrapInfo, marketplaceInfo ); -// } -// return network; -// } -// } -//} diff --git a/CodexPlugin/Marketplace/GethBootstrapNodeInfo.cs b/CodexPlugin/Marketplace/GethBootstrapNodeInfo.cs deleted file mode 100644 index 3e84dec..0000000 --- a/CodexPlugin/Marketplace/GethBootstrapNodeInfo.cs +++ /dev/null @@ -1,42 +0,0 @@ -//using KubernetesWorkflow; -//using NethereumWorkflow; - -//namespace DistTestCore.Marketplace -//{ -// public class GethBootstrapNodeInfo -// { -// public GethBootstrapNodeInfo(RunningContainers runningContainers, AllGethAccounts allAccounts, string pubKey, Port discoveryPort) -// { -// RunningContainers = runningContainers; -// AllAccounts = allAccounts; -// Account = allAccounts.Accounts[0]; -// PubKey = pubKey; -// DiscoveryPort = discoveryPort; -// } - -// public RunningContainers RunningContainers { get; } -// public AllGethAccounts AllAccounts { get; } -// public GethAccount Account { get; } -// public string PubKey { get; } -// public Port DiscoveryPort { get; } - -// public NethereumInteraction StartInteraction(TestLifecycle lifecycle) -// { -// var address = lifecycle.Configuration.GetAddress(RunningContainers.Containers[0]); -// var account = Account; - -// var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, account.PrivateKey); -// return creator.CreateWorkflow(); -// } -// } - -// public class AllGethAccounts -// { -// public GethAccount[] Accounts { get; } - -// public AllGethAccounts(GethAccount[] accounts) -// { -// Accounts = accounts; -// } -// } -//} diff --git a/CodexPlugin/Marketplace/GethBootstrapNodeStarter.cs b/CodexPlugin/Marketplace/GethBootstrapNodeStarter.cs deleted file mode 100644 index b94d041..0000000 --- a/CodexPlugin/Marketplace/GethBootstrapNodeStarter.cs +++ /dev/null @@ -1,40 +0,0 @@ -//using KubernetesWorkflow; - -//namespace DistTestCore.Marketplace -//{ -// public class GethBootstrapNodeStarter : BaseStarter -// { -// public GethBootstrapNodeStarter(TestLifecycle lifecycle) -// : base(lifecycle) -// { -// } - -// public GethBootstrapNodeInfo StartGethBootstrapNode() -// { -// LogStart("Starting Geth bootstrap node..."); -// var startupConfig = CreateBootstrapStartupConfig(); - -// var workflow = lifecycle.WorkflowCreator.CreateWorkflow(); -// var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), startupConfig); -// if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure."); -// var bootstrapContainer = containers.Containers[0]; - -// var extractor = new ContainerInfoExtractor(lifecycle.Log, workflow, bootstrapContainer); -// var accounts = extractor.ExtractAccounts(); -// var pubKey = extractor.ExtractPubKey(); -// var discoveryPort = bootstrapContainer.Recipe.GetPortByTag(GethContainerRecipe.DiscoveryPortTag); -// var result = new GethBootstrapNodeInfo(containers, accounts, pubKey, discoveryPort); - -// LogEnd($"Geth bootstrap node started with account '{result.Account.Account}'"); - -// return result; -// } - -// private StartupConfig CreateBootstrapStartupConfig() -// { -// var config = new StartupConfig(); -// config.Add(new GethStartupConfig(true, null!, 0, 0)); -// return config; -// } -// } -//} diff --git a/CodexPlugin/Marketplace/GethCompanionNodeInfo.cs b/CodexPlugin/Marketplace/GethCompanionNodeInfo.cs deleted file mode 100644 index 30f2e78..0000000 --- a/CodexPlugin/Marketplace/GethCompanionNodeInfo.cs +++ /dev/null @@ -1,38 +0,0 @@ -//using KubernetesWorkflow; -//using NethereumWorkflow; - -//namespace DistTestCore.Marketplace -//{ -// public class GethCompanionNodeInfo -// { -// public GethCompanionNodeInfo(RunningContainer runningContainer, GethAccount[] accounts) -// { -// RunningContainer = runningContainer; -// Accounts = accounts; -// } - -// public RunningContainer RunningContainer { get; } -// public GethAccount[] Accounts { get; } - -// public NethereumInteraction StartInteraction(TestLifecycle lifecycle, GethAccount account) -// { -// var address = lifecycle.Configuration.GetAddress(RunningContainer); -// var privateKey = account.PrivateKey; - -// var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, privateKey); -// return creator.CreateWorkflow(); -// } -// } - -// public class GethAccount -// { -// public GethAccount(string account, string privateKey) -// { -// Account = account; -// PrivateKey = privateKey; -// } - -// public string Account { get; } -// public string PrivateKey { get; } -// } -//} diff --git a/CodexPlugin/Marketplace/GethCompanionNodeStarter.cs b/CodexPlugin/Marketplace/GethCompanionNodeStarter.cs deleted file mode 100644 index 9c8a303..0000000 --- a/CodexPlugin/Marketplace/GethCompanionNodeStarter.cs +++ /dev/null @@ -1,77 +0,0 @@ -//using KubernetesWorkflow; -//using Utils; - -//namespace DistTestCore.Marketplace -//{ -// public class GethCompanionNodeStarter : BaseStarter -// { -// private int companionAccountIndex = 0; - -// public GethCompanionNodeStarter(TestLifecycle lifecycle) -// : base(lifecycle) -// { -// } - -// public GethCompanionNodeInfo StartCompanionNodeFor(CodexSetup codexSetup, MarketplaceNetwork marketplace) -// { -// LogStart($"Initializing companion for {codexSetup.NumberOfNodes} Codex nodes."); - -// var config = CreateCompanionNodeStartupConfig(marketplace.Bootstrap, codexSetup.NumberOfNodes); - -// var workflow = lifecycle.WorkflowCreator.CreateWorkflow(); -// var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), CreateStartupConfig(config)); -// if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected one Geth companion node to be created. Test infra failure."); -// var container = containers.Containers[0]; - -// var node = CreateCompanionInfo(container, marketplace, config); -// EnsureCompanionNodeIsSynced(node, marketplace); - -// LogEnd($"Initialized one companion node for {codexSetup.NumberOfNodes} Codex nodes. Their accounts: [{string.Join(",", node.Accounts.Select(a => a.Account))}]"); -// return node; -// } - -// private GethCompanionNodeInfo CreateCompanionInfo(RunningContainer container, MarketplaceNetwork marketplace, GethStartupConfig config) -// { -// var accounts = ExtractAccounts(marketplace, config); -// return new GethCompanionNodeInfo(container, accounts); -// } - -// private static GethAccount[] ExtractAccounts(MarketplaceNetwork marketplace, GethStartupConfig config) -// { -// return marketplace.Bootstrap.AllAccounts.Accounts -// .Skip(1 + config.CompanionAccountStartIndex) -// .Take(config.NumberOfCompanionAccounts) -// .ToArray(); -// } - -// private void EnsureCompanionNodeIsSynced(GethCompanionNodeInfo node, MarketplaceNetwork marketplace) -// { -// try -// { -// Time.WaitUntil(() => -// { -// var interaction = node.StartInteraction(lifecycle, node.Accounts.First()); -// return interaction.IsSynced(marketplace.Marketplace.Address, marketplace.Marketplace.Abi); -// }, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(3)); -// } -// catch (Exception e) -// { -// throw new Exception("Geth companion node did not sync within timeout. Test infra failure.", e); -// } -// } - -// private GethStartupConfig CreateCompanionNodeStartupConfig(GethBootstrapNodeInfo bootstrapNode, int numberOfAccounts) -// { -// var config = new GethStartupConfig(false, bootstrapNode, companionAccountIndex, numberOfAccounts); -// companionAccountIndex += numberOfAccounts; -// return config; -// } - -// private StartupConfig CreateStartupConfig(GethStartupConfig gethConfig) -// { -// var config = new StartupConfig(); -// config.Add(gethConfig); -// return config; -// } -// } -//} diff --git a/CodexPlugin/Marketplace/GethContainerRecipe.cs b/CodexPlugin/Marketplace/GethContainerRecipe.cs deleted file mode 100644 index e5b2f9b..0000000 --- a/CodexPlugin/Marketplace/GethContainerRecipe.cs +++ /dev/null @@ -1,73 +0,0 @@ -//using KubernetesWorkflow; - -//namespace DistTestCore.Marketplace -//{ -// public class GethContainerRecipe : DefaultContainerRecipe -// { -// private const string defaultArgs = "--ipcdisable --syncmode full"; - -// public const string HttpPortTag = "http_port"; -// public const string DiscoveryPortTag = "disc_port"; -// public const string AccountsFilename = "accounts.csv"; - -// public override string AppName => "geth"; -// public override string Image => "codexstorage/dist-tests-geth:latest"; - -// protected override void InitializeRecipe(StartupConfig startupConfig) -// { -// var config = startupConfig.Get(); - -// var args = CreateArgs(config); - -// AddEnvVar("GETH_ARGS", args); -// } - -// private string CreateArgs(GethStartupConfig config) -// { -// var discovery = AddInternalPort(tag: DiscoveryPortTag); - -// if (config.IsBootstrapNode) -// { -// return CreateBootstapArgs(discovery); -// } - -// return CreateCompanionArgs(discovery, config); -// } - -// private string CreateBootstapArgs(Port discovery) -// { -// AddEnvVar("ENABLE_MINER", "1"); -// UnlockAccounts(0, 1); -// var exposedPort = AddExposedPort(tag: HttpPortTag); -// return $"--http.port {exposedPort.Number} --port {discovery.Number} --discovery.port {discovery.Number} {defaultArgs}"; -// } - -// private string CreateCompanionArgs(Port discovery, GethStartupConfig config) -// { -// UnlockAccounts( -// config.CompanionAccountStartIndex + 1, -// config.NumberOfCompanionAccounts); - -// var port = AddInternalPort(); -// var authRpc = AddInternalPort(); -// var httpPort = AddExposedPort(tag: HttpPortTag); - -// var bootPubKey = config.BootstrapNode.PubKey; -// var bootIp = config.BootstrapNode.RunningContainers.Containers[0].Pod.PodInfo.Ip; -// var bootPort = config.BootstrapNode.DiscoveryPort.Number; -// var bootstrapArg = $"--bootnodes enode://{bootPubKey}@{bootIp}:{bootPort} --nat=extip:{bootIp}"; - -// return $"--port {port.Number} --discovery.port {discovery.Number} --authrpc.port {authRpc.Number} --http.addr 0.0.0.0 --http.port {httpPort.Number} --ws --ws.addr 0.0.0.0 --ws.port {httpPort.Number} {bootstrapArg} {defaultArgs}"; -// } - -// private void UnlockAccounts(int startIndex, int numberOfAccounts) -// { -// if (startIndex < 0) throw new ArgumentException(); -// if (numberOfAccounts < 1) throw new ArgumentException(); -// if (startIndex + numberOfAccounts > 1000) throw new ArgumentException("Out of accounts!"); - -// AddEnvVar("UNLOCK_START_INDEX", startIndex.ToString()); -// AddEnvVar("UNLOCK_NUMBER", numberOfAccounts.ToString()); -// } -// } -//} diff --git a/CodexPlugin/Marketplace/GethStartResult.cs b/CodexPlugin/Marketplace/GethStartResult.cs deleted file mode 100644 index f9e1048..0000000 --- a/CodexPlugin/Marketplace/GethStartResult.cs +++ /dev/null @@ -1,19 +0,0 @@ -//using Newtonsoft.Json; - -//namespace DistTestCore.Marketplace -//{ -// public class GethStartResult -// { -// public GethStartResult(IMarketplaceAccessFactory marketplaceAccessFactory, MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode) -// { -// MarketplaceAccessFactory = marketplaceAccessFactory; -// MarketplaceNetwork = marketplaceNetwork; -// CompanionNode = companionNode; -// } - -// [JsonIgnore] -// public IMarketplaceAccessFactory MarketplaceAccessFactory { get; } -// public MarketplaceNetwork MarketplaceNetwork { get; } -// public GethCompanionNodeInfo CompanionNode { get; } -// } -//} diff --git a/CodexPlugin/Marketplace/GethStartupConfig.cs b/CodexPlugin/Marketplace/GethStartupConfig.cs deleted file mode 100644 index 67ad0d5..0000000 --- a/CodexPlugin/Marketplace/GethStartupConfig.cs +++ /dev/null @@ -1,18 +0,0 @@ -//namespace DistTestCore.Marketplace -//{ -// public class GethStartupConfig -// { -// public GethStartupConfig(bool isBootstrapNode, GethBootstrapNodeInfo bootstrapNode, int companionAccountStartIndex, int numberOfCompanionAccounts) -// { -// IsBootstrapNode = isBootstrapNode; -// BootstrapNode = bootstrapNode; -// CompanionAccountStartIndex = companionAccountStartIndex; -// NumberOfCompanionAccounts = numberOfCompanionAccounts; -// } - -// public bool IsBootstrapNode { get; } -// public GethBootstrapNodeInfo BootstrapNode { get; } -// public int CompanionAccountStartIndex { get; } -// public int NumberOfCompanionAccounts { get; } -// } -//} diff --git a/Core/CoreInterface.cs b/Core/CoreInterface.cs index 43a66b5..83ea593 100644 --- a/Core/CoreInterface.cs +++ b/Core/CoreInterface.cs @@ -31,6 +31,11 @@ namespace Core return logHandler.DownloadLog(); } + public string ExecuteContainerCommand(IHasContainer containerSource, string command, params string[] args) + { + return ExecuteContainerCommand(containerSource.Container, command, args); + } + public string ExecuteContainerCommand(RunningContainer container, string command, params string[] args) { var workflow = entryPoint.Tools.CreateWorkflow(); diff --git a/GethPlugin/CoreInterfaceExtensions.cs b/GethPlugin/CoreInterfaceExtensions.cs new file mode 100644 index 0000000..6625d58 --- /dev/null +++ b/GethPlugin/CoreInterfaceExtensions.cs @@ -0,0 +1,44 @@ +using Core; +using KubernetesWorkflow; + +namespace GethPlugin +{ + public static class CoreInterfaceExtensions + { + //public static RunningContainers[] StartCodexNodes(this CoreInterface ci, int number, Action setup) + //{ + // return Plugin(ci).StartCodexNodes(number, setup); + //} + + //public static ICodexNodeGroup WrapCodexContainers(this CoreInterface ci, RunningContainers[] containers) + //{ + // return Plugin(ci).WrapCodexContainers(containers); + //} + + //public static IOnlineCodexNode SetupCodexNode(this CoreInterface ci) + //{ + // return ci.SetupCodexNodes(1)[0]; + //} + + //public static IOnlineCodexNode SetupCodexNode(this CoreInterface ci, Action setup) + //{ + // return ci.SetupCodexNodes(1, setup)[0]; + //} + + //public static ICodexNodeGroup SetupCodexNodes(this CoreInterface ci, int number, Action setup) + //{ + // var rc = ci.StartCodexNodes(number, setup); + // return ci.WrapCodexContainers(rc); + //} + + //public static ICodexNodeGroup SetupCodexNodes(this CoreInterface ci, int number) + //{ + // return ci.SetupCodexNodes(number, s => { }); + //} + + //private static CodexPlugin Plugin(CoreInterface ci) + //{ + // return ci.GetPlugin(); + //} + } +} diff --git a/GethPlugin/GethBootstrapNodeInfo.cs b/GethPlugin/GethBootstrapNodeInfo.cs new file mode 100644 index 0000000..8dffbc9 --- /dev/null +++ b/GethPlugin/GethBootstrapNodeInfo.cs @@ -0,0 +1,42 @@ +using KubernetesWorkflow; +using NethereumWorkflow; + +namespace GethPlugin +{ + public class GethBootstrapNodeInfo + { + public GethBootstrapNodeInfo(RunningContainers runningContainers, AllGethAccounts allAccounts, string pubKey, Port discoveryPort) + { + RunningContainers = runningContainers; + AllAccounts = allAccounts; + Account = allAccounts.Accounts[0]; + PubKey = pubKey; + DiscoveryPort = discoveryPort; + } + + public RunningContainers RunningContainers { get; } + public AllGethAccounts AllAccounts { get; } + public GethAccount Account { get; } + public string PubKey { get; } + public Port DiscoveryPort { get; } + + public NethereumInteraction StartInteraction(TestLifecycle lifecycle) + { + var address = lifecycle.Configuration.GetAddress(RunningContainers.Containers[0]); + var account = Account; + + var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, account.PrivateKey); + return creator.CreateWorkflow(); + } + } + + public class AllGethAccounts + { + public GethAccount[] Accounts { get; } + + public AllGethAccounts(GethAccount[] accounts) + { + Accounts = accounts; + } + } +} diff --git a/GethPlugin/GethBootstrapNodeStarter.cs b/GethPlugin/GethBootstrapNodeStarter.cs new file mode 100644 index 0000000..b18292a --- /dev/null +++ b/GethPlugin/GethBootstrapNodeStarter.cs @@ -0,0 +1,35 @@ +using KubernetesWorkflow; + +namespace GethPlugin +{ + public class GethBootstrapNodeStarter + { + public GethBootstrapNodeInfo StartGethBootstrapNode() + { + LogStart("Starting Geth bootstrap node..."); + var startupConfig = CreateBootstrapStartupConfig(); + + var workflow = lifecycle.WorkflowCreator.CreateWorkflow(); + var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), startupConfig); + if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected 1 Geth bootstrap node to be created. Test infra failure."); + var bootstrapContainer = containers.Containers[0]; + + var extractor = new ContainerInfoExtractor(lifecycle.Log, workflow, bootstrapContainer); + var accounts = extractor.ExtractAccounts(); + var pubKey = extractor.ExtractPubKey(); + var discoveryPort = bootstrapContainer.Recipe.GetPortByTag(GethContainerRecipe.DiscoveryPortTag); + var result = new GethBootstrapNodeInfo(containers, accounts, pubKey, discoveryPort); + + LogEnd($"Geth bootstrap node started with account '{result.Account.Account}'"); + + return result; + } + + private StartupConfig CreateBootstrapStartupConfig() + { + var config = new StartupConfig(); + config.Add(new GethStartupConfig(true, null!, 0, 0)); + return config; + } + } +} diff --git a/GethPlugin/GethCompanionNodeInfo.cs b/GethPlugin/GethCompanionNodeInfo.cs new file mode 100644 index 0000000..313271c --- /dev/null +++ b/GethPlugin/GethCompanionNodeInfo.cs @@ -0,0 +1,38 @@ +using KubernetesWorkflow; +using NethereumWorkflow; + +namespace GethPlugin +{ + public class GethCompanionNodeInfo + { + public GethCompanionNodeInfo(RunningContainer runningContainer, GethAccount[] accounts) + { + RunningContainer = runningContainer; + Accounts = accounts; + } + + public RunningContainer RunningContainer { get; } + public GethAccount[] Accounts { get; } + + public NethereumInteraction StartInteraction(TestLifecycle lifecycle, GethAccount account) + { + var address = lifecycle.Configuration.GetAddress(RunningContainer); + var privateKey = account.PrivateKey; + + var creator = new NethereumInteractionCreator(lifecycle.Log, address.Host, address.Port, privateKey); + return creator.CreateWorkflow(); + } + } + + public class GethAccount + { + public GethAccount(string account, string privateKey) + { + Account = account; + PrivateKey = privateKey; + } + + public string Account { get; } + public string PrivateKey { get; } + } +} diff --git a/GethPlugin/GethCompanionNodeStarter.cs b/GethPlugin/GethCompanionNodeStarter.cs new file mode 100644 index 0000000..998e8f2 --- /dev/null +++ b/GethPlugin/GethCompanionNodeStarter.cs @@ -0,0 +1,72 @@ +using KubernetesWorkflow; +using Utils; + +namespace GethPlugin +{ + public class GethCompanionNodeStarter + { + private int companionAccountIndex = 0; + + public GethCompanionNodeInfo StartCompanionNodeFor(CodexSetup codexSetup, MarketplaceNetwork marketplace) + { + LogStart($"Initializing companion for {codexSetup.NumberOfNodes} Codex nodes."); + + var config = CreateCompanionNodeStartupConfig(marketplace.Bootstrap, codexSetup.NumberOfNodes); + + var workflow = lifecycle.WorkflowCreator.CreateWorkflow(); + var containers = workflow.Start(1, Location.Unspecified, new GethContainerRecipe(), CreateStartupConfig(config)); + if (containers.Containers.Length != 1) throw new InvalidOperationException("Expected one Geth companion node to be created. Test infra failure."); + var container = containers.Containers[0]; + + var node = CreateCompanionInfo(container, marketplace, config); + EnsureCompanionNodeIsSynced(node, marketplace); + + LogEnd($"Initialized one companion node for {codexSetup.NumberOfNodes} Codex nodes. Their accounts: [{string.Join(",", node.Accounts.Select(a => a.Account))}]"); + return node; + } + + private GethCompanionNodeInfo CreateCompanionInfo(RunningContainer container, MarketplaceNetwork marketplace, GethStartupConfig config) + { + var accounts = ExtractAccounts(marketplace, config); + return new GethCompanionNodeInfo(container, accounts); + } + + private static GethAccount[] ExtractAccounts(MarketplaceNetwork marketplace, GethStartupConfig config) + { + return marketplace.Bootstrap.AllAccounts.Accounts + .Skip(1 + config.CompanionAccountStartIndex) + .Take(config.NumberOfCompanionAccounts) + .ToArray(); + } + + private void EnsureCompanionNodeIsSynced(GethCompanionNodeInfo node, MarketplaceNetwork marketplace) + { + try + { + Time.WaitUntil(() => + { + var interaction = node.StartInteraction(lifecycle, node.Accounts.First()); + return interaction.IsSynced(marketplace.Marketplace.Address, marketplace.Marketplace.Abi); + }, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(3)); + } + catch (Exception e) + { + throw new Exception("Geth companion node did not sync within timeout. Test infra failure.", e); + } + } + + private GethStartupConfig CreateCompanionNodeStartupConfig(GethBootstrapNodeInfo bootstrapNode, int numberOfAccounts) + { + var config = new GethStartupConfig(false, bootstrapNode, companionAccountIndex, numberOfAccounts); + companionAccountIndex += numberOfAccounts; + return config; + } + + private StartupConfig CreateStartupConfig(GethStartupConfig gethConfig) + { + var config = new StartupConfig(); + config.Add(gethConfig); + return config; + } + } +} diff --git a/GethPlugin/GethContainerRecipe.cs b/GethPlugin/GethContainerRecipe.cs new file mode 100644 index 0000000..1c1b2e1 --- /dev/null +++ b/GethPlugin/GethContainerRecipe.cs @@ -0,0 +1,73 @@ +using KubernetesWorkflow; + +namespace GethPlugin +{ + public class GethContainerRecipe : ContainerRecipeFactory + { + private const string defaultArgs = "--ipcdisable --syncmode full"; + + public const string HttpPortTag = "http_port"; + public const string DiscoveryPortTag = "disc_port"; + public const string AccountsFilename = "accounts.csv"; + + public override string AppName => "geth"; + public override string Image => "codexstorage/dist-tests-geth:latest"; + + protected override void Initialize(StartupConfig startupConfig) + { + var config = startupConfig.Get(); + + var args = CreateArgs(config); + + AddEnvVar("GETH_ARGS", args); + } + + private string CreateArgs(GethStartupConfig config) + { + var discovery = AddInternalPort(tag: DiscoveryPortTag); + + if (config.IsBootstrapNode) + { + return CreateBootstapArgs(discovery); + } + + return CreateCompanionArgs(discovery, config); + } + + private string CreateBootstapArgs(Port discovery) + { + AddEnvVar("ENABLE_MINER", "1"); + UnlockAccounts(0, 1); + var exposedPort = AddExposedPort(tag: HttpPortTag); + return $"--http.port {exposedPort.Number} --port {discovery.Number} --discovery.port {discovery.Number} {defaultArgs}"; + } + + private string CreateCompanionArgs(Port discovery, GethStartupConfig config) + { + UnlockAccounts( + config.CompanionAccountStartIndex + 1, + config.NumberOfCompanionAccounts); + + var port = AddInternalPort(); + var authRpc = AddInternalPort(); + var httpPort = AddExposedPort(tag: HttpPortTag); + + var bootPubKey = config.BootstrapNode.PubKey; + var bootIp = config.BootstrapNode.RunningContainers.Containers[0].Pod.PodInfo.Ip; + var bootPort = config.BootstrapNode.DiscoveryPort.Number; + var bootstrapArg = $"--bootnodes enode://{bootPubKey}@{bootIp}:{bootPort} --nat=extip:{bootIp}"; + + return $"--port {port.Number} --discovery.port {discovery.Number} --authrpc.port {authRpc.Number} --http.addr 0.0.0.0 --http.port {httpPort.Number} --ws --ws.addr 0.0.0.0 --ws.port {httpPort.Number} {bootstrapArg} {defaultArgs}"; + } + + private void UnlockAccounts(int startIndex, int numberOfAccounts) + { + if (startIndex < 0) throw new ArgumentException(); + if (numberOfAccounts < 1) throw new ArgumentException(); + if (startIndex + numberOfAccounts > 1000) throw new ArgumentException("Out of accounts!"); + + AddEnvVar("UNLOCK_START_INDEX", startIndex.ToString()); + AddEnvVar("UNLOCK_NUMBER", numberOfAccounts.ToString()); + } + } +} diff --git a/GethPlugin/GethPlugin.cs b/GethPlugin/GethPlugin.cs new file mode 100644 index 0000000..fcd15c2 --- /dev/null +++ b/GethPlugin/GethPlugin.cs @@ -0,0 +1,45 @@ +using Core; +using KubernetesWorkflow; + +namespace GethPlugin +{ + public class GethPlugin : IProjectPlugin, IHasLogPrefix, IHasMetadata + { + private readonly IPluginTools tools; + + public GethPlugin(IPluginTools tools) + { + //codexStarter = new CodexStarter(tools); + this.tools = tools; + } + + public string LogPrefix => "(Geth) "; + + public void Announce() + { + //tools.GetLog().Log($"Loaded with Codex ID: '{codexStarter.GetCodexId()}'"); + } + + public void AddMetadata(IAddMetadata metadata) + { + //metadata.Add("codexid", codexStarter.GetCodexId()); + } + + public void Decommission() + { + } + + //public RunningContainers[] StartCodexNodes(int numberOfNodes, Action setup) + //{ + // var codexSetup = new CodexSetup(numberOfNodes); + // codexSetup.LogLevel = defaultLogLevel; + // setup(codexSetup); + // return codexStarter.BringOnline(codexSetup); + //} + + //public ICodexNodeGroup WrapCodexContainers(RunningContainers[] containers) + //{ + // return codexStarter.WrapCodexContainers(containers); + //} + } +} diff --git a/GethPlugin/GethPlugin.csproj b/GethPlugin/GethPlugin.csproj new file mode 100644 index 0000000..3b52869 --- /dev/null +++ b/GethPlugin/GethPlugin.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + enable + enable + + + + + + + + + diff --git a/GethPlugin/GethStartResult.cs b/GethPlugin/GethStartResult.cs new file mode 100644 index 0000000..c098870 --- /dev/null +++ b/GethPlugin/GethStartResult.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace GethPlugin +{ + public class GethStartResult + { + public GethStartResult(IMarketplaceAccessFactory marketplaceAccessFactory, MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode) + { + MarketplaceAccessFactory = marketplaceAccessFactory; + MarketplaceNetwork = marketplaceNetwork; + CompanionNode = companionNode; + } + + [JsonIgnore] + public IMarketplaceAccessFactory MarketplaceAccessFactory { get; } + public MarketplaceNetwork MarketplaceNetwork { get; } + public GethCompanionNodeInfo CompanionNode { get; } + } +} diff --git a/GethPlugin/GethStarter.cs b/GethPlugin/GethStarter.cs new file mode 100644 index 0000000..f9e2162 --- /dev/null +++ b/GethPlugin/GethStarter.cs @@ -0,0 +1,86 @@ +namespace CodexPlugin +{ + public class GethStarter + { + private readonly MarketplaceNetworkCache marketplaceNetworkCache; + private readonly GethCompanionNodeStarter companionNodeStarter; + + public GethStarter(TestLifecycle lifecycle) + : base(lifecycle) + { + marketplaceNetworkCache = new MarketplaceNetworkCache( + new GethBootstrapNodeStarter(lifecycle), + new CodexContractsStarter(lifecycle)); + companionNodeStarter = new GethCompanionNodeStarter(lifecycle); + } + + public GethStartResult BringOnlineMarketplaceFor(CodexSetup codexSetup) + { + if (codexSetup.MarketplaceConfig == null) return CreateMarketplaceUnavailableResult(); + + var marketplaceNetwork = marketplaceNetworkCache.Get(); + var companionNode = StartCompanionNode(codexSetup, marketplaceNetwork); + + LogStart("Setting up initial balance..."); + TransferInitialBalance(marketplaceNetwork, codexSetup.MarketplaceConfig, companionNode); + LogEnd($"Initial balance of {codexSetup.MarketplaceConfig.InitialTestTokens} set for {codexSetup.NumberOfNodes} nodes."); + + return CreateGethStartResult(marketplaceNetwork, companionNode); + } + + private void TransferInitialBalance(MarketplaceNetwork marketplaceNetwork, MarketplaceInitialConfig marketplaceConfig, GethCompanionNodeInfo companionNode) + { + if (marketplaceConfig.InitialTestTokens.Amount == 0) return; + + var interaction = marketplaceNetwork.StartInteraction(lifecycle); + var tokenAddress = marketplaceNetwork.Marketplace.TokenAddress; + + var accounts = companionNode.Accounts.Select(a => a.Account).ToArray(); + interaction.MintTestTokens(accounts, marketplaceConfig.InitialTestTokens.Amount, tokenAddress); + } + + private GethStartResult CreateGethStartResult(MarketplaceNetwork marketplaceNetwork, GethCompanionNodeInfo companionNode) + { + return new GethStartResult(CreateMarketplaceAccessFactory(marketplaceNetwork), marketplaceNetwork, companionNode); + } + + private GethStartResult CreateMarketplaceUnavailableResult() + { + return new GethStartResult(new MarketplaceUnavailableAccessFactory(), null!, null!); + } + + private IMarketplaceAccessFactory CreateMarketplaceAccessFactory(MarketplaceNetwork marketplaceNetwork) + { + return new GethMarketplaceAccessFactory(lifecycle, marketplaceNetwork); + } + + private GethCompanionNodeInfo StartCompanionNode(CodexSetup codexSetup, MarketplaceNetwork marketplaceNetwork) + { + return companionNodeStarter.StartCompanionNodeFor(codexSetup, marketplaceNetwork); + } + } + + public class MarketplaceNetworkCache + { + private readonly GethBootstrapNodeStarter bootstrapNodeStarter; + private readonly CodexContractsStarter codexContractsStarter; + private MarketplaceNetwork? network; + + public MarketplaceNetworkCache(GethBootstrapNodeStarter bootstrapNodeStarter, CodexContractsStarter codexContractsStarter) + { + this.bootstrapNodeStarter = bootstrapNodeStarter; + this.codexContractsStarter = codexContractsStarter; + } + + public MarketplaceNetwork Get() + { + if (network == null) + { + var bootstrapInfo = bootstrapNodeStarter.StartGethBootstrapNode(); + var marketplaceInfo = codexContractsStarter.Start(bootstrapInfo); + network = new MarketplaceNetwork(bootstrapInfo, marketplaceInfo); + } + return network; + } + } +} diff --git a/GethPlugin/GethStartupConfig.cs b/GethPlugin/GethStartupConfig.cs new file mode 100644 index 0000000..d662a42 --- /dev/null +++ b/GethPlugin/GethStartupConfig.cs @@ -0,0 +1,18 @@ +namespace GethPlugin +{ + public class GethStartupConfig + { + public GethStartupConfig(bool isBootstrapNode, GethBootstrapNodeInfo bootstrapNode, int companionAccountStartIndex, int numberOfCompanionAccounts) + { + IsBootstrapNode = isBootstrapNode; + BootstrapNode = bootstrapNode; + CompanionAccountStartIndex = companionAccountStartIndex; + NumberOfCompanionAccounts = numberOfCompanionAccounts; + } + + public bool IsBootstrapNode { get; } + public GethBootstrapNodeInfo BootstrapNode { get; } + public int CompanionAccountStartIndex { get; } + public int NumberOfCompanionAccounts { get; } + } +} diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index 4065079..d0e90d0 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -29,7 +29,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexPlugin", "CodexPlugin\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{F2BF34B3-C660-43EF-BD42-BC5C60237FC4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetricsPlugin", "MetricsPlugin\MetricsPlugin.csproj", "{FCC74AF1-463D-4E5A-9FE7-B4A13F7C8820}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetricsPlugin", "MetricsPlugin\MetricsPlugin.csproj", "{FCC74AF1-463D-4E5A-9FE7-B4A13F7C8820}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GethPlugin", "GethPlugin\GethPlugin.csproj", "{5A1EF1DD-9E81-4501-B44C-493C72D2B166}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -93,6 +95,10 @@ Global {FCC74AF1-463D-4E5A-9FE7-B4A13F7C8820}.Debug|Any CPU.Build.0 = Debug|Any CPU {FCC74AF1-463D-4E5A-9FE7-B4A13F7C8820}.Release|Any CPU.ActiveCfg = Release|Any CPU {FCC74AF1-463D-4E5A-9FE7-B4A13F7C8820}.Release|Any CPU.Build.0 = Release|Any CPU + {5A1EF1DD-9E81-4501-B44C-493C72D2B166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A1EF1DD-9E81-4501-B44C-493C72D2B166}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A1EF1DD-9E81-4501-B44C-493C72D2B166}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A1EF1DD-9E81-4501-B44C-493C72D2B166}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE