Merge branch 'feature/public-testnet-deploying'

This commit is contained in:
benbierens 2023-12-14 15:59:22 +01:00
commit b143136590
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
36 changed files with 518 additions and 544 deletions

View File

@ -84,6 +84,31 @@ namespace KubernetesWorkflow
return result;
}
public int[] GetUsedExternalPorts()
{
return client.Run(c =>
{
var result = new List<int>();
var services = c.ListServiceForAllNamespaces();
var nodePorts = services.Items.Where(s => s.Spec.Type == "NodePort").ToArray();
if (!nodePorts.Any()) return result.ToArray();
foreach (var service in nodePorts)
{
foreach (var port in service.Spec.Ports)
{
if (port.NodePort.HasValue)
{
result.Add(port.NodePort.Value);
}
}
}
return result.ToArray();
});
}
public void DeleteAllNamespacesStartingWith(string prefix)
{
log.Debug();
@ -704,17 +729,17 @@ namespace KubernetesWorkflow
private RunningService? CreateInternalService(ContainerRecipe[] recipes)
{
return CreateService(recipes, r => r.InternalPorts.Concat(r.ExposedPorts).ToArray(), "ClusterIP", "int");
return CreateService(recipes, r => r.InternalPorts.Concat(r.ExposedPorts).ToArray(), "ClusterIP", "int", false);
}
private RunningService? CreateExternalService(ContainerRecipe[] recipes)
{
return CreateService(recipes, r => r.ExposedPorts, "NodePort", "ext");
return CreateService(recipes, r => r.ExposedPorts, "NodePort", "ext", true);
}
private RunningService? CreateService(ContainerRecipe[] recipes, Func<ContainerRecipe, Port[]> portSelector, string serviceType, string namePostfix)
private RunningService? CreateService(ContainerRecipe[] recipes, Func<ContainerRecipe, Port[]> portSelector, string serviceType, string namePostfix, bool isNodePort)
{
var ports = CreateServicePorts(recipes, portSelector);
var ports = CreateServicePorts(recipes, portSelector, isNodePort);
if (!ports.Any()) return null;
var serviceSpec = new V1Service
@ -788,7 +813,7 @@ namespace KubernetesWorkflow
};
}
private List<V1ServicePort> CreateServicePorts(ContainerRecipe[] recipes, Func<ContainerRecipe, Port[]> portSelector)
private List<V1ServicePort> CreateServicePorts(ContainerRecipe[] recipes, Func<ContainerRecipe, Port[]> portSelector, bool isNodePort)
{
var result = new List<V1ServicePort>();
foreach (var recipe in recipes)
@ -796,29 +821,33 @@ namespace KubernetesWorkflow
var ports = portSelector(recipe);
foreach (var port in ports)
{
result.AddRange(CreateServicePorts(recipe, port));
result.AddRange(CreateServicePorts(recipe, port, isNodePort));
}
}
return result;
}
private List<V1ServicePort> CreateServicePorts(ContainerRecipe recipe, Port recipePort)
private List<V1ServicePort> CreateServicePorts(ContainerRecipe recipe, Port recipePort, bool isNodePort)
{
var result = new List<V1ServicePort>();
if (recipePort.IsTcp()) CreateServicePort(result, recipe, recipePort, "TCP");
if (recipePort.IsUdp()) CreateServicePort(result, recipe, recipePort, "UDP");
if (recipePort.IsTcp()) CreateServicePort(result, recipe, recipePort, "TCP", isNodePort);
if (recipePort.IsUdp()) CreateServicePort(result, recipe, recipePort, "UDP", isNodePort);
return result;
}
private void CreateServicePort(List<V1ServicePort> result, ContainerRecipe recipe, Port port, string protocol)
private void CreateServicePort(List<V1ServicePort> result, ContainerRecipe recipe, Port port, string protocol, bool isNodePort)
{
result.Add(new V1ServicePort
var p = new V1ServicePort
{
Name = GetNameForPort(recipe, port),
Protocol = protocol,
Port = port.Number,
TargetPort = GetNameForPort(recipe, port)
});
};
if (isNodePort) p.NodePort = port.Number;
result.Add(p);
}
#endregion

View File

@ -56,17 +56,17 @@ namespace KubernetesWorkflow.Recipe
protected Port AddExposedPort(string tag, PortProtocol protocol = PortProtocol.TCP)
{
return AddExposedPort(factory.CreatePort(tag, protocol));
return AddExposedPort(factory.CreateExternalPort(tag, protocol));
}
protected Port AddExposedPort(int number, string tag, PortProtocol protocol = PortProtocol.TCP)
{
return AddExposedPort(factory.CreatePort(number, tag, protocol));
return AddExposedPort(factory.CreateExternalPort(number, tag, protocol));
}
protected Port AddInternalPort(string tag = "", PortProtocol protocol = PortProtocol.TCP)
{
var p = factory.CreatePort(tag, protocol);
var p = factory.CreateInternalPort(tag, protocol);
internalPorts.Add(p);
return p;
}

View File

@ -5,16 +5,36 @@ namespace KubernetesWorkflow.Recipe
{
public class RecipeComponentFactory
{
private NumberSource portNumberSource = new NumberSource(8080);
private readonly NumberSource internalNumberSource = new NumberSource(8080);
private static readonly NumberSource externalNumberSource = new NumberSource(30000);
private static int[] usedExternalPorts = Array.Empty<int>();
public Port CreatePort(int number, string tag, PortProtocol protocol)
public void Update(K8sController controller)
{
usedExternalPorts = controller.GetUsedExternalPorts();
}
public Port CreateInternalPort(string tag, PortProtocol protocol)
{
return new Port(internalNumberSource.GetNextNumber(), tag, protocol);
}
public Port CreateExternalPort(int number, string tag, PortProtocol protocol)
{
if (usedExternalPorts.Contains(number)) throw new Exception($"External port number {number} is already in use by the cluster.");
return new Port(number, tag, protocol);
}
public Port CreatePort(string tag, PortProtocol protocol)
public Port CreateExternalPort(string tag, PortProtocol protocol)
{
return new Port(portNumberSource.GetNextNumber(), tag, protocol);
while (true)
{
var number = externalNumberSource.GetNextNumber();
if (!usedExternalPorts.Contains(number))
{
return new Port(number, tag, protocol);
}
}
}
public EnvVar CreateEnvVar(string name, int value)

View File

@ -54,12 +54,19 @@ namespace KubernetesWorkflow
{
return K8s(controller =>
{
componentFactory.Update(controller);
var recipes = CreateRecipes(numberOfContainers, recipeFactory, startupConfig);
var startResult = controller.BringOnline(recipes, location);
var containers = CreateContainers(startResult, recipes, startupConfig);
var rc = new RunningContainers(startupConfig, startResult, containers);
cluster.Configuration.Hooks.OnContainersStarted(rc);
if (startResult.ExternalService != null)
{
componentFactory.Update(controller);
}
return rc;
});
}
@ -222,7 +229,7 @@ namespace KubernetesWorkflow
}
catch (k8s.Autorest.HttpOperationException ex)
{
log.Error(JsonConvert.SerializeObject(ex));
log.Error(JsonConvert.SerializeObject(ex.Response));
throw;
}
}
@ -238,7 +245,7 @@ namespace KubernetesWorkflow
}
catch (k8s.Autorest.HttpOperationException ex)
{
log.Error(JsonConvert.SerializeObject(ex));
log.Error(JsonConvert.SerializeObject(ex.Response));
throw;
}
}

