Skip to content

Commit

Permalink
Have orchestrator generate a key, be able to create/start containers
Browse files Browse the repository at this point in the history
  • Loading branch information
kayabaNerve committed Feb 7, 2024

Verified

This commit was signed with the committer’s verified signature.
Mararok Mararok
1 parent 497bb3b commit 370a7bf
Showing 6 changed files with 232 additions and 22 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions orchestration/Cargo.toml
Original file line number Diff line number Diff line change
@@ -26,3 +26,5 @@ transcript = { package = "flexible-transcript", path = "../crypto/transcript", d
ciphersuite = { path = "../crypto/ciphersuite", default-features = false, features = ["std", "ristretto"] }

zalloc = { path = "../common/zalloc" }

home = "0.5"
4 changes: 3 additions & 1 deletion orchestration/src/coordinator.rs
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ pub fn coordinator(
orchestration_path: &Path,
network: Network,
coordinator_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
serai_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
) {
let db = network.db();
let longer_reattempts = if network == Network::Dev { "longer-reattempts" } else { "" };
@@ -27,9 +28,10 @@ RUN apt install -y ca-certificates
"#;

let env_vars = [
("MESSAGE_QUEUE_RPC", format!("serai-{}-message-queue", network.label())),
("MESSAGE_QUEUE_KEY", hex::encode(coordinator_key.to_repr())),
("DB_PATH", "./coordinator-db".to_string()),
("SERAI_KEY", String::new()), // TODO
("SERAI_KEY", hex::encode(serai_key.to_repr())),
("SERAI_HOSTNAME", format!("serai-{}", network.label())),
("RUST_LOG", "serai_coordinator=debug,tributary_chain=debug,tendermint=debug".to_string()),
];
48 changes: 48 additions & 0 deletions orchestration/src/docker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::{collections::HashSet, path::Path, env, process::Command};

use crate::Network;

pub fn build(orchestration_path: &Path, network: Network, name: &str) {
// Else, hold the lock while we build
let mut repo_path = env::current_exe().unwrap();
repo_path.pop();
if repo_path.as_path().ends_with("deps") {
repo_path.pop();
}
assert!(repo_path.as_path().ends_with("debug") || repo_path.as_path().ends_with("release"));
repo_path.pop();
assert!(repo_path.as_path().ends_with("target"));
repo_path.pop();

let mut dockerfile_path = orchestration_path.to_path_buf();
if HashSet::from(["bitcoin", "ethereum", "monero"]).contains(name) {
dockerfile_path = dockerfile_path.join("coins");
}
if name.contains("-processor") {
dockerfile_path =
dockerfile_path.join("processor").join(name.split('-').next().unwrap()).join("Dockerfile");
} else {
dockerfile_path = dockerfile_path.join(name).join("Dockerfile");
}

println!("Building {}...", &name);

if !Command::new("docker")
.current_dir(&repo_path)
.arg("build")
.arg("-f")
.arg(dockerfile_path)
.arg(".")
.arg("-t")
.arg(format!("serai-{}-{name}-img", network.label()))
.spawn()
.unwrap()
.wait()
.unwrap()
.success()
{
panic!("failed to build {name}");
}

println!("Built!");
}
196 changes: 176 additions & 20 deletions orchestration/src/main.rs
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
// TODO: Generate keys for a validator and the infra

use core::ops::Deref;
use std::{env, path::PathBuf, io::Write, fs};
use std::{collections::HashSet, env, path::PathBuf, io::Write, fs, process::Command};

use zeroize::Zeroizing;

@@ -11,7 +11,13 @@ use rand_chacha::ChaCha20Rng;

use transcript::{Transcript, RecommendedTranscript};

use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
use ciphersuite::{
group::{
ff::{Field, PrimeField},
GroupEncoding,
},
Ciphersuite, Ristretto,
};

mod mimalloc;
use mimalloc::mimalloc;
@@ -31,6 +37,8 @@ use coordinator::coordinator;
mod serai;
use serai::serai;

mod docker;

#[global_allocator]
static ALLOCATOR: zalloc::ZeroizingAlloc<std::alloc::System> =
zalloc::ZeroizingAlloc(std::alloc::System);
@@ -173,20 +181,22 @@ pub fn write_dockerfile(path: PathBuf, dockerfile: &str) {
fs::File::create(path).unwrap().write_all(dockerfile.as_bytes()).unwrap();
}

fn orchestration_path(network: Network) -> PathBuf {
let mut repo_path = env::current_exe().unwrap();
repo_path.pop();
assert!(repo_path.as_path().ends_with("debug"));
repo_path.pop();
assert!(repo_path.as_path().ends_with("target"));
repo_path.pop();

let mut orchestration_path = repo_path.clone();
orchestration_path.push("orchestration");
orchestration_path.push(network.label());
orchestration_path
}

fn dockerfiles(network: Network) {
let orchestration_path = {
let mut repo_path = env::current_exe().unwrap();
repo_path.pop();
assert!(repo_path.as_path().ends_with("debug"));
repo_path.pop();
assert!(repo_path.as_path().ends_with("target"));
repo_path.pop();

let mut orchestration_path = repo_path.clone();
orchestration_path.push("orchestration");
orchestration_path.push(network.label());
orchestration_path
};
let orchestration_path = orchestration_path(network);

bitcoin(&orchestration_path, network);
ethereum(&orchestration_path);
@@ -195,9 +205,14 @@ fn dockerfiles(network: Network) {
monero_wallet_rpc(&orchestration_path);
}

// TODO: Generate infra keys in key_gen, yet service entropy here?

// Generate entropy for the infrastructure keys
let mut entropy = [0; 32];
OsRng.fill_bytes(&mut entropy);
let mut entropy = Zeroizing::new([0; 32]);
// Only use actual entropy if this isn't a development environment
if network != Network::Dev {
OsRng.fill_bytes(entropy.as_mut());
}
let mut transcript = RecommendedTranscript::new(b"Serai Orchestrator Transcript");
transcript.append_message(b"entropy", entropy);
let mut new_rng = |label| ChaCha20Rng::from_seed(transcript.rng_seed(label));
@@ -246,12 +261,153 @@ fn dockerfiles(network: Network) {
);
processor(&orchestration_path, network, "monero", coordinator_key.1, monero_key.0, new_entropy());

coordinator(&orchestration_path, network, coordinator_key.0);
let serai_key = {
let serai_key = Zeroizing::new(
fs::read(home::home_dir().unwrap().join(".serai").join(network.label()).join("key"))
.expect("couldn't read key for this network"),
);
let mut serai_key_repr =
Zeroizing::new(<<Ristretto as Ciphersuite>::F as PrimeField>::Repr::default());
serai_key_repr.as_mut().copy_from_slice(serai_key.as_ref());
Zeroizing::new(<Ristretto as Ciphersuite>::F::from_repr(*serai_key_repr).unwrap())
};

coordinator(&orchestration_path, network, coordinator_key.0, serai_key);

serai(&orchestration_path, network);
}

fn key_gen(network: Network) {
let key = <Ristretto as Ciphersuite>::F::random(&mut OsRng);
let serai_dir = home::home_dir().unwrap().join(".serai");
fs::create_dir(&serai_dir).expect("couldn't create ~/.serai");

fs::create_dir(serai_dir.join(network.label())).expect("couldn't create ~/.serai/{network}");
fs::write(serai_dir.join(network.label()).join("key"), key.to_repr())
.expect("couldn't write key");
println!(
"Public Key: {}",
hex::encode((<Ristretto as Ciphersuite>::generator() * key).to_bytes())
);
}

fn start(network: Network, services: HashSet<String>) {
for service in services {
println!("Starting {service}");
let name = match service.as_ref() {
"serai" => "serai",
"coordinator" => "coordinator",
"message-queue" => "message-queue",
"bitcoin-daemon" => "bitcoin",
"bitcoin-processor" => "bitcoin-processor",
"monero-daemon" => "monero",
"monero-processor" => "monero-processor",
"monero-wallet-rpc" => "monero-wallet-rpc",
_ => panic!("starting unrecognized service"),
};

// Build it, if it wasn't already built
let docker_name = format!("serai-{}-{name}", network.label());
let docker_image = format!("{docker_name}-img");
if !Command::new("docker")
.arg("inspect")
.arg("-f")
.arg("{{ .Metadata.LastTagTime }}")
.arg(&docker_image)
.status()
.unwrap()
.success()
{
println!("Building {service}");
docker::build(&orchestration_path(network), network, name);
}

if !Command::new("docker").arg("inspect").arg(&docker_name).status().unwrap().success() {
// Create the docker container
println!("Creating new container for {service}");
assert!(
Command::new("docker")
.arg("create")
.arg("--name")
.arg(&docker_name)
.arg("--network")
.arg("bridge")
.arg(docker_image)
.status()
.unwrap()
.success(),
"couldn't create the container"
);
}

// Start it
// TODO: Check it successfully started
println!("Starting existing container for {service}");
let _ = Command::new("docker").arg("start").arg(docker_name).output();
}
}

fn main() {
dockerfiles(Network::Dev);
dockerfiles(Network::Testnet);
let help = || -> ! {
println!(
r#"
Serai Orchestrator v0.0.1
Commands:
key_gen *network*
Generates a key for the validator.
setup *network*
Generate infrastructure keys and the Dockerfiles for every Serai service.
start *network* [service1, service2...]
Start the specified services for the specified network ("dev" or "testnet").
- `serai`
- `coordinator`
- `message-queue`
- `bitcoin-daemon`
- `bitcoin-processor`
- `monero-daemon`
- `monero-processor`
- `monero-wallet-rpc` (if "dev")
are valid services.
`*network*-processor` will automatically start `*network*-daemon`.
"#
);
std::process::exit(1);
};

let mut args = env::args();
args.next();
let command = args.next();
let network = match args.next().as_ref().map(AsRef::as_ref) {
Some("dev") => Network::Dev,
Some("testnet") => Network::Testnet,
Some(_) => panic!(r#"unrecognized network. only "dev" and "testnet" are recognized"#),
None => help(),
};

match command.as_ref().map(AsRef::as_ref) {
Some("key_gen") => {
key_gen(network);
}
Some("setup") => {
dockerfiles(network);
}
Some("start") => {
let mut services = HashSet::new();
for arg in args {
if let Some(ext_network) = arg.strip_suffix("-processor") {
services.insert(ext_network.to_string() + "-daemon");
}
services.insert(arg);
}

start(network, services);
}
_ => help(),
}
}
3 changes: 2 additions & 1 deletion orchestration/src/processor.rs
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ RUN apt install -y ca-certificates
const RPC_USER: &str = "serai";
const RPC_PASS: &str = "seraidex";
// TODO: Isolate networks
let hostname = format!("{coin}-{}", network.label());
let hostname = format!("serai-{}-{coin}", network.label());
let port = match coin {
"bitcoin" => 8332,
"ethereum" => return, // TODO
@@ -40,6 +40,7 @@ RUN apt install -y ca-certificates
};

let env_vars = [
("MESSAGE_QUEUE_RPC", format!("serai-{}-message_queue", network.label())),
("MESSAGE_QUEUE_KEY", hex::encode(coin_key.to_repr())),
("ENTROPY", hex::encode(entropy.as_ref())),
("NETWORK", coin.to_string()),

0 comments on commit 370a7bf

Please sign in to comment.