View File

@ -55,7 +55,9 @@ namespace Logging
public virtual void Error(string message)
{
Log($"[ERROR] {message}");
var msg = $"[ERROR] {message}";
Console.WriteLine(msg);
Log(msg);
}
public virtual void AddStringReplace(string from, string to)

View File

@ -16,6 +16,7 @@
public override void Error(string message)
{
Console.WriteLine("Error: " + message);
base.Error(message);
}

View File

@ -19,12 +19,24 @@ namespace CodexDiscordBotPlugin
AddEnvVar("SERVERNAME", config.ServerName);
AddEnvVar("ADMINROLE", config.AdminRoleName);
AddEnvVar("ADMINCHANNELNAME", config.AdminChannelName);
AddEnvVar("KUBECONFIG", "/opt/kubeconfig.yaml");
AddEnvVar("KUBENAMESPACE", config.KubeNamespace);
var gethInfo = config.GethInfo;
AddEnvVar("GETH_HOST", gethInfo.Host);
AddEnvVar("GETH_HTTP_PORT", gethInfo.Port.ToString());
AddEnvVar("GETH_PRIVATE_KEY", gethInfo.PrivKey);
AddEnvVar("CODEXCONTRACTS_MARKETPLACEADDRESS", gethInfo.MarketplaceAddress);
AddEnvVar("CODEXCONTRACTS_TOKENADDRESS", gethInfo.TokenAddress);
AddEnvVar("CODEXCONTRACTS_ABI", gethInfo.Abi);
if (!string.IsNullOrEmpty(config.DataPath))
{
AddEnvVar("DATAPATH", config.DataPath);
AddVolume(config.DataPath, 1.GB());
}
AddVolume(name: "kubeconfig", mountPath: "/opt/kubeconfig.yaml", subPath: "kubeconfig.yaml", secret: "discordbot-sa-kubeconfig");
}
}
}

View File

@ -2,13 +2,15 @@
{
public class DiscordBotStartupConfig
{
public DiscordBotStartupConfig(string name, string token, string serverName, string adminRoleName, string adminChannelName)
public DiscordBotStartupConfig(string name, string token, string serverName, string adminRoleName, string adminChannelName, string kubeNamespace, DiscordBotGethInfo gethInfo)
{
Name = name;
Token = token;
ServerName = serverName;
AdminRoleName = adminRoleName;
AdminChannelName = adminChannelName;
KubeNamespace = kubeNamespace;
GethInfo = gethInfo;
}
public string Name { get; }
@ -16,6 +18,28 @@
public string ServerName { get; }
public string AdminRoleName { get; }
public string AdminChannelName { get; }
public string KubeNamespace { get; }
public DiscordBotGethInfo GethInfo { get; }
public string? DataPath { get; set; }
}
public class DiscordBotGethInfo
{
public DiscordBotGethInfo(string host, int port, string privKey, string marketplaceAddress, string tokenAddress, string abi)
{
Host = host;
Port = port;
PrivKey = privKey;
MarketplaceAddress = marketplaceAddress;
TokenAddress = tokenAddress;
Abi = abi;
}
public string Host { get; }
public int Port { get; }
public string PrivKey { get; }
public string MarketplaceAddress { get; }
public string TokenAddress { get; }
public string Abi { get; }
}
}

View File

@ -5,9 +5,10 @@ namespace CodexPlugin
public class CodexDebugResponse
{
public string id { get; set; } = string.Empty;
public string[] addrs { get; set; } = new string[0];
public string[] addrs { get; set; } = Array.Empty<string>();
public string repo { get; set; } = string.Empty;
public string spr { get; set; } = string.Empty;
public string[] announceAddresses { get; set; } = Array.Empty<string>();
public EnginePeerResponse[] enginePeers { get; set; } = Array.Empty<EnginePeerResponse>();
public SwitchPeerResponse[] switchPeers { get; set; } = Array.Empty<SwitchPeerResponse>();
public CodexDebugVersionResponse codex { get; set; } = new();

View File

@ -48,8 +48,9 @@ namespace CodexPlugin
if (config.PublicTestNet != null)
{
AddEnvVar("CODEX_NAT", config.PublicTestNet.PublicNatIP);
// This makes the node announce itself to its public IP address.
AddEnvVar("NAT_IP_AUTO", "false");
AddEnvVar("NAT_PUBLIC_IP_AUTO", PublicIpService.Address);
}
else
{

View File

@ -133,7 +133,7 @@ namespace CodexPlugin
private IEnumerable<string> DescribeArgs()
{
if (PublicTestNet != null) yield return $"<!>Public TestNet at {PublicTestNet.PublicNatIP}:{PublicTestNet.PublicListenPort}<!>";
if (PublicTestNet != null) yield return $"<!>Public TestNet with listenPort: {PublicTestNet.PublicListenPort}<!>";
yield return $"LogLevel={LogLevelWithTopics()}";
if (BootstrapSpr != null) yield return $"BootstrapNode={BootstrapSpr}";
if (StorageQuota != null) yield return $"StorageQuota={StorageQuota}";

View File

@ -65,7 +65,6 @@ namespace CodexPlugin
public class CodexTestNetConfig
{
public string PublicNatIP { get; set; } = string.Empty;
public int PublicDiscoveryPort { get; set; }
public int PublicListenPort { get; set; }
}

View File

@ -32,8 +32,11 @@ namespace GethPlugin
private string CreateArgs(GethStartupConfig config)
{
if (config.IsMiner) AddEnvVar("ENABLE_MINER", "1");
UnlockAccounts(0, 1);
if (config.IsMiner)
{
AddEnvVar("ENABLE_MINER", "1");
UnlockAccounts(0, 1);
}
var httpPort = CreateApiPort(config, tag: HttpPortTag);
var discovery = CreateDiscoveryPort(config);
@ -41,16 +44,20 @@ namespace GethPlugin
var authRpc = CreateP2pPort(config, tag: AuthRpcPortTag);
var wsPort = CreateP2pPort(config, tag: WsPortTag);
var args = $"--http.addr 0.0.0.0 --http.port {httpPort.Number} --port {listen.Number} --discovery.port {discovery.Number} {GetTestNetArgs(config)} {defaultArgs}";
var args = $"--http.addr 0.0.0.0 --http.port {httpPort.Number} --port {listen.Number} --discovery.port {discovery.Number} {defaultArgs}";
if (config.BootstrapNode != null)
{
var bootPubKey = config.BootstrapNode.PublicKey;
var bootIp = config.BootstrapNode.IpAddress;
var bootPort = config.BootstrapNode.Port;
var bootstrapArg = $" --bootnodes enode://{bootPubKey}@{bootIp}:{bootPort} --nat=extip:{bootIp}";
var bootstrapArg = $" --bootnodes enode://{bootPubKey}@{bootIp}:{bootPort}";
args += bootstrapArg;
}
if (config.IsPublicTestNet != null)
{
AddEnvVar("NAT_PUBLIC_IP_AUTO", PublicIpService.Address);
}
return args + $" --authrpc.port {authRpc.Number} --ws --ws.addr 0.0.0.0 --ws.port {wsPort.Number}";
}
@ -58,20 +65,13 @@ namespace GethPlugin
private void UnlockAccounts(int startIndex, int numberOfAccounts)
{
if (startIndex < 0) throw new ArgumentException();
if (numberOfAccounts < 1) throw new ArgumentException();
if (numberOfAccounts < 0) throw new ArgumentException();
if (startIndex + numberOfAccounts > 1000) throw new ArgumentException("Out of accounts!");
AddEnvVar("UNLOCK_START_INDEX", startIndex.ToString());
AddEnvVar("UNLOCK_NUMBER", numberOfAccounts.ToString());
}
private string GetTestNetArgs(GethStartupConfig config)
{
if (config.IsPublicTestNet == null) return string.Empty;
return $"--nat=extip:{config.IsPublicTestNet.PublicIp}";
}
private Port CreateDiscoveryPort(GethStartupConfig config)
{
if (config.IsPublicTestNet == null) return AddInternalPort(DiscoveryPortTag);

View File

@ -19,13 +19,14 @@ namespace GethPlugin
void SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
decimal? GetSyncedBlockNumber();
bool IsContractAvailable(string abi, string contractAddress);
GethBootstrapNode GetBootstrapRecord();
}
public class GethNode : IGethNode
public class DeploymentGethNode : BaseGethNode, IGethNode
{
private readonly ILog log;
public GethNode(ILog log, GethDeployment startResult)
public DeploymentGethNode(ILog log, GethDeployment startResult)
{
this.log = log;
StartResult = startResult;
@ -34,6 +35,59 @@ namespace GethPlugin
public GethDeployment StartResult { get; }
public RunningContainer Container => StartResult.Container;
public GethBootstrapNode GetBootstrapRecord()
{
var address = StartResult.Container.GetInternalAddress(GethContainerRecipe.ListenPortTag);
return new GethBootstrapNode(
publicKey: StartResult.PubKey,
ipAddress: address.Host.Replace("http://", ""),
port: address.Port
);
}
protected override NethereumInteraction StartInteraction()
{
var address = StartResult.Container.GetAddress(log, GethContainerRecipe.HttpPortTag);
var account = StartResult.Account;
var creator = new NethereumInteractionCreator(log, address.Host, address.Port, account.PrivateKey);
return creator.CreateWorkflow();
}
}
public class CustomGethNode : BaseGethNode, IGethNode
{
private readonly ILog log;
private readonly string gethHost;
private readonly int gethPort;
private readonly string privateKey;
public GethDeployment StartResult => throw new NotImplementedException();
public RunningContainer Container => throw new NotImplementedException();
public CustomGethNode(ILog log, string gethHost, int gethPort, string privateKey)
{
this.log = log;
this.gethHost = gethHost;
this.gethPort = gethPort;
this.privateKey = privateKey;
}
public GethBootstrapNode GetBootstrapRecord()
{
throw new NotImplementedException();
}
protected override NethereumInteraction StartInteraction()
{
var creator = new NethereumInteractionCreator(log, gethHost, gethPort, privateKey);
return creator.CreateWorkflow();
}
}
public abstract class BaseGethNode
{
public Ether GetEthBalance()
{
return StartInteraction().GetEthBalance().Eth();
@ -69,15 +123,6 @@ namespace GethPlugin
StartInteraction().SendTransaction(contractAddress, function);
}
private NethereumInteraction StartInteraction()
{
var address = StartResult.Container.GetAddress(log, GethContainerRecipe.HttpPortTag);
var account = StartResult.Account;
var creator = new NethereumInteractionCreator(log, address.Host, address.Port, account.PrivateKey);
return creator.CreateWorkflow();
}
public decimal? GetSyncedBlockNumber()
{
return StartInteraction().GetSyncedBlockNumber();
@ -87,5 +132,7 @@ namespace GethPlugin
{
return StartInteraction().IsContractAvailable(abi, contractAddress);
}
protected abstract NethereumInteraction StartInteraction();
}
}

View File

@ -44,7 +44,7 @@ namespace GethPlugin
public IGethNode WrapGethContainer(GethDeployment startResult)
{
startResult = SerializeGate.Gate(startResult);
return new GethNode(tools.GetLog(), startResult);
return new DeploymentGethNode(tools.GetLog(), startResult);
}
private void Log(string msg)

View File

@ -3,6 +3,7 @@
public interface IGethSetup
{
IGethSetup IsMiner();
IGethSetup WithBootstrapNode(IGethNode node);
IGethSetup WithBootstrapNode(GethBootstrapNode node);
IGethSetup WithName(string name);
IGethSetup AsPublicTestNet(GethTestNetConfig gethTestNetConfig);
@ -15,6 +16,11 @@
public string? NameOverride { get; private set; }
public GethTestNetConfig? IsPublicTestNet { get; private set; }
public IGethSetup WithBootstrapNode(IGethNode node)
{
return WithBootstrapNode(node.GetBootstrapRecord());
}
public IGethSetup WithBootstrapNode(GethBootstrapNode node)
{
BootstrapNode = node;
@ -42,14 +48,12 @@
public class GethTestNetConfig
{
public GethTestNetConfig(string publicIp, int discoveryPort, int listenPort)
public GethTestNetConfig(int discoveryPort, int listenPort)
{
PublicIp = publicIp;
DiscoveryPort = discoveryPort;
ListenPort = listenPort;
}
public string PublicIp { get; }
public int DiscoveryPort { get; }
public int ListenPort { get; }
}

View File

@ -18,6 +18,8 @@ namespace MetricsPlugin
public RunningContainers CollectMetricsFor(IMetricsScrapeTarget[] targets)
{
if (!targets.Any()) throw new ArgumentException(nameof(targets) + " must not be empty.");
Log($"Starting metrics server for {targets.Length} targets...");
var startupConfig = new StartupConfig();
startupConfig.Add(new PrometheusStartupConfig(GeneratePrometheusConfig(targets)));

View File

@ -97,5 +97,22 @@ namespace CodexTests.BasicTests
CheckLogForErrors(seller, buyer);
}
[Test]
public void GethBootstrapTest()
{
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);
var bootN = boot.GetSyncedBlockNumber();
var discN = disconnected.GetSyncedBlockNumber();
var followN = follow.GetSyncedBlockNumber();
Assert.That(bootN, Is.EqualTo(followN));
Assert.That(discN, Is.LessThan(bootN));
}
}
}

View File

@ -1,27 +0,0 @@
using BiblioTech.Options;
using CodexPlugin;
using Core;
namespace BiblioTech
{
public abstract class BaseCodexCommand : BaseDeploymentCommand
{
private readonly CoreInterface ci;
public BaseCodexCommand(CoreInterface ci)
{
this.ci = ci;
}
protected override async Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment)
{
var codexContainers = codexDeployment.CodexInstances.Select(c => c.Containers).ToArray();
var group = ci.WrapCodexContainers(codexContainers);
await Execute(context, group);
}
protected abstract Task Execute(CommandContext context, ICodexNodeGroup codexGroup);
}
}

View File

@ -9,13 +9,7 @@ namespace BiblioTech
public abstract string Name { get; }
public abstract string StartingMessage { get; }
public abstract string Description { get; }
public virtual CommandOption[] Options
{
get
{
return Array.Empty<CommandOption>();
}
}
public virtual CommandOption[] Options => Array.Empty<CommandOption>();
public async Task SlashCommandHandler(SocketSlashCommand command)
{
@ -29,7 +23,15 @@ namespace BiblioTech
}
catch (Exception ex)
{
await command.FollowupAsync("Something failed while trying to do that...", ephemeral: true);
if (IsInAdminChannel(command))
{
var msg = "Failed with exception: " + ex;
await command.FollowupAsync(msg.Substring(0, Math.Min(1900, msg.Length)));
}
else
{
await command.FollowupAsync("Something failed while trying to do that...", ephemeral: true);
}
Console.WriteLine(ex);
}
}

View File

@ -1,36 +0,0 @@
using BiblioTech.Options;
using CodexPlugin;
namespace BiblioTech
{
public abstract class BaseDeploymentCommand : BaseCommand
{
protected override async Task Invoke(CommandContext context)
{
var proceed = await OnInvoke(context);
if (!proceed) return;
var deployments = Program.DeploymentFilesMonitor.GetDeployments();
if (deployments.Length == 0)
{
await context.Followup("No deployments are currently available.");
return;
}
if (deployments.Length > 1)
{
await context.Followup("Multiple deployments are online. I don't know which one to pick!");
return;
}
var codexDeployment = deployments.Single();
await ExecuteDeploymentCommand(context, codexDeployment);
}
protected abstract Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment);
protected virtual Task<bool> OnInvoke(CommandContext context)
{
return Task.FromResult(true);
}
}
}

View File

@ -1,27 +1,89 @@
using BiblioTech.Options;
using CodexContractsPlugin;
using CodexPlugin;
using Core;
using GethPlugin;
using Logging;
namespace BiblioTech
{
public abstract class BaseGethCommand : BaseDeploymentCommand
public static class GethInput
{
private readonly CoreInterface ci;
private const string GethHostVar = "GETH_HOST";
private const string GethPortVar = "GETH_HTTP_PORT";
private const string GethPrivKeyVar = "GETH_PRIVATE_KEY";
private const string MarketplaceAddressVar = "CODEXCONTRACTS_MARKETPLACEADDRESS";
private const string TokenAddressVar = "CODEXCONTRACTS_TOKENADDRESS";
private const string AbiVar = "CODEXCONTRACTS_ABI";
public BaseGethCommand(CoreInterface ci)
static GethInput()
{
this.ci = ci;
var error = new List<string>();
var gethHost = GetEnvVar(error, GethHostVar);
var gethPort = Convert.ToInt32(GetEnvVar(error, GethPortVar));
var privateKey = GetEnvVar(error, GethPrivKeyVar);
var marketplaceAddress = GetEnvVar(error, MarketplaceAddressVar);
var tokenAddress = GetEnvVar(error, TokenAddressVar);
var abi = GetEnvVar(error, AbiVar);
if (error.Any())
{
LoadError = string.Join(", ", error);
}
else
{
GethHost = gethHost!;
GethPort = gethPort;
PrivateKey = privateKey!;
MarketplaceAddress = marketplaceAddress!;
TokenAddress = tokenAddress!;
ABI = abi!;
}
}
protected override async Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment)
{
var gethDeployment = codexDeployment.GethDeployment;
var contractsDeployment = codexDeployment.CodexContractsDeployment;
public static string GethHost { get; } = string.Empty;
public static int GethPort { get; }
public static string PrivateKey { get; } = string.Empty;
public static string MarketplaceAddress { get; } = string.Empty;
public static string TokenAddress { get; } = string.Empty;
public static string ABI { get; } = string.Empty;
public static string LoadError { get; } = string.Empty;
var gethNode = ci.WrapGethDeployment(gethDeployment);
var contracts = ci.WrapCodexContractsDeployment(gethNode, contractsDeployment);
private static string? GetEnvVar(List<string> error, string name)
{
var result = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(result)) error.Add($"'{name}' is not set.");
return result;
}
}
public abstract class BaseGethCommand : BaseCommand
{
protected override async Task Invoke(CommandContext context)
{
var log = new ConsoleLog();
if (!string.IsNullOrEmpty(GethInput.LoadError))
{
var msg = "Geth input incorrect: " + GethInput.LoadError;
log.Error(msg);
if (IsInAdminChannel(context.Command))
{
await context.Followup(msg);
}
else
{
await context.Followup("I'm sorry, there seems to be a configuration error.");
}
return;
}
var contractsDeployment = new CodexContractsDeployment(
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);
await Execute(context, gethNode, contracts);
}

View File

@ -1,7 +1,4 @@
using BiblioTech.Options;
using CodexPlugin;
using Core;
using Newtonsoft.Json;
namespace BiblioTech.Commands
{
@ -9,17 +6,16 @@ namespace BiblioTech.Commands
{
private readonly ClearUserAssociationCommand clearCommand = new ClearUserAssociationCommand();
private readonly ReportCommand reportCommand = new ReportCommand();
private readonly DeployListCommand deployListCommand = new DeployListCommand();
private readonly DeployUploadCommand deployUploadCommand = new DeployUploadCommand();
private readonly DeployRemoveCommand deployRemoveCommand = new DeployRemoveCommand();
private readonly WhoIsCommand whoIsCommand = new WhoIsCommand();
private readonly NetInfoCommand netInfoCommand;
private readonly DebugPeerCommand debugPeerCommand;
private readonly AddSprCommand addSprCommand;
private readonly ClearSprsCommand clearSprsCommand;
private readonly GetSprCommand getSprCommand;
public AdminCommand(CoreInterface ci)
public AdminCommand(SprCommand sprCommand)
{
netInfoCommand = new NetInfoCommand(ci);
debugPeerCommand = new DebugPeerCommand(ci);
addSprCommand = new AddSprCommand(sprCommand);
clearSprsCommand = new ClearSprsCommand(sprCommand);
getSprCommand = new GetSprCommand(sprCommand);
}
public override string Name => "admin";
@ -30,12 +26,10 @@ namespace BiblioTech.Commands
{
clearCommand,
reportCommand,
deployListCommand,
deployUploadCommand,
deployRemoveCommand,
whoIsCommand,
netInfoCommand,
debugPeerCommand
addSprCommand,
clearSprsCommand,
getSprCommand
};
protected override async Task Invoke(CommandContext context)
@ -54,12 +48,10 @@ namespace BiblioTech.Commands
await clearCommand.CommandHandler(context);
await reportCommand.CommandHandler(context);
await deployListCommand.CommandHandler(context);
await deployUploadCommand.CommandHandler(context);
await deployRemoveCommand.CommandHandler(context);
await whoIsCommand.CommandHandler(context);
await netInfoCommand.CommandHandler(context);
await debugPeerCommand.CommandHandler(context);
await addSprCommand.CommandHandler(context);
await clearSprsCommand.CommandHandler(context);
await getSprCommand.CommandHandler(context);
}
public class ClearUserAssociationCommand : SubCommandOption
@ -109,108 +101,8 @@ namespace BiblioTech.Commands
return;
}
var report = string.Join(Environment.NewLine, Program.UserRepo.GetInteractionReport(user));
if (report.Length > 1900)
{
var filename = $"user-{user.Username}.log";
await context.FollowupWithAttachement(filename, report);
}
else
{
await context.Followup(report);
}
}
}
public class DeployListCommand : SubCommandOption
{
public DeployListCommand()
: base("list", "Lists current deployments.")
{
}
protected override async Task onSubCommand(CommandContext context)
{
var deployments = Program.DeploymentFilesMonitor.GetDeployments();
//todo shows old deployments
if (!deployments.Any())
{
await context.Followup("No deployments available.");
return;
}
var nl = Environment.NewLine;
await context.Followup($"Deployments:{nl}{string.Join(nl, deployments.Select(FormatDeployment))}");
}
private string FormatDeployment(CodexDeployment deployment)
{
var m = deployment.Metadata;
return $"'{m.Name}' ({m.StartUtc.ToString("o")})";
}
}
public class DeployUploadCommand : SubCommandOption
{
private readonly FileAttachementOption fileOption = new FileAttachementOption(
name: "json",
description: "Codex-deployment json to add.",
isRequired: true);
public DeployUploadCommand()
: base("add", "Upload a new deployment JSON file.")
{
}
public override CommandOption[] Options => new[] { fileOption };
protected override async Task onSubCommand(CommandContext context)
{
var file = await fileOption.Parse(context);
if (file == null) return;
var result = await Program.DeploymentFilesMonitor.DownloadDeployment(file);
if (result)
{
await context.Followup("Success!");
}
else
{
await context.Followup("That didn't work.");
}
}
}
public class DeployRemoveCommand : SubCommandOption
{
private readonly StringOption stringOption = new StringOption(
name: "name",
description: "Name of deployment to remove.",
isRequired: true);
public DeployRemoveCommand()
: base("remove", "Removes a deployment file.")
{
}
public override CommandOption[] Options => new[] { stringOption };
protected override async Task onSubCommand(CommandContext context)
{
var str = await stringOption.Parse(context);
if (string.IsNullOrEmpty(str)) return;
var result = Program.DeploymentFilesMonitor.DeleteDeployment(str);
if (result)
{
await context.Followup("Success!");
}
else
{
await context.Followup("That didn't work.");
}
var report = Program.UserRepo.GetInteractionReport(user);
await context.Followup(report);
}
}
@ -247,118 +139,76 @@ namespace BiblioTech.Commands
}
}
public abstract class AdminDeploymentCommand : SubCommandOption
public class AddSprCommand : SubCommandOption
{
private readonly CoreInterface ci;
private readonly SprCommand sprCommand;
private readonly StringOption stringOption = new StringOption("spr", "Codex SPR", true);
public AdminDeploymentCommand(CoreInterface ci, string name, string description)
: base(name, description)
public AddSprCommand(SprCommand sprCommand)
: base(name: "addspr",
description: "Adds a Codex SPR, to be given to users with '/boot'.")
{
this.ci = ci;
this.sprCommand = sprCommand;
}
protected async Task OnDeployment(CommandContext context, Func<ICodexNodeGroup, string, Task> action)
{
var deployment = Program.DeploymentFilesMonitor.GetDeployments().SingleOrDefault();
if (deployment == null)
{
await context.Followup("No deployment found.");
return;
}
public override CommandOption[] Options => new[] { stringOption };
try
protected override async Task onSubCommand(CommandContext context)
{
var spr = await stringOption.Parse(context);
if (!string.IsNullOrEmpty(spr) )
{
var group = ci.WrapCodexContainers(deployment.CodexInstances.Select(i => i.Containers).ToArray());
await action(group, deployment.Metadata.Name);
sprCommand.Add(spr);
await context.Followup("A-OK!");
}
catch (Exception ex)
else
{
await context.Followup("Failed to wrap nodes with exception: " + ex);
await context.Followup("SPR is null or empty.");
}
}
}
public class NetInfoCommand : AdminDeploymentCommand
public class ClearSprsCommand : SubCommandOption
{
public NetInfoCommand(CoreInterface ci)
: base(ci, name: "netinfo",
description: "Fetches info endpoints of codex nodes.")
private readonly SprCommand sprCommand;
private readonly StringOption stringOption = new StringOption("areyousure", "set to 'true' if you are.", true);
public ClearSprsCommand(SprCommand sprCommand)
: base(name: "clearsprs",
description: "Clears all Codex SPRs in the bot. Users won't be able to use '/boot' till new ones are added.")
{
this.sprCommand = sprCommand;
}
public override CommandOption[] Options => new[] { stringOption };
protected override async Task onSubCommand(CommandContext context)
{
var areyousure = await stringOption.Parse(context);
if (areyousure != "true") return;
sprCommand.Clear();
await context.Followup("Cleared all SPRs.");
}
}
public class GetSprCommand: SubCommandOption
{
private readonly SprCommand sprCommand;
public GetSprCommand(SprCommand sprCommand)
: base(name: "getsprs",
description: "Shows all Codex SPRs in the bot.")
{
this.sprCommand = sprCommand;
}
protected override async Task onSubCommand(CommandContext context)
{
await OnDeployment(context, async (group, name) =>
{
var nl = Environment.NewLine;
var content = new List<string>
{
$"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes."
};
foreach (var node in group)
{
try
{
var info = node.GetDebugInfo();
var json = JsonConvert.SerializeObject(info, Formatting.Indented);
var jsonInsert = $"{nl}```{nl}{json}{nl}```{nl}";
content.Add($"Node '{node.GetName()}' responded with {jsonInsert}");
}
catch (Exception ex)
{
content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex);
}
}
var filename = $"netinfo-{NoWhitespaces(name)}.log";
await context.FollowupWithAttachement(filename, string.Join(nl, content.ToArray()));
});
await context.Followup("SPRs: " + string.Join(", ", sprCommand.Get().Select(s => $"'{s}'")));
}
}
public class DebugPeerCommand : AdminDeploymentCommand
{
private readonly StringOption peerIdOption = new StringOption("peerid", "id of peer to try and reach.", true);
public DebugPeerCommand(CoreInterface ci)
: base(ci, name: "debugpeer",
description: "Calls debug/peer on each codex node.")
{
}
public override CommandOption[] Options => new[] { peerIdOption };
protected override async Task onSubCommand(CommandContext context)
{
var peerId = await peerIdOption.Parse(context);
if (string.IsNullOrEmpty(peerId)) return;
await OnDeployment(context, async (group, name) =>
{
await context.Followup($"Calling debug/peer for '{peerId}' on {group.Count()} Codex nodes.");
foreach (var node in group)
{
try
{
var info = node.GetDebugPeer(peerId);
var nl = Environment.NewLine;
var json = JsonConvert.SerializeObject(info, Formatting.Indented);
var jsonInsert = $"{nl}```{nl}{json}{nl}```{nl}";
await context.Followup($"Node '{node.GetName()}' responded with {jsonInsert}");
}
catch (Exception ex)
{
await context.Followup($"Node '{node.GetName()}' failed to respond with exception: " + ex);
}
}
});
}
}
private static string NoWhitespaces(string s)
{
return s.Replace(" ", "-");
}
}
}

View File

@ -1,5 +1,6 @@
using BiblioTech.Options;
using CodexContractsPlugin;
using CodexPlugin;
using Core;
using GethPlugin;
@ -12,8 +13,7 @@ namespace BiblioTech.Commands
description: "If set, get balance for another user. (Optional, admin-only)",
isRequired: false);
public GetBalanceCommand(CoreInterface ci, UserAssociateCommand userAssociateCommand)
: base(ci)
public GetBalanceCommand(UserAssociateCommand userAssociateCommand)
{
this.userAssociateCommand = userAssociateCommand;
}

View File

@ -1,6 +1,5 @@
using BiblioTech.Options;
using CodexContractsPlugin;
using Core;
using GethPlugin;
namespace BiblioTech.Commands
@ -14,8 +13,7 @@ namespace BiblioTech.Commands
isRequired: false);
private readonly UserAssociateCommand userAssociateCommand;
public MintCommand(CoreInterface ci, UserAssociateCommand userAssociateCommand)
: base(ci)
public MintCommand(UserAssociateCommand userAssociateCommand)
{
this.userAssociateCommand = userAssociateCommand;
}

View File

@ -1,61 +1,48 @@
using BiblioTech.Options;
using CodexPlugin;
using Core;
namespace BiblioTech.Commands
{
public class SprCommand : BaseCodexCommand
public class SprCommand : BaseCommand
{
private readonly Random random = new Random();
private readonly List<string> sprCache = new List<string>();
private DateTime lastUpdate = DateTime.MinValue;
public SprCommand(CoreInterface ci) : base(ci)
{
}
private readonly List<string> knownSprs = new List<string>();
public override string Name => "boot";
public override string StartingMessage => RandomBusyMessage.Get();
public override string Description => "Gets an SPR. (Signed peer record, used for bootstrapping.)";
protected override async Task<bool> OnInvoke(CommandContext context)
protected override async Task Invoke(CommandContext context)
{
if (ShouldUpdate())
{
return true;
}
await ReplyWithRandomSpr(context);
return false;
}
protected override async Task Execute(CommandContext context, ICodexNodeGroup codexGroup)
public void Add(string spr)
{
lastUpdate = DateTime.UtcNow;
sprCache.Clear();
if (knownSprs.Contains(spr)) return;
knownSprs.Add(spr);
}
var infos = codexGroup.Select(c => c.GetDebugInfo()).ToArray();
sprCache.AddRange(infos.Select(i => i.spr));
public void Clear()
{
knownSprs.Clear();
}
await ReplyWithRandomSpr(context);
public string[] Get()
{
return knownSprs.ToArray();
}
private async Task ReplyWithRandomSpr(CommandContext context)
{
if (!sprCache.Any())
if (!knownSprs.Any())
{
await context.Followup("I'm sorry, no SPRs are available... :c");
return;
}
var i = random.Next(0, sprCache.Count);
var spr = sprCache[i];
await context.Followup($"Your SPR: '{spr}'");
}
private bool ShouldUpdate()
{
return (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10);
var i = random.Next(0, knownSprs.Count);
var spr = knownSprs[i];
await context.Followup($"Your SPR: `{spr}`");
}
}
}

View File

@ -27,8 +27,6 @@ namespace BiblioTech.Commands
return;
}
// private commands
var result = Program.UserRepo.AssociateUserWithAddress(user, data);
if (result)
{

View File

@ -19,6 +19,12 @@ namespace BiblioTech
[Uniform("admin-channel-name", "ac", "ADMINCHANNELNAME", true, "Name of the Discord server channel where admin commands are allowed.")]
public string AdminChannelName { get; set; } = "admin-channel";
[Uniform("kube-config", "kc", "KUBECONFIG", true, "Path to Kubeconfig file. Use a Kubeconfig with read-only access.")]
public string KubeConfigFile { get; set; } = "null";
[Uniform("kube-namespace", "kn", "KUBENAMESPACE", true, "Kubernetes namespace.")]
public string KubeNamespace { get; set; } = string.Empty;
public string EndpointsPath
{
get

View File

@ -1,89 +0,0 @@
using CodexPlugin;
using Discord;
using Newtonsoft.Json;
namespace BiblioTech
{
public class DeploymentsFilesMonitor
{
private readonly List<CodexDeployment> deployments = new List<CodexDeployment>();
public void Initialize()
{
LoadDeployments();
}
public CodexDeployment[] GetDeployments()
{
return deployments.ToArray();
}
public async Task<bool> DownloadDeployment(IAttachment file)
{
using var http = new HttpClient();
var response = await http.GetAsync(file.Url);
var str = await response.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(str)) return false;
try
{
var deploy = JsonConvert.DeserializeObject<CodexDeployment>(str);
if (deploy != null)
{
var targetFile = Path.Combine(Program.Config.EndpointsPath, Guid.NewGuid().ToString().ToLowerInvariant() + ".json");
File.WriteAllText(targetFile, str);
deployments.Add(deploy);
return true;
}
}
catch { }
return false;
}
public bool DeleteDeployment(string deploymentName)
{
var path = Program.Config.EndpointsPath;
if (!Directory.Exists(path)) return false;
var files = Directory.GetFiles(path);
foreach (var file in files)
{
var deploy = ProcessFile(file);
if (deploy != null && deploy.Metadata.Name == deploymentName)
{
File.Delete(file);
deployments.Remove(deploy);
return true;
}
}
return false;
}
private void LoadDeployments()
{
var path = Program.Config.EndpointsPath;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
File.WriteAllText(Path.Combine(path, "readme.txt"), "Place codex-deployment.json here.");
return;
}
var files = Directory.GetFiles(path);
deployments.AddRange(files.Select(ProcessFile).Where(d => d != null).Cast<CodexDeployment>());
}
private CodexDeployment? ProcessFile(string filename)
{
try
{
var lines = string.Join(" ", File.ReadAllLines(filename));
return JsonConvert.DeserializeObject<CodexDeployment>(lines);
}
catch
{
return null;
}
}
}
}

View File

@ -4,30 +4,99 @@ namespace BiblioTech.Options
{
public class CommandContext
{
private const string AttachmentFolder = "attachments";
public CommandContext(SocketSlashCommand command, IReadOnlyCollection<SocketSlashCommandDataOption> options)
{
Command = command;
Options = options;
var attachmentPath = Path.Combine(Program.Config.DataPath, AttachmentFolder);
if (Directory.Exists(attachmentPath))
{
Directory.CreateDirectory(attachmentPath);
}
}
public SocketSlashCommand Command { get; }
public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; }
public async Task Followup(string message)
public async Task Followup(string line)
{
await Command.ModifyOriginalResponseAsync(m =>
{
m.Content = message;
});
var array = line.Split(Environment.NewLine);
await Followup(array);
}
public async Task FollowupWithAttachement(string filename, string content)
public async Task Followup(string[] lines)
{
using var fileStream = new MemoryStream();
using var streamWriter = new StreamWriter(fileStream);
await streamWriter.WriteAsync(content);
var chunker = new LineChunker(lines);
var chunks = chunker.GetChunks();
if (!chunks.Any()) return;
await Command.FollowupWithFileAsync(fileStream, filename);
// First chunk is a modification of the original message.
// Everything after that, we must create a new message.
var first = chunks.First();
chunks.RemoveAt(0);
await Command.ModifyOriginalResponseAsync(m =>
{
m.Content = FormatChunk(first);
});
foreach (var remaining in chunks)
{
await Command.FollowupAsync(FormatChunk(remaining));
}
}
private string FormatChunk(string[] chunk)
{
return string.Join(Environment.NewLine, chunk);
}
}
public class LineChunker
{
private readonly List<string> input;
private readonly int maxCharacters;
public LineChunker(string[] input, int maxCharacters = 1950)
{
this.input = input.ToList();
this.maxCharacters = maxCharacters;
}
public List<string[]> GetChunks()
{
var result = new List<string[]>();
while (input.Any())
{
result.Add(GetChunk());
}
return result;
}
private string[] GetChunk()
{
var totalLength = 0;
var result = new List<string>();
while (input.Any())
{
var nextLine = input[0];
var nextLength = totalLength + nextLine.Length;
if (nextLength > maxCharacters)
{
return result.ToArray();
}
input.RemoveAt(0);
result.Add(nextLine);
totalLength += nextLine.Length;
}
return result.ToArray();
}
}
}

View File

@ -1,9 +1,7 @@
using ArgsUniform;
using BiblioTech.Commands;
using Core;
using Discord;
using Discord.WebSocket;
using Logging;
namespace BiblioTech
{
@ -12,7 +10,6 @@ namespace BiblioTech
private DiscordSocketClient client = null!;
public static Configuration Config { get; private set; } = null!;
public static DeploymentsFilesMonitor DeploymentFilesMonitor { get; } = new DeploymentsFilesMonitor();
public static UserRepo UserRepo { get; } = new UserRepo();
public static AdminChecker AdminChecker { get; } = new AdminChecker();
@ -21,8 +18,6 @@ namespace BiblioTech
var uniformArgs = new ArgsUniform<Configuration>(PrintHelp, args);
Config = uniformArgs.Parse();
DeploymentFilesMonitor.Initialize();
EnsurePath(Config.DataPath);
EnsurePath(Config.UserDataPath);
EnsurePath(Config.EndpointsPath);
@ -36,25 +31,14 @@ namespace BiblioTech
client = new DiscordSocketClient();
client.Log += Log;
ProjectPlugin.Load<CodexPlugin.CodexPlugin>();
ProjectPlugin.Load<GethPlugin.GethPlugin>();
ProjectPlugin.Load<CodexContractsPlugin.CodexContractsPlugin>();
var entryPoint = new EntryPoint(new ConsoleLog(), new KubernetesWorkflow.Configuration(
kubeConfigFile: null,
operationTimeout: TimeSpan.FromMinutes(5),
retryDelay: TimeSpan.FromSeconds(10),
kubernetesNamespace: "not-applicable"), "datafiles");
var ci = entryPoint.CreateInterface();
var associateCommand = new UserAssociateCommand();
var sprCommand = new SprCommand();
var handler = new CommandHandler(client,
new GetBalanceCommand(ci, associateCommand),
new MintCommand(ci, associateCommand),
new SprCommand(ci),
new GetBalanceCommand(associateCommand),
new MintCommand(associateCommand),
sprCommand,
associateCommand,
new AdminCommand(ci)
new AdminCommand(sprCommand)
);
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);

View File

@ -2,7 +2,6 @@
using Discord;
using GethPlugin;
using Newtonsoft.Json;
using Utils;
namespace BiblioTech
{
@ -76,17 +75,17 @@ namespace BiblioTech
return result.ToArray();
}
public string GetUserReport(IUser user)
public string[] GetUserReport(IUser user)
{
var userData = GetUserData(user);
if (userData == null) return "User has not joined the test net.";
if (userData == null) return new[] { "User has not joined the test net." };
return userData.CreateOverview();
}
public string GetUserReport(EthAddress ethAddress)
public string[] GetUserReport(EthAddress ethAddress)
{
var userData = GetUserDataForAddress(ethAddress);
if (userData == null) return "No user is using this eth address.";
if (userData == null) return new[] { "No user is using this eth address." };
return userData.CreateOverview();
}
@ -196,14 +195,15 @@ namespace BiblioTech
public List<UserAssociateAddressEvent> AssociateEvents { get; }
public List<UserMintEvent> MintEvents { get; }
public string CreateOverview()
public string[] CreateOverview()
{
var nl = Environment.NewLine;
return
$"name: '{Name}' - id:{DiscordId}{nl}" +
$"joined: {CreatedUtc.ToString("o")}{nl}" +
$"current address: {CurrentAddress}{nl}" +
$"{AssociateEvents.Count + MintEvents.Count} total bot events.";
return new[]
{
$"name: '{Name}' - id:{DiscordId}",
$"joined: {CreatedUtc.ToString("o")}",
$"current address: {CurrentAddress}",
$"{AssociateEvents.Count + MintEvents.Count} total bot events."
};
}
}

View File

@ -105,7 +105,6 @@ namespace CodexNetDeployer
return new CodexTestNetConfig
{
PublicNatIP = config.PublicIP,
PublicDiscoveryPort = Convert.ToInt32(discPort),
PublicListenPort = Convert.ToInt32(listenPort)
};

View File

@ -86,18 +86,12 @@ namespace CodexNetDeployer
[Uniform("public-testnet", "ptn", "PUBLICTESTNET", false, "If true, deployment is created for public exposure. Default is false.")]
public bool IsPublicTestNet { get; set; } = false;
[Uniform("public-ip", "pip", "PUBLICIP", false, "Required if public-testnet is true. Public IP address used by nodes for network annoucements.")]
public string PublicIP { get; set; } = string.Empty;
[Uniform("public-discports", "pdps", "PUBLICDISCPORTS", false, "Required if public-testnet is true. Comma-separated port numbers used for discovery. Number must match number of nodes.")]
public string PublicDiscPorts { get; set; } = string.Empty;
[Uniform("public-listenports", "plps", "PUBLICLISTENPORTS", false, "Required if public-testnet is true. Comma-separated port numbers used for listening. Number must match number of nodes.")]
public string PublicListenPorts { get; set; } = string.Empty;
[Uniform("public-gethip", "pgdp", "PUBLICGETHIP", false, "Required if public-testnet is true. Geth's public IP address.")]
public string PublicGethIP { get; set; } = string.Empty;
[Uniform("public-gethdiscport", "pgdp", "PUBLICGETHDISCPORT", false, "Required if public-testnet is true. Single port number used for Geth's public discovery port.")]
public int PublicGethDiscPort { get; set; }
@ -150,9 +144,11 @@ namespace CodexNetDeployer
if (IsPublicTestNet)
{
if (string.IsNullOrEmpty(PublicIP)) errors.Add("Public IP required when deploying public testnet.");
if (PublicDiscPorts.Split(",").Length != NumberOfCodexNodes) errors.Add("Number of public discovery-ports provided does not match number of codex nodes.");
if (PublicListenPorts.Split(",").Length != NumberOfCodexNodes) errors.Add("Number of public listen-ports provided does not match number of codex nodes.");
if (NumberOfCodexNodes > 0)
{
if (PublicDiscPorts.Split(",").Length != NumberOfCodexNodes) errors.Add("Number of public discovery-ports provided does not match number of codex nodes.");
if (PublicListenPorts.Split(",").Length != NumberOfCodexNodes) errors.Add("Number of public listen-ports provided does not match number of codex nodes.");
}
if (PublicGethDiscPort == 0) errors.Add("Geth public discovery port is not set.");
if (PublicGethListenPort == 0) errors.Add("Geth public listen port is not set.");
}

View File

@ -82,7 +82,7 @@ namespace CodexNetDeployer
var codexInstances = CreateCodexInstances(startResults);
var discordBotContainer = DeployDiscordBot(ci);
var discordBotContainer = DeployDiscordBot(ci, gethDeployment, contractsDeployment);
return new CodexDeployment(codexInstances, gethDeployment, contractsDeployment, metricsService, discordBotContainer, CreateMetadata(startUtc));
}
@ -113,7 +113,6 @@ namespace CodexNetDeployer
if (config.IsPublicTestNet)
{
s.AsPublicTestNet(new GethTestNetConfig(
publicIp: config.PublicGethIP,
discoveryPort: config.PublicGethDiscPort,
listenPort: config.PublicGethListenPort
));
@ -121,17 +120,29 @@ namespace CodexNetDeployer
});
}
private RunningContainers? DeployDiscordBot(CoreInterface ci)
private RunningContainers? DeployDiscordBot(CoreInterface ci, GethDeployment gethDeployment, CodexContractsDeployment contractsDeployment)
{
if (!config.DeployDiscordBot) return null;
Log("Deploying Discord bot...");
var addr = gethDeployment.Container.GetInternalAddress(GethContainerRecipe.HttpPortTag);
var info = new DiscordBotGethInfo(
host: addr.Host,
port: addr.Port,
privKey: gethDeployment.Account.PrivateKey,
marketplaceAddress: contractsDeployment.MarketplaceAddress,
tokenAddress: contractsDeployment.TokenAddress,
abi: contractsDeployment.Abi
);
var rc = ci.DeployCodexDiscordBot(new DiscordBotStartupConfig(
name: "discordbot-" + config.DeploymentName,
token: config.DiscordBotToken,
serverName: config.DiscordBotServerName,
adminRoleName: config.DiscordBotAdminRoleName,
adminChannelName: config.DiscordBotAdminChannelName)
adminChannelName: config.DiscordBotAdminChannelName,
kubeNamespace: config.KubeNamespace,
gethInfo: info)
{
DataPath = config.DiscordBotDataPath
});
@ -142,7 +153,7 @@ namespace CodexNetDeployer
private RunningContainers? StartMetricsService(CoreInterface ci, List<CodexNodeStartResult> startResults)
{
if (!config.MetricsScraper) return null;
if (!config.MetricsScraper || !startResults.Any()) return null;
Log("Starting metrics service...");
@ -176,7 +187,7 @@ namespace CodexNetDeployer
private void CheckPeerConnectivity(List<CodexNodeStartResult> codexContainers)
{
if (!config.CheckPeerConnection) return;
if (!config.CheckPeerConnection || !codexContainers.Any()) return;
Log("Starting peer connectivity check for deployed nodes...");
peerConnectivityChecker.CheckConnectivity(codexContainers);

View File

@ -16,12 +16,10 @@ dotnet run \
--check-connect=0 \
\
--public-testnet=1 \
--public-ip=1.2.3.4 \
--public-discports=20010,20020,20030 \
--public-listenports=20011,20021,20031 \
--public-gethip=1.2.3.5 \
--public-gethdiscport=20040 \
--public-gethlistenport=20041 \
--public-discports=30010,30020,30030 \
--public-listenports=30011,30021,30031 \
--public-gethdiscport=30040 \
--public-gethlistenport=30041 \
\
--discord-bot=1 \
--dbot-token=tokenhere \