From 13b147cbf663152ce409ec8376de0e6d0411dbe4 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 20 Mar 2024 08:23:23 -0400 Subject: [PATCH 01/29] Reduce coordinator tests contention re: cosign messages --- tests/coordinator/src/lib.rs | 318 +++++++++++++-------------- tests/coordinator/src/tests/batch.rs | 2 +- 2 files changed, 158 insertions(+), 162 deletions(-) diff --git a/tests/coordinator/src/lib.rs b/tests/coordinator/src/lib.rs index 0c197a92..0541c4fd 100644 --- a/tests/coordinator/src/lib.rs +++ b/tests/coordinator/src/lib.rs @@ -5,7 +5,10 @@ use std::{ time::Duration, }; -use tokio::{task::AbortHandle, sync::Mutex as AsyncMutex}; +use tokio::{ + task::AbortHandle, + sync::{Mutex as AsyncMutex, mpsc}, +}; use rand_core::{RngCore, OsRng}; @@ -96,7 +99,6 @@ pub struct Handles { pub(crate) message_queue: String, } -#[derive(Clone)] pub struct Processor { network: NetworkId, @@ -104,7 +106,8 @@ pub struct Processor { #[allow(unused)] handles: Handles, - queue: Arc>, + msgs: mpsc::UnboundedReceiver, + queue_for_sending: MessageQueue, abort_handle: Option>, substrate_key: Arc::F>>>>, @@ -145,156 +148,173 @@ impl Processor { // The Serai RPC may or may not be started // Assume it is and continue, so if it's a few seconds late, it's still within tolerance + // Create the queue + let mut queue = ( + 0, + Arc::new(MessageQueue::new( + Service::Processor(network), + message_queue_rpc.clone(), + Zeroizing::new(processor_key), + )), + ); + + let (msg_send, msg_recv) = mpsc::unbounded_channel(); + + let substrate_key = Arc::new(AsyncMutex::new(None)); let mut res = Processor { network, serai_rpc, handles, - queue: Arc::new(AsyncMutex::new(( - 0, - 0, - MessageQueue::new( - Service::Processor(network), - message_queue_rpc, - Zeroizing::new(processor_key), - ), - ))), + queue_for_sending: MessageQueue::new( + Service::Processor(network), + message_queue_rpc, + Zeroizing::new(processor_key), + ), + msgs: msg_recv, abort_handle: None, - substrate_key: Arc::new(AsyncMutex::new(None)), + substrate_key: substrate_key.clone(), }; - // Handle any cosigns which come up - res.abort_handle = Some(Arc::new( - tokio::spawn({ - let mut res = res.clone(); - async move { - loop { - tokio::task::yield_now().await; + // Spawn a task to handle cosigns and forward messages as appropriate + let abort_handle = tokio::spawn({ + async move { + loop { + // Get new messages + let (next_recv_id, queue) = &mut queue; + let msg = queue.next(Service::Coordinator).await; + assert_eq!(msg.from, Service::Coordinator); + assert_eq!(msg.id, *next_recv_id); + queue.ack(Service::Coordinator, msg.id).await; + *next_recv_id += 1; - let msg = { - let mut queue_lock = res.queue.lock().await; - let (_, next_recv_id, queue) = &mut *queue_lock; - let Ok(msg) = - tokio::time::timeout(Duration::from_secs(1), queue.next(Service::Coordinator)) - .await - else { - continue; - }; - assert_eq!(msg.from, Service::Coordinator); - assert_eq!(msg.id, *next_recv_id); + let msg_msg = borsh::from_slice(&msg.msg).unwrap(); - let msg_msg = borsh::from_slice(&msg.msg).unwrap(); - // Remove any BatchReattempts clogging the pipe - // TODO: Set up a wrapper around serai-client so we aren't throwing this away yet - // leave it for the tests - if matches!( - msg_msg, - messages::CoordinatorMessage::Coordinator( - messages::coordinator::CoordinatorMessage::BatchReattempt { .. } - ) - ) { - queue.ack(Service::Coordinator, msg.id).await; - *next_recv_id += 1; - continue; - } - if !is_cosign_message(&msg_msg) { - continue; - }; - queue.ack(Service::Coordinator, msg.id).await; - *next_recv_id += 1; - msg_msg - }; + // Remove any BatchReattempts clogging the pipe + // TODO: Set up a wrapper around serai-client so we aren't throwing this away yet + // leave it for the tests + if matches!( + msg_msg, + messages::CoordinatorMessage::Coordinator( + messages::coordinator::CoordinatorMessage::BatchReattempt { .. } + ) + ) { + continue; + } - struct CurrentCosign { - block_number: u64, - block: [u8; 32], - } - static CURRENT_COSIGN: OnceLock>> = OnceLock::new(); - let mut current_cosign = - CURRENT_COSIGN.get_or_init(|| AsyncMutex::new(None)).lock().await; - match msg { - // If this is a CosignSubstrateBlock, reset the CurrentCosign - // While technically, each processor should individually track the current cosign, - // this is fine for current testing purposes - CoordinatorMessage::Coordinator( - messages::coordinator::CoordinatorMessage::CosignSubstrateBlock { - id, - block_number, + if !is_cosign_message(&msg_msg) { + msg_send.send(msg_msg).unwrap(); + continue; + } + let msg = msg_msg; + + let send_message = |msg: ProcessorMessage| async move { + queue + .queue( + Metadata { + from: Service::Processor(network), + to: Service::Coordinator, + intent: msg.intent(), }, - ) => { - let SubstrateSignId { - id: SubstrateSignableId::CosigningSubstrateBlock(block), .. - } = id - else { - panic!("CosignSubstrateBlock didn't have CosigningSubstrateBlock ID") - }; + borsh::to_vec(&msg).unwrap(), + ) + .await; + }; - let new_cosign = CurrentCosign { block_number, block }; - if current_cosign.is_none() || (current_cosign.as_ref().unwrap().block != block) { - *current_cosign = Some(new_cosign); + struct CurrentCosign { + block_number: u64, + block: [u8; 32], + } + static CURRENT_COSIGN: OnceLock>> = OnceLock::new(); + let mut current_cosign = + CURRENT_COSIGN.get_or_init(|| AsyncMutex::new(None)).lock().await; + match msg { + // If this is a CosignSubstrateBlock, reset the CurrentCosign + // While technically, each processor should individually track the current cosign, + // this is fine for current testing purposes + CoordinatorMessage::Coordinator( + messages::coordinator::CoordinatorMessage::CosignSubstrateBlock { id, block_number }, + ) => { + let SubstrateSignId { + id: SubstrateSignableId::CosigningSubstrateBlock(block), .. + } = id + else { + panic!("CosignSubstrateBlock didn't have CosigningSubstrateBlock ID") + }; + + let new_cosign = CurrentCosign { block_number, block }; + if current_cosign.is_none() || (current_cosign.as_ref().unwrap().block != block) { + *current_cosign = Some(new_cosign); + } + send_message( + messages::coordinator::ProcessorMessage::CosignPreprocess { + id: id.clone(), + preprocesses: vec![[raw_i; 64]], } - res - .send_message(messages::coordinator::ProcessorMessage::CosignPreprocess { - id: id.clone(), - preprocesses: vec![[raw_i; 64]], - }) - .await; - } - CoordinatorMessage::Coordinator( - messages::coordinator::CoordinatorMessage::SubstratePreprocesses { id, .. }, - ) => { - // TODO: Assert the ID matches CURRENT_COSIGN - // TODO: Verify the received preprocesses - res - .send_message(messages::coordinator::ProcessorMessage::SubstrateShare { - id, - shares: vec![[raw_i; 32]], - }) - .await; - } - CoordinatorMessage::Coordinator( - messages::coordinator::CoordinatorMessage::SubstrateShares { .. }, - ) => { - // TODO: Assert the ID matches CURRENT_COSIGN - // TODO: Verify the shares - - let block_number = current_cosign.as_ref().unwrap().block_number; - let block = current_cosign.as_ref().unwrap().block; - - let substrate_key = res.substrate_key.lock().await.clone().unwrap(); - - // Expand to a key pair as Schnorrkel expects - // It's the private key + 32-bytes of entropy for nonces + the public key - let mut schnorrkel_key_pair = [0; 96]; - schnorrkel_key_pair[.. 32].copy_from_slice(&substrate_key.to_repr()); - OsRng.fill_bytes(&mut schnorrkel_key_pair[32 .. 64]); - schnorrkel_key_pair[64 ..].copy_from_slice( - &(::generator() * *substrate_key).to_bytes(), - ); - let signature = Signature( - schnorrkel::keys::Keypair::from_bytes(&schnorrkel_key_pair) - .unwrap() - .sign_simple(b"substrate", &cosign_block_msg(block_number, block)) - .to_bytes(), - ); - - res - .send_message(messages::coordinator::ProcessorMessage::CosignedBlock { - block_number, - block, - signature: signature.0.to_vec(), - }) - .await; - } - _ => panic!("unexpected message passed is_cosign_message"), + .into(), + ) + .await; } + CoordinatorMessage::Coordinator( + messages::coordinator::CoordinatorMessage::SubstratePreprocesses { id, .. }, + ) => { + // TODO: Assert the ID matches CURRENT_COSIGN + // TODO: Verify the received preprocesses + send_message( + messages::coordinator::ProcessorMessage::SubstrateShare { + id, + shares: vec![[raw_i; 32]], + } + .into(), + ) + .await; + } + CoordinatorMessage::Coordinator( + messages::coordinator::CoordinatorMessage::SubstrateShares { .. }, + ) => { + // TODO: Assert the ID matches CURRENT_COSIGN + // TODO: Verify the shares + + let block_number = current_cosign.as_ref().unwrap().block_number; + let block = current_cosign.as_ref().unwrap().block; + + let substrate_key = substrate_key.lock().await.clone().unwrap(); + + // Expand to a key pair as Schnorrkel expects + // It's the private key + 32-bytes of entropy for nonces + the public key + let mut schnorrkel_key_pair = [0; 96]; + schnorrkel_key_pair[.. 32].copy_from_slice(&substrate_key.to_repr()); + OsRng.fill_bytes(&mut schnorrkel_key_pair[32 .. 64]); + schnorrkel_key_pair[64 ..].copy_from_slice( + &(::generator() * *substrate_key).to_bytes(), + ); + let signature = Signature( + schnorrkel::keys::Keypair::from_bytes(&schnorrkel_key_pair) + .unwrap() + .sign_simple(b"substrate", &cosign_block_msg(block_number, block)) + .to_bytes(), + ); + + send_message( + messages::coordinator::ProcessorMessage::CosignedBlock { + block_number, + block, + signature: signature.0.to_vec(), + } + .into(), + ) + .await; + } + _ => panic!("unexpected message passed is_cosign_message"), } } - }) - .abort_handle(), - )); + } + }) + .abort_handle(); + + res.abort_handle = Some(Arc::new(abort_handle)); res } @@ -307,9 +327,8 @@ impl Processor { pub async fn send_message(&mut self, msg: impl Into) { let msg: ProcessorMessage = msg.into(); - let mut queue_lock = self.queue.lock().await; - let (next_send_id, _, queue) = &mut *queue_lock; - queue + self + .queue_for_sending .queue( Metadata { from: Service::Processor(self.network), @@ -319,36 +338,13 @@ impl Processor { borsh::to_vec(&msg).unwrap(), ) .await; - *next_send_id += 1; - } - - async fn recv_message_inner(&mut self) -> CoordinatorMessage { - loop { - tokio::task::yield_now().await; - - let mut queue_lock = self.queue.lock().await; - let (_, next_recv_id, queue) = &mut *queue_lock; - let msg = queue.next(Service::Coordinator).await; - assert_eq!(msg.from, Service::Coordinator); - assert_eq!(msg.id, *next_recv_id); - - // If this is a cosign message, let the cosign task handle it - let msg_msg = borsh::from_slice(&msg.msg).unwrap(); - if is_cosign_message(&msg_msg) { - continue; - } - - queue.ack(Service::Coordinator, msg.id).await; - *next_recv_id += 1; - return msg_msg; - } } /// Receive a message from the coordinator as a processor. pub async fn recv_message(&mut self) -> CoordinatorMessage { // Set a timeout of 20 minutes to allow effectively any protocol to occur without a fear of // an arbitrary timeout cutting it short - tokio::time::timeout(Duration::from_secs(20 * 60), self.recv_message_inner()).await.unwrap() + tokio::time::timeout(Duration::from_secs(20 * 60), self.msgs.recv()).await.unwrap().unwrap() } pub async fn set_substrate_key( diff --git a/tests/coordinator/src/tests/batch.rs b/tests/coordinator/src/tests/batch.rs index 67bafa24..ebba957b 100644 --- a/tests/coordinator/src/tests/batch.rs +++ b/tests/coordinator/src/tests/batch.rs @@ -245,7 +245,7 @@ pub async fn batch( ) ); - // Send the ack as expected, though it shouldn't trigger any observable behavior + // Send the ack as expected processor .send_message(messages::ProcessorMessage::Coordinator( messages::coordinator::ProcessorMessage::SubstrateBlockAck { From 1f2b9376f9583b65e7237e73252d889e1a2c6368 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 20 Mar 2024 21:53:57 -0400 Subject: [PATCH 02/29] zstd 0.13 --- Cargo.lock | 24 +++++++++++++++++++++--- patches/zstd/Cargo.toml | 2 +- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e66c478..9c5d097e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2953,7 +2953,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.13", - "socket2 0.4.10", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -10575,7 +10575,7 @@ dependencies = [ name = "zstd" version = "0.11.2+zstd.1.5.2" dependencies = [ - "zstd 0.12.4", + "zstd 0.13.0", ] [[package]] @@ -10584,7 +10584,16 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" dependencies = [ - "zstd-safe", + "zstd-safe 6.0.6", +] + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe 7.0.0", ] [[package]] @@ -10597,6 +10606,15 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.9+zstd.1.5.5" diff --git a/patches/zstd/Cargo.toml b/patches/zstd/Cargo.toml index f7bf11d6..0d1368e4 100644 --- a/patches/zstd/Cargo.toml +++ b/patches/zstd/Cargo.toml @@ -14,4 +14,4 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -zstd = "0.12" +zstd = "0.13" From c706d8664a3417b7fbe8baba36d15d6cab7b24c2 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 20 Mar 2024 21:44:53 -0400 Subject: [PATCH 03/29] Use OptimisticTransactionDb Exposes flush calls. Adds safety, at the cost of a panic risk, as multiple TXNs simultaneously writing to a key will now cause a panic. This should be fine and the safety is appreciated. --- common/db/src/rocks.rs | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/common/db/src/rocks.rs b/common/db/src/rocks.rs index 20d39666..6a724563 100644 --- a/common/db/src/rocks.rs +++ b/common/db/src/rocks.rs @@ -1,44 +1,51 @@ use std::sync::Arc; use rocksdb::{ - DBCompressionType, ThreadMode, SingleThreaded, LogLevel, WriteOptions, Transaction, Options, - TransactionDB, + DBCompressionType, ThreadMode, SingleThreaded, LogLevel, WriteOptions, + Transaction as RocksTransaction, Options, OptimisticTransactionDB, }; use crate::*; -impl Get for Transaction<'_, TransactionDB> { +pub struct Transaction<'a, T: ThreadMode>( + RocksTransaction<'a, OptimisticTransactionDB>, + &'a OptimisticTransactionDB, +); + +impl Get for Transaction<'_, T> { fn get(&self, key: impl AsRef<[u8]>) -> Option> { - self.get(key).expect("couldn't read from RocksDB via transaction") + self.0.get(key).expect("couldn't read from RocksDB via transaction") } } -impl DbTxn for Transaction<'_, TransactionDB> { +impl DbTxn for Transaction<'_, T> { fn put(&mut self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) { - Transaction::put(self, key, value).expect("couldn't write to RocksDB via transaction") + self.0.put(key, value).expect("couldn't write to RocksDB via transaction") } fn del(&mut self, key: impl AsRef<[u8]>) { - self.delete(key).expect("couldn't delete from RocksDB via transaction") + self.0.delete(key).expect("couldn't delete from RocksDB via transaction") } fn commit(self) { - Transaction::commit(self).expect("couldn't commit to RocksDB via transaction") + self.0.commit().expect("couldn't commit to RocksDB via transaction"); + self.1.flush_wal(true).expect("couldn't flush RocksDB WAL"); + self.1.flush().expect("couldn't flush RocksDB"); } } -impl Get for Arc> { +impl Get for Arc> { fn get(&self, key: impl AsRef<[u8]>) -> Option> { - TransactionDB::get(self, key).expect("couldn't read from RocksDB") + OptimisticTransactionDB::get(self, key).expect("couldn't read from RocksDB") } } -impl Db for Arc> { - type Transaction<'a> = Transaction<'a, TransactionDB>; +impl Db for Arc> { + type Transaction<'a> = Transaction<'a, T>; fn txn(&mut self) -> Self::Transaction<'_> { let mut opts = WriteOptions::default(); opts.set_sync(true); - self.transaction_opt(&opts, &Default::default()) + Transaction(self.transaction_opt(&opts, &Default::default()), &**self) } } -pub type RocksDB = Arc>; +pub type RocksDB = Arc>; pub fn new_rocksdb(path: &str) -> RocksDB { let mut options = Options::default(); options.create_if_missing(true); @@ -54,5 +61,5 @@ pub fn new_rocksdb(path: &str) -> RocksDB { options.set_max_log_file_size(1024 * 1024); options.set_recycle_log_file_num(1); - Arc::new(TransactionDB::open(&options, &Default::default(), path).unwrap()) + Arc::new(OptimisticTransactionDB::open(&options, path).unwrap()) } From 84cee06ac17f780a322d1b5182670c23349343b7 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 21 Mar 2024 20:05:34 -0400 Subject: [PATCH 04/29] Rust 1.77 --- orchestration/runtime/Dockerfile | 2 +- orchestration/src/main.rs | 2 +- rust-toolchain.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/orchestration/runtime/Dockerfile b/orchestration/runtime/Dockerfile index 4df69842..21da0a75 100644 --- a/orchestration/runtime/Dockerfile +++ b/orchestration/runtime/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=linux/amd64 rust:1.76.0-slim-bookworm as builder +FROM --platform=linux/amd64 rust:1.77.0-slim-bookworm as builder # Move to a Debian package snapshot RUN rm -rf /etc/apt/sources.list.d/debian.sources && \ diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index 1a2c48ca..0ec5913f 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -129,7 +129,7 @@ fn build_serai_service(release: bool, features: &str, package: &str) -> String { format!( r#" -FROM rust:1.76-slim-bookworm as builder +FROM rust:1.77-slim-bookworm as builder COPY --from=mimalloc-debian libmimalloc.so /usr/lib RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 227b9c21..77a0cea2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.76" +channel = "1.77" targets = ["wasm32-unknown-unknown"] profile = "minimal" components = ["rust-src", "rustfmt", "clippy"] From fab7a0a7cbaad0f529b3b0f092ec0c5d268a1c2d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 02:19:09 -0400 Subject: [PATCH 05/29] Use the deterministically built wasm Has the Dockerfile output to a volume. Has the node use the wasm from the volume, if it exists. --- orchestration/runtime/Dockerfile | 14 +++-- orchestration/src/main.rs | 82 +++++++++++++++++++++++++++++ orchestration/src/serai.rs | 2 +- substrate/node/src/chain_spec.rs | 32 ++++++----- substrate/node/src/command.rs | 4 +- substrate/signals/pallet/src/lib.rs | 1 + 6 files changed, 117 insertions(+), 18 deletions(-) diff --git a/orchestration/runtime/Dockerfile b/orchestration/runtime/Dockerfile index 21da0a75..2801f070 100644 --- a/orchestration/runtime/Dockerfile +++ b/orchestration/runtime/Dockerfile @@ -1,17 +1,20 @@ -FROM --platform=linux/amd64 rust:1.77.0-slim-bookworm as builder +# rust:1.77.0-slim-bookworm as of March 22nd, 2024 (GMT) +FROM --platform=linux/amd64 rust@sha256:e785e4aa81f87bc1ee02fa2026ffbc491e0410bdaf6652cea74884373f452664 as deterministic # Move to a Debian package snapshot RUN rm -rf /etc/apt/sources.list.d/debian.sources && \ rm -rf /var/lib/apt/lists/* && \ - echo "deb [arch=amd64] http://snapshot.debian.org/archive/debian/20240201T000000Z bookworm main" > /etc/apt/sources.list && \ + echo "deb [arch=amd64] http://snapshot.debian.org/archive/debian/20240301T000000Z bookworm main" > /etc/apt/sources.list && \ apt update # Install dependencies -RUN apt install clang -y +RUN apt update && apt upgrade && apt install clang -y # Add the wasm toolchain RUN rustup target add wasm32-unknown-unknown +FROM deterministic + # Add files for build ADD patches /serai/patches ADD common /serai/common @@ -30,3 +33,8 @@ ADD Cargo.lock /serai ADD AGPL-3.0 /serai WORKDIR /serai + +# Build the runtime, copying it to the volume if it exists +CMD cargo build --release -p serai-runtime && \ + mkdir -p /volume && \ + cp /serai/target/release/wbuild/serai-runtime/serai_runtime.wasm /volume/serai.wasm diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index 0ec5913f..a2533c49 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -325,6 +325,87 @@ fn start(network: Network, services: HashSet) { _ => panic!("starting unrecognized service"), }; + // If we're building the Serai service, first build the runtime + let serai_runtime_volume = format!("serai-{}-runtime-volume", network.label()); + if name == "serai" { + // Check if it's built by checking if the volume has the expected runtime file + let built = || { + if let Ok(path) = Command::new("docker") + .arg("volume") + .arg("inspect") + .arg("-f") + .arg("{{ .Mountpoint }}") + .arg(&serai_runtime_volume) + .output() + { + if let Ok(path) = String::from_utf8(path.stdout) { + if let Ok(iter) = std::fs::read_dir(PathBuf::from(path.trim())) { + for item in iter.flatten() { + if item.file_name() == "serai.wasm" { + return true; + } + } + } + } + } + false + }; + + if !built() { + 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(); + + // Build the image to build the runtime + if !Command::new("docker") + .current_dir(&repo_path) + .arg("build") + .arg("-f") + .arg("orchestration/runtime/Dockerfile") + .arg(".") + .arg("-t") + .arg(format!("serai-{}-runtime-img", network.label())) + .spawn() + .unwrap() + .wait() + .unwrap() + .success() + { + panic!("failed to build runtime image"); + } + + // Run the image, building the runtime + println!("Building the Serai runtime"); + let container_name = format!("serai-{}-runtime", network.label()); + let _ = + Command::new("docker").arg("rm").arg("-f").arg(&container_name).spawn().unwrap().wait(); + let _ = Command::new("docker") + .arg("run") + .arg("--name") + .arg(container_name) + .arg("--volume") + .arg(format!("{serai_runtime_volume}:/volume")) + .arg(format!("serai-{}-runtime-img", network.label())) + .spawn(); + + // Wait until its built + let mut ticks = 0; + while !built() { + std::thread::sleep(core::time::Duration::from_secs(60)); + ticks += 1; + if ticks > 6 * 60 { + panic!("couldn't build the runtime after 6 hours") + } + } + } + } + // Build it println!("Building {service}"); docker::build(&orchestration_path(network), network, name); @@ -367,6 +448,7 @@ fn start(network: Network, services: HashSet) { assert_eq!(network, Network::Dev, "monero-wallet-rpc is only for dev"); command.arg("-p").arg("18082:18082") } + "serai" => command.arg("--volume").arg(format!("{serai_runtime_volume}:/runtime")), _ => command, }; assert!( diff --git a/orchestration/src/serai.rs b/orchestration/src/serai.rs index a3382acb..74fa78e6 100644 --- a/orchestration/src/serai.rs +++ b/orchestration/src/serai.rs @@ -21,7 +21,7 @@ EXPOSE 30333 9615 9933 9944 ADD /orchestration/{}/serai/run.sh / CMD ["/run.sh"] "#, - network.label() + network.label(), ); let run = os(Os::Debian, "", "serai") + &run_serai; diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index 042f5178..b630c00b 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -15,6 +15,14 @@ fn account_from_name(name: &'static str) -> PublicKey { insecure_pair_from_name(name).public() } +fn wasm_binary() -> Vec { + // TODO: Accept a config of runtime path + if let Ok(binary) = std::fs::read("/runtime/serai.wasm") { + return binary; + } + WASM_BINARY.ok_or("compiled in wasm not available").unwrap().to_vec() +} + fn testnet_genesis( wasm_binary: &[u8], validators: &[&'static str], @@ -64,18 +72,18 @@ fn testnet_genesis( } } -pub fn development_config() -> Result { - let wasm_binary = WASM_BINARY.ok_or("Development wasm not available")?; +pub fn development_config() -> ChainSpec { + let wasm_binary = wasm_binary(); - Ok(ChainSpec::from_genesis( + ChainSpec::from_genesis( // Name "Development Network", // ID "devnet", ChainType::Development, - || { + move || { testnet_genesis( - wasm_binary, + &wasm_binary, &["Alice"], vec![ account_from_name("Alice"), @@ -99,21 +107,21 @@ pub fn development_config() -> Result { None, // Extensions None, - )) + ) } -pub fn testnet_config() -> Result { - let wasm_binary = WASM_BINARY.ok_or("Testnet wasm not available")?; +pub fn testnet_config() -> ChainSpec { + let wasm_binary = wasm_binary(); - Ok(ChainSpec::from_genesis( + ChainSpec::from_genesis( // Name "Local Test Network", // ID "local", ChainType::Local, - || { + move || { testnet_genesis( - wasm_binary, + &wasm_binary, &["Alice", "Bob", "Charlie", "Dave"], vec![ account_from_name("Alice"), @@ -137,5 +145,5 @@ pub fn testnet_config() -> Result { None, // Extensions None, - )) + ) } diff --git a/substrate/node/src/command.rs b/substrate/node/src/command.rs index 3588f95f..2f7ea0f7 100644 --- a/substrate/node/src/command.rs +++ b/substrate/node/src/command.rs @@ -39,8 +39,8 @@ impl SubstrateCli for Cli { fn load_spec(&self, id: &str) -> Result, String> { match id { - "dev" | "devnet" => Ok(Box::new(chain_spec::development_config()?)), - "local" => Ok(Box::new(chain_spec::testnet_config()?)), + "dev" | "devnet" => Ok(Box::new(chain_spec::development_config())), + "local" => Ok(Box::new(chain_spec::testnet_config())), _ => panic!("Unknown network ID"), } } diff --git a/substrate/signals/pallet/src/lib.rs b/substrate/signals/pallet/src/lib.rs index 3fad27c9..54d6086a 100644 --- a/substrate/signals/pallet/src/lib.rs +++ b/substrate/signals/pallet/src/lib.rs @@ -142,6 +142,7 @@ pub mod pallet { } // 80% threshold + // TODO: Use 34% for halting a set (not 80%) const REQUIREMENT_NUMERATOR: u64 = 4; const REQUIREMENT_DIVISOR: u64 = 5; From e0259f2fe59b516184537aa72929c441ab9ca48b Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 16:06:04 -0400 Subject: [PATCH 06/29] Add TODO re: Monero --- coins/monero/src/ringct/clsag/multisig.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/coins/monero/src/ringct/clsag/multisig.rs b/coins/monero/src/ringct/clsag/multisig.rs index 9cb930ce..85748b78 100644 --- a/coins/monero/src/ringct/clsag/multisig.rs +++ b/coins/monero/src/ringct/clsag/multisig.rs @@ -199,6 +199,7 @@ impl Algorithm for ClsagMultisig { l: Participant, addendum: ClsagAddendum, ) -> Result<(), FrostError> { + // TODO: This check is faulty if two shares are additive inverses of each other if self.image.is_identity().into() { self.transcript.domain_separate(b"CLSAG"); self.input().transcript(&mut self.transcript); From 2f07d04d881731869ae0ed8f13dc3dcc275035b6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 16:06:26 -0400 Subject: [PATCH 07/29] Extend timeout for rebroadcast of consensus messages in coordinator --- coordinator/tributary/src/lib.rs | 2 +- coordinator/tributary/src/tendermint/mod.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/coordinator/tributary/src/lib.rs b/coordinator/tributary/src/lib.rs index 92fb98da..81b4fc17 100644 --- a/coordinator/tributary/src/lib.rs +++ b/coordinator/tributary/src/lib.rs @@ -207,7 +207,7 @@ impl Tributary { for msg in to_rebroadcast { p2p.broadcast(genesis, msg).await; } - tokio::time::sleep(core::time::Duration::from_secs(1)).await; + tokio::time::sleep(core::time::Duration::from_secs(60)).await; } } }) diff --git a/coordinator/tributary/src/tendermint/mod.rs b/coordinator/tributary/src/tendermint/mod.rs index d362364c..40d01380 100644 --- a/coordinator/tributary/src/tendermint/mod.rs +++ b/coordinator/tributary/src/tendermint/mod.rs @@ -331,14 +331,12 @@ impl Network for TendermintNetwork // until the block it's trying to build is complete // If the P2P layer drops a message before all nodes obtained access, or a node had an // intermittent failure, this will ensure reconcilliation - // Resolves halts caused by timing discrepancies, which technically are violations of - // Tendermint as a BFT protocol, and shouldn't occur yet have in low-powered testing - // environments // This is atrocious if there's no content-based deduplication protocol for messages actively // being gossiped // LibP2p, as used by Serai, is configured to content-based deduplicate let mut to_broadcast = vec![TENDERMINT_MESSAGE]; to_broadcast.extend(msg.encode()); + // TODO: Prune messages from old rounds which are no longer necessary self.to_rebroadcast.write().await.push(to_broadcast.clone()); self.p2p.broadcast(self.genesis, to_broadcast).await } From 6658d95c85c04da9a417301ec0f96e4ecf83080f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 16:06:56 -0400 Subject: [PATCH 08/29] Extend orchestration as actually needed for testnet Contains various bug fixes. --- orchestration/src/coordinator.rs | 7 +++++-- orchestration/src/main.rs | 19 ++++++++++++++++--- orchestration/src/message_queue.rs | 2 +- orchestration/src/processor.rs | 4 ++-- orchestration/src/serai.rs | 20 ++++++++++++++++---- orchestration/testnet/serai/run.sh | 2 +- 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/orchestration/src/coordinator.rs b/orchestration/src/coordinator.rs index a8556a00..9995dbbf 100644 --- a/orchestration/src/coordinator.rs +++ b/orchestration/src/coordinator.rs @@ -11,7 +11,7 @@ pub fn coordinator( orchestration_path: &Path, network: Network, coordinator_key: Zeroizing<::F>, - serai_key: Zeroizing<::F>, + serai_key: &Zeroizing<::F>, ) { let db = network.db(); let longer_reattempts = if network == Network::Dev { "longer-reattempts" } else { "" }; @@ -27,13 +27,16 @@ pub fn coordinator( RUN apt install -y ca-certificates "#; + #[rustfmt::skip] + const DEFAULT_RUST_LOG: &str = "info,serai_coordinator=debug,tributary_chain=debug,tendermint=debug,libp2p_gossipsub::behaviour=error"; + 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", hex::encode(serai_key.to_repr())), ("SERAI_HOSTNAME", format!("serai-{}-serai", network.label())), - ("RUST_LOG", "serai_coordinator=debug,tributary_chain=debug,tendermint=debug".to_string()), + ("RUST_LOG", DEFAULT_RUST_LOG.to_string()), ]; let mut env_vars_str = String::new(); for (env_var, value) in env_vars { diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index a2533c49..548aca8b 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -276,9 +276,9 @@ fn dockerfiles(network: Network) { Zeroizing::new(::F::from_repr(*serai_key_repr).unwrap()) }; - coordinator(&orchestration_path, network, coordinator_key.0, serai_key); + coordinator(&orchestration_path, network, coordinator_key.0, &serai_key); - serai(&orchestration_path, network); + serai(&orchestration_path, network, &serai_key); } fn key_gen(network: Network) { @@ -448,7 +448,20 @@ fn start(network: Network, services: HashSet) { assert_eq!(network, Network::Dev, "monero-wallet-rpc is only for dev"); command.arg("-p").arg("18082:18082") } - "serai" => command.arg("--volume").arg(format!("{serai_runtime_volume}:/runtime")), + "coordinator" => { + if network != Network::Dev { + command.arg("-p").arg("30563:30563") + } else { + command + } + } + "serai" => { + let mut command = command; + if network != Network::Dev { + command = command.arg("-p").arg("30333:30333"); + } + command.arg("--volume").arg(format!("{serai_runtime_volume}:/runtime")) + } _ => command, }; assert!( diff --git a/orchestration/src/message_queue.rs b/orchestration/src/message_queue.rs index ef6bdcbf..3e47571c 100644 --- a/orchestration/src/message_queue.rs +++ b/orchestration/src/message_queue.rs @@ -21,7 +21,7 @@ pub fn message_queue( ("ETHEREUM_KEY", hex::encode(ethereum_key.to_bytes())), ("MONERO_KEY", hex::encode(monero_key.to_bytes())), ("DB_PATH", "./message-queue-db".to_string()), - ("RUST_LOG", "serai_message_queue=trace".to_string()), + ("RUST_LOG", "info,serai_message_queue=trace".to_string()), ]; let mut env_vars_str = String::new(); for (env_var, value) in env_vars { diff --git a/orchestration/src/processor.rs b/orchestration/src/processor.rs index e2afde09..3d76a6c9 100644 --- a/orchestration/src/processor.rs +++ b/orchestration/src/processor.rs @@ -40,7 +40,7 @@ RUN apt install -y ca-certificates }; let env_vars = [ - ("MESSAGE_QUEUE_RPC", format!("serai-{}-message_queue", network.label())), + ("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()), @@ -48,7 +48,7 @@ RUN apt install -y ca-certificates ("NETWORK_RPC_HOSTNAME", hostname), ("NETWORK_RPC_PORT", format!("{port}")), ("DB_PATH", "./processor-db".to_string()), - ("RUST_LOG", "serai_processor=debug".to_string()), + ("RUST_LOG", "info,serai_processor=debug".to_string()), ]; let mut env_vars_str = String::new(); for (env_var, value) in env_vars { diff --git a/orchestration/src/serai.rs b/orchestration/src/serai.rs index 74fa78e6..1487b70d 100644 --- a/orchestration/src/serai.rs +++ b/orchestration/src/serai.rs @@ -1,14 +1,26 @@ use std::{path::Path}; +use zeroize::Zeroizing; +use ciphersuite::{group::ff::PrimeField, Ciphersuite, Ristretto}; + use crate::{Network, Os, mimalloc, os, build_serai_service, write_dockerfile}; -pub fn serai(orchestration_path: &Path, network: Network) { +pub fn serai( + orchestration_path: &Path, + network: Network, + serai_key: &Zeroizing<::F>, +) { // Always builds in release for performance reasons let setup = mimalloc(Os::Debian).to_string() + &build_serai_service(true, "", "serai-node"); let setup_fast_epoch = mimalloc(Os::Debian).to_string() + &build_serai_service(true, "fast-epoch", "serai-node"); - // TODO: Review the ports exposed here + let env_vars = [("KEY", hex::encode(serai_key.to_repr()))]; + let mut env_vars_str = String::new(); + for (env_var, value) in env_vars { + env_vars_str += &format!(r#"{env_var}=${{{env_var}}}:="{value}"}} "#); + } + let run_serai = format!( r#" # Copy the Serai binary and relevant license @@ -16,10 +28,10 @@ COPY --from=builder --chown=serai /serai/bin/serai-node /bin/ COPY --from=builder --chown=serai /serai/AGPL-3.0 . # Run the Serai node -EXPOSE 30333 9615 9933 9944 +EXPOSE 30333 9944 ADD /orchestration/{}/serai/run.sh / -CMD ["/run.sh"] +CMD {env_vars_str} "/run.sh" "#, network.label(), ); diff --git a/orchestration/testnet/serai/run.sh b/orchestration/testnet/serai/run.sh index 2bb8d868..7400ff50 100755 --- a/orchestration/testnet/serai/run.sh +++ b/orchestration/testnet/serai/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -exit 1 +serai-node --unsafe-rpc-external --rpc-cors all --chain testnet --validator From e861859deca64af978017b3d2de335d4b2f8cd0e Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 16:18:01 -0400 Subject: [PATCH 09/29] Update EpochDuration in runtime --- substrate/runtime/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 10340567..9a534a72 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -314,12 +314,10 @@ pub type ReportLongevity = ::EpochDuration; impl babe::Config for Runtime { #[cfg(feature = "fast-epoch")] - #[allow(clippy::identity_op)] - type EpochDuration = ConstU64<{ DAYS / (24 * 60 * 2) }>; // 30 seconds + type EpochDuration = ConstU64<{ MINUTES / 2 }>; // 30 seconds #[cfg(not(feature = "fast-epoch"))] - #[allow(clippy::identity_op)] - type EpochDuration = ConstU64<{ DAYS }>; + type EpochDuration = ConstU64<{ 4 * 7 * DAYS }>; type ExpectedBlockTime = ConstU64<{ TARGET_BLOCK_TIME * 1000 }>; type EpochChangeTrigger = babe::ExternalTrigger; From bdf5a66e95dcf2a2e43d4aee09f44d299d19a4fb Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 17:11:44 -0400 Subject: [PATCH 10/29] Correct Serai key provision --- orchestration/src/serai.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestration/src/serai.rs b/orchestration/src/serai.rs index 1487b70d..77d098b6 100644 --- a/orchestration/src/serai.rs +++ b/orchestration/src/serai.rs @@ -18,7 +18,7 @@ pub fn serai( let env_vars = [("KEY", hex::encode(serai_key.to_repr()))]; let mut env_vars_str = String::new(); for (env_var, value) in env_vars { - env_vars_str += &format!(r#"{env_var}=${{{env_var}}}:="{value}"}} "#); + env_vars_str += &format!(r#"{env_var}=${{{env_var}:="{value}"}} "#); } let run_serai = format!( From 08c7c1b413768c8ba3d22113c06576303f534122 Mon Sep 17 00:00:00 2001 From: j-berman Date: Fri, 22 Mar 2024 14:26:42 -0700 Subject: [PATCH 11/29] monero: reference updated PR in fee test comment --- coins/monero/tests/wallet2_compatibility.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coins/monero/tests/wallet2_compatibility.rs b/coins/monero/tests/wallet2_compatibility.rs index 2002f3bd..c6b58978 100644 --- a/coins/monero/tests/wallet2_compatibility.rs +++ b/coins/monero/tests/wallet2_compatibility.rs @@ -88,7 +88,7 @@ async fn from_wallet_rpc_to_self(spec: AddressSpec) { .unwrap(); let tx_hash = hex::decode(tx.tx_hash).unwrap().try_into().unwrap(); - // TODO: Needs https://github.com/monero-project/monero/pull/8882 + // TODO: Needs https://github.com/monero-project/monero/pull/9260 // let fee_rate = daemon_rpc // .get_fee(daemon_rpc.get_protocol().await.unwrap(), FeePriority::Unimportant) // .await @@ -107,7 +107,7 @@ async fn from_wallet_rpc_to_self(spec: AddressSpec) { let tx = daemon_rpc.get_transaction(tx_hash).await.unwrap(); let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0); - // TODO: Needs https://github.com/monero-project/monero/pull/8882 + // TODO: Needs https://github.com/monero-project/monero/pull/9260 // runner::check_weight_and_fee(&tx, fee_rate); match spec { From e5afcda76bc709f0decc8184dc2a1c849c5ce7fb Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 22:34:18 -0400 Subject: [PATCH 12/29] Explicitly use "" for KEY within the tests Causes the provided keystore to be used over our keystore. --- substrate/node/src/keystore.rs | 3 +++ tests/coordinator/src/lib.rs | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/substrate/node/src/keystore.rs b/substrate/node/src/keystore.rs index ca00e79b..f7b9110f 100644 --- a/substrate/node/src/keystore.rs +++ b/substrate/node/src/keystore.rs @@ -8,6 +8,9 @@ pub struct Keystore(sr25519::Pair); impl Keystore { pub fn from_env() -> Option { let mut key_hex = serai_env::var("KEY")?; + if key_hex.is_empty() { + None?; + } let mut key = hex::decode(&key_hex).expect("KEY from environment wasn't hex"); key_hex.zeroize(); diff --git a/tests/coordinator/src/lib.rs b/tests/coordinator/src/lib.rs index 0541c4fd..d09f4487 100644 --- a/tests/coordinator/src/lib.rs +++ b/tests/coordinator/src/lib.rs @@ -66,7 +66,9 @@ pub fn serai_composition(name: &str) -> TestBodySpecification { TestBodySpecification::with_image( Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never), ) - .replace_env([("SERAI_NAME".to_string(), name.to_lowercase())].into()) + .replace_env( + [("SERAI_NAME".to_string(), name.to_lowercase()), ("KEY".to_string(), String::new())].into(), + ) .set_publish_all_ports(true) } From af9b1ad5f91777dc38cfe7dee4d3b488ef9a4dc9 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 23:18:53 -0400 Subject: [PATCH 13/29] Initial pruning of backlogged consensus messages --- coordinator/tributary/src/lib.rs | 4 +- coordinator/tributary/src/tendermint/mod.rs | 51 ++++++++----------- coordinator/tributary/src/tests/mod.rs | 3 ++ coordinator/tributary/src/tests/tendermint.rs | 28 ++++++++++ 4 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 coordinator/tributary/src/tests/tendermint.rs diff --git a/coordinator/tributary/src/lib.rs b/coordinator/tributary/src/lib.rs index 81b4fc17..99deb588 100644 --- a/coordinator/tributary/src/lib.rs +++ b/coordinator/tributary/src/lib.rs @@ -1,5 +1,5 @@ use core::{marker::PhantomData, fmt::Debug}; -use std::{sync::Arc, io}; +use std::{sync::Arc, io, collections::VecDeque}; use async_trait::async_trait; @@ -194,7 +194,7 @@ impl Tributary { ); let blockchain = Arc::new(RwLock::new(blockchain)); - let to_rebroadcast = Arc::new(RwLock::new(vec![])); + let to_rebroadcast = Arc::new(RwLock::new(VecDeque::new())); // Actively rebroadcast consensus messages to ensure they aren't prematurely dropped from the // P2P layer let p2p_meta_task_handle = Arc::new( diff --git a/coordinator/tributary/src/tendermint/mod.rs b/coordinator/tributary/src/tendermint/mod.rs index 40d01380..df8f7219 100644 --- a/coordinator/tributary/src/tendermint/mod.rs +++ b/coordinator/tributary/src/tendermint/mod.rs @@ -1,5 +1,8 @@ use core::ops::Deref; -use std::{sync::Arc, collections::HashMap}; +use std::{ + sync::Arc, + collections::{VecDeque, HashMap}, +}; use async_trait::async_trait; @@ -268,7 +271,7 @@ pub struct TendermintNetwork { pub(crate) validators: Arc, pub(crate) blockchain: Arc>>, - pub(crate) to_rebroadcast: Arc>>>, + pub(crate) to_rebroadcast: Arc>>>, pub(crate) p2p: P, } @@ -277,29 +280,6 @@ pub const BLOCK_PROCESSING_TIME: u32 = 999; pub const LATENCY_TIME: u32 = 1667; pub const TARGET_BLOCK_TIME: u32 = BLOCK_PROCESSING_TIME + (3 * LATENCY_TIME); -#[test] -fn assert_target_block_time() { - use serai_db::MemDb; - - #[derive(Clone, Debug)] - pub struct DummyP2p; - - #[async_trait::async_trait] - impl P2p for DummyP2p { - async fn broadcast(&self, _: [u8; 32], _: Vec) { - unimplemented!() - } - } - - // Type paremeters don't matter here since we only need to call the block_time() - // and it only relies on the constants of the trait implementation. block_time() is in seconds, - // TARGET_BLOCK_TIME is in milliseconds. - assert_eq!( - as Network>::block_time(), - TARGET_BLOCK_TIME / 1000 - ) -} - #[async_trait] impl Network for TendermintNetwork { type Db = D; @@ -327,6 +307,9 @@ impl Network for TendermintNetwork } async fn broadcast(&mut self, msg: SignedMessageFor) { + let mut to_broadcast = vec![TENDERMINT_MESSAGE]; + to_broadcast.extend(msg.encode()); + // Since we're broadcasting a Tendermint message, set it to be re-broadcasted every second // until the block it's trying to build is complete // If the P2P layer drops a message before all nodes obtained access, or a node had an @@ -334,10 +317,18 @@ impl Network for TendermintNetwork // This is atrocious if there's no content-based deduplication protocol for messages actively // being gossiped // LibP2p, as used by Serai, is configured to content-based deduplicate - let mut to_broadcast = vec![TENDERMINT_MESSAGE]; - to_broadcast.extend(msg.encode()); - // TODO: Prune messages from old rounds which are no longer necessary - self.to_rebroadcast.write().await.push(to_broadcast.clone()); + { + let mut to_rebroadcast_lock = self.to_rebroadcast.write().await; + to_rebroadcast_lock.push_back(to_broadcast.clone()); + // We should have, ideally, 3 * validators messages within a round + // Therefore, this should keep the most recent 2-rounds + // TODO: This isn't perfect. Each participant should just rebroadcast their latest round of + // messages + while to_rebroadcast_lock.len() > (6 * self.validators.weights.len()) { + to_rebroadcast_lock.pop_front(); + } + } + self.p2p.broadcast(self.genesis, to_broadcast).await } @@ -443,7 +434,7 @@ impl Network for TendermintNetwork } // Since we've added a valid block, clear to_rebroadcast - *self.to_rebroadcast.write().await = vec![]; + *self.to_rebroadcast.write().await = VecDeque::new(); Some(TendermintBlock( self.blockchain.write().await.build_block::(&self.signature_scheme()).serialize(), diff --git a/coordinator/tributary/src/tests/mod.rs b/coordinator/tributary/src/tests/mod.rs index 7c75ac36..dcaa11a5 100644 --- a/coordinator/tributary/src/tests/mod.rs +++ b/coordinator/tributary/src/tests/mod.rs @@ -1,3 +1,6 @@ +#[cfg(test)] +mod tendermint; + mod transaction; pub use transaction::*; diff --git a/coordinator/tributary/src/tests/tendermint.rs b/coordinator/tributary/src/tests/tendermint.rs new file mode 100644 index 00000000..77dfc9e5 --- /dev/null +++ b/coordinator/tributary/src/tests/tendermint.rs @@ -0,0 +1,28 @@ +use tendermint::ext::Network; +use crate::{ + P2p, TendermintTx, + tendermint::{TARGET_BLOCK_TIME, TendermintNetwork}, +}; + +#[test] +fn assert_target_block_time() { + use serai_db::MemDb; + + #[derive(Clone, Debug)] + pub struct DummyP2p; + + #[async_trait::async_trait] + impl P2p for DummyP2p { + async fn broadcast(&self, _: [u8; 32], _: Vec) { + unimplemented!() + } + } + + // Type paremeters don't matter here since we only need to call the block_time() + // and it only relies on the constants of the trait implementation. block_time() is in seconds, + // TARGET_BLOCK_TIME is in milliseconds. + assert_eq!( + as Network>::block_time(), + TARGET_BLOCK_TIME / 1000 + ) +} From 35b58a45bdcb8cc8a13db3504ad13e6ba8931ae0 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 23:40:15 -0400 Subject: [PATCH 14/29] Split peer finding into a dedicated task --- coordinator/src/p2p.rs | 138 +++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/coordinator/src/p2p.rs b/coordinator/src/p2p.rs index ce6be688..8fe609a0 100644 --- a/coordinator/src/p2p.rs +++ b/coordinator/src/p2p.rs @@ -290,6 +290,75 @@ impl LibP2p { IdentTopic::new(format!("{LIBP2P_TOPIC}-{}", hex::encode(set.encode()))) } + // Find and connect to peers + let (pending_p2p_connections_send, mut pending_p2p_connections_recv) = + tokio::sync::mpsc::unbounded_channel(); + let (to_dial_send, mut to_dial_recv) = tokio::sync::mpsc::unbounded_channel(); + tokio::spawn({ + let pending_p2p_connections_send = pending_p2p_connections_send.clone(); + async move { + loop { + // TODO: Add better peer management logic? + { + let connect = |addr: Multiaddr| { + log::info!("found peer from substrate: {addr}"); + + let protocols = addr.iter().filter_map(|piece| match piece { + // Drop PeerIds from the Substrate P2p network + Protocol::P2p(_) => None, + // Use our own TCP port + Protocol::Tcp(_) => Some(Protocol::Tcp(PORT)), + other => Some(other), + }); + + let mut new_addr = Multiaddr::empty(); + for protocol in protocols { + new_addr.push(protocol); + } + let addr = new_addr; + log::debug!("transformed found peer: {addr}"); + + // TODO: Check this isn't a duplicate + to_dial_send.send(addr).unwrap(); + }; + + while let Some(network) = pending_p2p_connections_recv.recv().await { + if let Ok(mut nodes) = serai.p2p_validators(network).await { + // If there's an insufficient amount of nodes known, connect to all yet add it + // back and break + if nodes.len() < 3 { + log::warn!( + "insufficient amount of P2P nodes known for {:?}: {}", + network, + nodes.len() + ); + pending_p2p_connections_send.send(network).unwrap(); + for node in nodes { + connect(node); + } + break; + } + + // Randomly select up to 5 + for _ in 0 .. 5 { + if !nodes.is_empty() { + let to_connect = nodes.swap_remove( + usize::try_from(OsRng.next_u64() % u64::try_from(nodes.len()).unwrap()) + .unwrap(), + ); + connect(to_connect); + } + } + } + } + } + // Sleep 60 seconds before moving to the next iteration + tokio::time::sleep(core::time::Duration::from_secs(60)).await; + } + } + }); + + // Manage the actual swarm tokio::spawn({ let mut time_of_last_p2p_message = Instant::now(); @@ -321,66 +390,7 @@ impl LibP2p { async move { let mut set_for_genesis = HashMap::new(); - let mut pending_p2p_connections = vec![]; - // Run this task ad-infinitum loop { - // Handle pending P2P connections - // TODO: Break this out onto its own task with better peer management logic? - { - let mut connect = |addr: Multiaddr| { - log::info!("found peer from substrate: {addr}"); - - let protocols = addr.iter().filter_map(|piece| match piece { - // Drop PeerIds from the Substrate P2p network - Protocol::P2p(_) => None, - // Use our own TCP port - Protocol::Tcp(_) => Some(Protocol::Tcp(PORT)), - other => Some(other), - }); - - let mut new_addr = Multiaddr::empty(); - for protocol in protocols { - new_addr.push(protocol); - } - let addr = new_addr; - log::debug!("transformed found peer: {addr}"); - - if let Err(e) = swarm.dial(addr) { - log::warn!("dialing peer failed: {e:?}"); - } - }; - - while let Some(network) = pending_p2p_connections.pop() { - if let Ok(mut nodes) = serai.p2p_validators(network).await { - // If there's an insufficient amount of nodes known, connect to all yet add it back - // and break - if nodes.len() < 3 { - log::warn!( - "insufficient amount of P2P nodes known for {:?}: {}", - network, - nodes.len() - ); - pending_p2p_connections.push(network); - for node in nodes { - connect(node); - } - break; - } - - // Randomly select up to 5 - for _ in 0 .. 5 { - if !nodes.is_empty() { - let to_connect = nodes.swap_remove( - usize::try_from(OsRng.next_u64() % u64::try_from(nodes.len()).unwrap()) - .unwrap(), - ); - connect(to_connect); - } - } - } - } - } - let time_since_last = Instant::now().duration_since(time_of_last_p2p_message); tokio::select! { biased; @@ -392,7 +402,7 @@ impl LibP2p { let topic = topic_for_set(set); if subscribe { log::info!("subscribing to p2p messages for {set:?}"); - pending_p2p_connections.push(set.network); + pending_p2p_connections_send.send(set.network).unwrap(); set_for_genesis.insert(genesis, set); swarm.behaviour_mut().gossipsub.subscribe(&topic).unwrap(); } else { @@ -440,6 +450,14 @@ impl LibP2p { } } + // Handle peers to dial + addr = to_dial_recv.recv() => { + let addr = addr.expect("received address was None (sender dropped?)"); + if let Err(e) = swarm.dial(addr) { + log::warn!("dialing peer failed: {e:?}"); + } + } + // If it's been >80s since we've published a message, publish a KeepAlive since we're // still an active service // This is useful when we have no active tributaries and accordingly aren't sending From f11a08c43668be3ecec512fa2f0a2240ed4a487c Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 23:47:43 -0400 Subject: [PATCH 15/29] Peer finding which won't get stuck on one specific network --- coordinator/src/p2p.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/coordinator/src/p2p.rs b/coordinator/src/p2p.rs index 8fe609a0..f64834ee 100644 --- a/coordinator/src/p2p.rs +++ b/coordinator/src/p2p.rs @@ -322,6 +322,7 @@ impl LibP2p { to_dial_send.send(addr).unwrap(); }; + let mut to_retry = vec![]; while let Some(network) = pending_p2p_connections_recv.recv().await { if let Ok(mut nodes) = serai.p2p_validators(network).await { // If there's an insufficient amount of nodes known, connect to all yet add it @@ -332,11 +333,11 @@ impl LibP2p { network, nodes.len() ); - pending_p2p_connections_send.send(network).unwrap(); + to_retry.push(network); for node in nodes { connect(node); } - break; + continue; } // Randomly select up to 5 @@ -351,6 +352,9 @@ impl LibP2p { } } } + for to_retry in to_retry { + pending_p2p_connections_send.send(to_retry).unwrap(); + } } // Sleep 60 seconds before moving to the next iteration tokio::time::sleep(core::time::Duration::from_secs(60)).await; @@ -432,12 +436,16 @@ impl LibP2p { log::debug!("dialing to peer in connection ID {}", &connection_id); } Some(SwarmEvent::ConnectionEstablished { peer_id, connection_id, .. }) => { - log::debug!( - "connection established to peer {} in connection ID {}", - &peer_id, - &connection_id, - ); - swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id) + if &peer_id == swarm.local_peer_id() { + swarm.close_connection(connection_id); + } else { + log::debug!( + "connection established to peer {} in connection ID {}", + &peer_id, + &connection_id, + ); + swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id) + } } Some(SwarmEvent::Behaviour(BehaviorEvent::Gossipsub( GsEvent::Message { propagation_source, message, .. }, From 4914420a379c57a1f2fd5f8da3a1e832a40e0d80 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 22 Mar 2024 23:51:51 -0400 Subject: [PATCH 16/29] Don't add as an explicit peer if already connected --- coordinator/src/p2p.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/coordinator/src/p2p.rs b/coordinator/src/p2p.rs index f64834ee..d840fea8 100644 --- a/coordinator/src/p2p.rs +++ b/coordinator/src/p2p.rs @@ -435,18 +435,18 @@ impl LibP2p { Some(SwarmEvent::Dialing { connection_id, .. }) => { log::debug!("dialing to peer in connection ID {}", &connection_id); } - Some(SwarmEvent::ConnectionEstablished { peer_id, connection_id, .. }) => { - if &peer_id == swarm.local_peer_id() { - swarm.close_connection(connection_id); - } else { - log::debug!( - "connection established to peer {} in connection ID {}", - &peer_id, - &connection_id, - ); - swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id) - } + Some(SwarmEvent::ConnectionEstablished { peer_id, connection_id, .. }) => { + if &peer_id == swarm.local_peer_id() { + swarm.close_connection(connection_id); + } else if swarm.is_connected(&peer_id) {} else { + log::debug!( + "connection established to peer {} in connection ID {}", + &peer_id, + &connection_id, + ); + swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id) } + } Some(SwarmEvent::Behaviour(BehaviorEvent::Gossipsub( GsEvent::Message { propagation_source, message, .. }, ))) => { From bca3728a10fcaf88d8a899063608986fdd084712 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 00:09:23 -0400 Subject: [PATCH 17/29] Randomly select an addr from the authority discovery --- Cargo.lock | 1 + substrate/node/Cargo.toml | 1 + substrate/node/src/rpc.rs | 15 +++++++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c5d097e..1ae1d463 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7625,6 +7625,7 @@ dependencies = [ "hex", "jsonrpsee", "pallet-transaction-payment-rpc", + "rand_core", "sc-authority-discovery", "sc-basic-authorship", "sc-cli", diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index f66a9705..12ba4d17 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -23,6 +23,7 @@ name = "serai-node" zeroize = "1" hex = "0.4" +rand_core = "0.6" schnorrkel = "0.11" sp-core = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/node/src/rpc.rs b/substrate/node/src/rpc.rs index f5ed2582..d07778cc 100644 --- a/substrate/node/src/rpc.rs +++ b/substrate/node/src/rpc.rs @@ -1,5 +1,7 @@ use std::{sync::Arc, collections::HashSet}; +use rand_core::{RngCore, OsRng}; + use sp_blockchain::{Error as BlockchainError, HeaderBackend, HeaderMetadata}; use sp_block_builder::BlockBuilder; use sp_api::ProvideRuntimeApi; @@ -72,14 +74,19 @@ where .get_addresses_by_authority_id(validator.into()) .await .unwrap_or_else(HashSet::new) - .into_iter(); - // Only take a single address + .into_iter() + .collect::>(); + // Randomly select an address // There should be one, there may be two if their IP address changed, and more should only // occur if they have multiple proxies/an IP address changing frequently/some issue // preventing consistent self-identification // It isn't beneficial to use multiple addresses for a single peer here - if let Some(address) = returned_addresses.next() { - all_p2p_addresses.push(address); + if !returned_addresses.is_empty() { + all_p2p_addresses.push( + returned_addresses.remove( + usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len(), + ), + ); } } Ok(all_p2p_addresses) From 2a31d8552e26f9a257e3c6eba877ac092b04dee9 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 16:48:12 -0400 Subject: [PATCH 18/29] Add empty string for the KEY to serai-client to use the default keystore --- substrate/client/tests/common/mod.rs | 7 ++++++- substrate/client/tests/dht.rs | 4 +++- substrate/client/tests/validator_sets.rs | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/substrate/client/tests/common/mod.rs b/substrate/client/tests/common/mod.rs index d887b0b1..a6d5300b 100644 --- a/substrate/client/tests/common/mod.rs +++ b/substrate/client/tests/common/mod.rs @@ -29,7 +29,12 @@ macro_rules! serai_test { "--rpc-cors".to_string(), "all".to_string(), ]) - .replace_env(HashMap::from([("RUST_LOG".to_string(), "runtime=debug".to_string())])) + .replace_env( + HashMap::from([ + ("RUST_LOG".to_string(), "runtime=debug".to_string()), + ("KEY".to_string(), String::new()), + ]) + ) .set_publish_all_ports(true) .set_handle(handle) .set_start_policy(StartPolicy::Strict) diff --git a/substrate/client/tests/dht.rs b/substrate/client/tests/dht.rs index 2fd40b12..4ab177ad 100644 --- a/substrate/client/tests/dht.rs +++ b/substrate/client/tests/dht.rs @@ -14,7 +14,9 @@ async fn dht() { TestBodySpecification::with_image( Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never), ) - .replace_env([("SERAI_NAME".to_string(), name.to_string())].into()) + .replace_env( + [("SERAI_NAME".to_string(), name.to_string()), ("KEY".to_string(), String::new())].into(), + ) .set_publish_all_ports(true) .set_handle(handle(name)) .set_start_policy(StartPolicy::Strict) diff --git a/substrate/client/tests/validator_sets.rs b/substrate/client/tests/validator_sets.rs index fc284f64..884a42db 100644 --- a/substrate/client/tests/validator_sets.rs +++ b/substrate/client/tests/validator_sets.rs @@ -100,7 +100,10 @@ async fn validator_set_rotation() { "local".to_string(), format!("--{name}"), ]) - .replace_env(HashMap::from([("RUST_LOG=runtime".to_string(), "debug".to_string())])) + .replace_env(HashMap::from([ + ("RUST_LOG".to_string(), "runtime=debug".to_string()), + ("KEY".to_string(), String::new()), + ])) .set_publish_all_ports(true) .set_handle(handle(name)) .set_start_policy(StartPolicy::Strict) From 5ea3b1bf9782cd6c44b3ba74cade4024e606f6cd Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 17:38:50 -0400 Subject: [PATCH 19/29] Use " " instead of "" for the empty key so sh doesn't interpret it as falsy --- substrate/client/tests/common/mod.rs | 2 +- substrate/client/tests/dht.rs | 2 +- substrate/client/tests/validator_sets.rs | 2 +- substrate/node/src/keystore.rs | 2 +- tests/coordinator/src/lib.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/substrate/client/tests/common/mod.rs b/substrate/client/tests/common/mod.rs index a6d5300b..d7e8436b 100644 --- a/substrate/client/tests/common/mod.rs +++ b/substrate/client/tests/common/mod.rs @@ -32,7 +32,7 @@ macro_rules! serai_test { .replace_env( HashMap::from([ ("RUST_LOG".to_string(), "runtime=debug".to_string()), - ("KEY".to_string(), String::new()), + ("KEY".to_string(), " ".to_string()), ]) ) .set_publish_all_ports(true) diff --git a/substrate/client/tests/dht.rs b/substrate/client/tests/dht.rs index 4ab177ad..82450e46 100644 --- a/substrate/client/tests/dht.rs +++ b/substrate/client/tests/dht.rs @@ -15,7 +15,7 @@ async fn dht() { Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never), ) .replace_env( - [("SERAI_NAME".to_string(), name.to_string()), ("KEY".to_string(), String::new())].into(), + [("SERAI_NAME".to_string(), name.to_string()), ("KEY".to_string(), " ".to_string())].into(), ) .set_publish_all_ports(true) .set_handle(handle(name)) diff --git a/substrate/client/tests/validator_sets.rs b/substrate/client/tests/validator_sets.rs index 884a42db..8ae150ec 100644 --- a/substrate/client/tests/validator_sets.rs +++ b/substrate/client/tests/validator_sets.rs @@ -102,7 +102,7 @@ async fn validator_set_rotation() { ]) .replace_env(HashMap::from([ ("RUST_LOG".to_string(), "runtime=debug".to_string()), - ("KEY".to_string(), String::new()), + ("KEY".to_string(), " ".to_string()), ])) .set_publish_all_ports(true) .set_handle(handle(name)) diff --git a/substrate/node/src/keystore.rs b/substrate/node/src/keystore.rs index f7b9110f..c313773a 100644 --- a/substrate/node/src/keystore.rs +++ b/substrate/node/src/keystore.rs @@ -8,7 +8,7 @@ pub struct Keystore(sr25519::Pair); impl Keystore { pub fn from_env() -> Option { let mut key_hex = serai_env::var("KEY")?; - if key_hex.is_empty() { + if key_hex.trim().is_empty() { None?; } let mut key = hex::decode(&key_hex).expect("KEY from environment wasn't hex"); diff --git a/tests/coordinator/src/lib.rs b/tests/coordinator/src/lib.rs index d09f4487..e6b0324d 100644 --- a/tests/coordinator/src/lib.rs +++ b/tests/coordinator/src/lib.rs @@ -67,7 +67,7 @@ pub fn serai_composition(name: &str) -> TestBodySpecification { Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never), ) .replace_env( - [("SERAI_NAME".to_string(), name.to_lowercase()), ("KEY".to_string(), String::new())].into(), + [("SERAI_NAME".to_string(), name.to_lowercase()), ("KEY".to_string(), " ".to_string())].into(), ) .set_publish_all_ports(true) } From b7d49af1d51c02ae8508cd3d40042398d4e4bb72 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 18:02:48 -0400 Subject: [PATCH 20/29] Track total peer count in the coordinator --- coordinator/src/p2p.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/coordinator/src/p2p.rs b/coordinator/src/p2p.rs index d840fea8..65b205d5 100644 --- a/coordinator/src/p2p.rs +++ b/coordinator/src/p2p.rs @@ -394,6 +394,7 @@ impl LibP2p { async move { let mut set_for_genesis = HashMap::new(); + let mut connected_peers = 0; loop { let time_since_last = Instant::now().duration_since(time_of_last_p2p_message); tokio::select! { @@ -437,15 +438,25 @@ impl LibP2p { } Some(SwarmEvent::ConnectionEstablished { peer_id, connection_id, .. }) => { if &peer_id == swarm.local_peer_id() { + log::warn!("established a libp2p connection to ourselves"); swarm.close_connection(connection_id); - } else if swarm.is_connected(&peer_id) {} else { - log::debug!( - "connection established to peer {} in connection ID {}", - &peer_id, - &connection_id, - ); - swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id) + continue; } + + connected_peers += 1; + log::debug!( + "connection established to peer {} in connection ID {}, connected peers: {}", + &peer_id, + &connection_id, + connected_peers, + ); + } + Some(SwarmEvent::ConnectionClosed { peer_id, .. }) => { + connected_peers -= 1; + log::debug!( + "connection with peer {peer_id} closed, connected peers: {}", + connected_peers, + ); } Some(SwarmEvent::Behaviour(BehaviorEvent::Gossipsub( GsEvent::Message { propagation_source, message, .. }, From 333a9571b8fff3b446a684182d010437209b6a44 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 22:17:43 -0400 Subject: [PATCH 21/29] Use volumes for message-queue/processors/coordinator/serai --- orchestration/src/coins/bitcoin.rs | 2 +- orchestration/src/coins/monero.rs | 3 +-- orchestration/src/coordinator.rs | 2 +- orchestration/src/main.rs | 40 ++++++++++++++++++++++-------- orchestration/src/message_queue.rs | 2 +- orchestration/src/processor.rs | 2 +- orchestration/testnet/serai/run.sh | 2 +- 7 files changed, 35 insertions(+), 18 deletions(-) diff --git a/orchestration/src/coins/bitcoin.rs b/orchestration/src/coins/bitcoin.rs index 13c86bad..f8b6b7fc 100644 --- a/orchestration/src/coins/bitcoin.rs +++ b/orchestration/src/coins/bitcoin.rs @@ -44,7 +44,7 @@ CMD ["/run.sh"] ); let run = - os(Os::Debian, "RUN mkdir /volume && chown bitcoin:bitcoin /volume", "bitcoin") + &run_bitcoin; + os(Os::Debian, "", "bitcoin") + &run_bitcoin; let res = setup + &run; let mut bitcoin_path = orchestration_path.to_path_buf(); diff --git a/orchestration/src/coins/monero.rs b/orchestration/src/coins/monero.rs index f64f0a04..7df874d2 100644 --- a/orchestration/src/coins/monero.rs +++ b/orchestration/src/coins/monero.rs @@ -57,8 +57,7 @@ CMD ["/run.sh"] let run = crate::os( os, - &("RUN mkdir /volume && chown monero /volume\r\n".to_string() + - if os == Os::Alpine { "RUN apk --no-cache add gcompat" } else { "" }), + if os == Os::Alpine { "RUN apk --no-cache add gcompat" } else { "" }, "monero", ) + &run_monero; let res = setup + &run; diff --git a/orchestration/src/coordinator.rs b/orchestration/src/coordinator.rs index 9995dbbf..67a24527 100644 --- a/orchestration/src/coordinator.rs +++ b/orchestration/src/coordinator.rs @@ -33,7 +33,7 @@ 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()), + ("DB_PATH", "/volume/coordinator-db".to_string()), ("SERAI_KEY", hex::encode(serai_key.to_repr())), ("SERAI_HOSTNAME", format!("serai-{}-serai", network.label())), ("RUST_LOG", DEFAULT_RUST_LOG.to_string()), diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index 548aca8b..c942efe2 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -92,6 +92,9 @@ RUN apk update && apk upgrade # System user (not a human), shell of nologin, no password assigned RUN adduser -S -s /sbin/nologin -D {user} +# Make the /volume directory and transfer it to the user +RUN mkdir /volume && chown {user}:{user} /volume + {additional_root} # Switch to a non-root user @@ -112,6 +115,9 @@ RUN apt update && apt upgrade -y && apt autoremove -y && apt clean RUN useradd --system --create-home --shell /sbin/nologin {user} +# Make the /volume directory and transfer it to the user +RUN mkdir /volume && chown {user}:{user} /volume + {additional_root} # Switch to a non-root user @@ -416,6 +422,10 @@ fn start(network: Network, services: HashSet) { .arg("container") .arg("inspect") .arg(&docker_name) + // Use null for all IO to silence 'container does not exist' + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) .status() .unwrap() .success() @@ -429,38 +439,46 @@ fn start(network: Network, services: HashSet) { let command = command.arg("--restart").arg("always"); let command = command.arg("--log-opt").arg("max-size=100m"); let command = command.arg("--log-opt").arg("max-file=3"); + let command = if network == Network::Dev { + command + } else { + // Assign a persistent volume if this isn't for Dev + command.arg("--volume").arg(volume); + } let command = match name { "bitcoin" => { + // Expose the RPC for tests if network == Network::Dev { command.arg("-p").arg("8332:8332") - } else { - command.arg("--volume").arg(volume) } } "monero" => { + // Expose the RPC for tests if network == Network::Dev { command.arg("-p").arg("18081:18081") - } else { - command.arg("--volume").arg(volume) } } "monero-wallet-rpc" => { assert_eq!(network, Network::Dev, "monero-wallet-rpc is only for dev"); + // Expose the RPC for tests command.arg("-p").arg("18082:18082") } "coordinator" => { - if network != Network::Dev { - command.arg("-p").arg("30563:30563") - } else { + if network == Network::Dev { command + else { + // Publish the port + command.arg("-p").arg("30563:30563") } } "serai" => { - let mut command = command; - if network != Network::Dev { - command = command.arg("-p").arg("30333:30333"); + let mut command = command.arg("--volume").arg(format!("{serai_runtime_volume}:/runtime"); + if network == Network::Dev { + command + } else { + // Publish the port + command.arg("-p").arg("30333:30333") } - command.arg("--volume").arg(format!("{serai_runtime_volume}:/runtime")) } _ => command, }; diff --git a/orchestration/src/message_queue.rs b/orchestration/src/message_queue.rs index 3e47571c..f16c6cbe 100644 --- a/orchestration/src/message_queue.rs +++ b/orchestration/src/message_queue.rs @@ -20,7 +20,7 @@ pub fn message_queue( ("BITCOIN_KEY", hex::encode(bitcoin_key.to_bytes())), ("ETHEREUM_KEY", hex::encode(ethereum_key.to_bytes())), ("MONERO_KEY", hex::encode(monero_key.to_bytes())), - ("DB_PATH", "./message-queue-db".to_string()), + ("DB_PATH", "/volume/message-queue-db".to_string()), ("RUST_LOG", "info,serai_message_queue=trace".to_string()), ]; let mut env_vars_str = String::new(); diff --git a/orchestration/src/processor.rs b/orchestration/src/processor.rs index 3d76a6c9..7ee69d11 100644 --- a/orchestration/src/processor.rs +++ b/orchestration/src/processor.rs @@ -47,7 +47,7 @@ RUN apt install -y ca-certificates ("NETWORK_RPC_LOGIN", format!("{RPC_USER}:{RPC_PASS}")), ("NETWORK_RPC_HOSTNAME", hostname), ("NETWORK_RPC_PORT", format!("{port}")), - ("DB_PATH", "./processor-db".to_string()), + ("DB_PATH", "/volume/processor-db".to_string()), ("RUST_LOG", "info,serai_processor=debug".to_string()), ]; let mut env_vars_str = String::new(); diff --git a/orchestration/testnet/serai/run.sh b/orchestration/testnet/serai/run.sh index 7400ff50..ab3b59df 100755 --- a/orchestration/testnet/serai/run.sh +++ b/orchestration/testnet/serai/run.sh @@ -1,3 +1,3 @@ #!/bin/sh -serai-node --unsafe-rpc-external --rpc-cors all --chain testnet --validator +serai-node --base-path /volume --unsafe-rpc-external --rpc-cors all --chain testnet --validator From 1f92e1cbda9725020e61b8a400f5dfdd88fe26f5 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 22:22:24 -0400 Subject: [PATCH 22/29] Fixes for prior commit --- orchestration/src/coins/bitcoin.rs | 3 +-- orchestration/src/coins/monero.rs | 8 +++----- orchestration/src/main.rs | 21 ++++++++++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/orchestration/src/coins/bitcoin.rs b/orchestration/src/coins/bitcoin.rs index f8b6b7fc..a5c8b21c 100644 --- a/orchestration/src/coins/bitcoin.rs +++ b/orchestration/src/coins/bitcoin.rs @@ -43,8 +43,7 @@ CMD ["/run.sh"] network.label() ); - let run = - os(Os::Debian, "", "bitcoin") + &run_bitcoin; + let run = os(Os::Debian, "", "bitcoin") + &run_bitcoin; let res = setup + &run; let mut bitcoin_path = orchestration_path.to_path_buf(); diff --git a/orchestration/src/coins/monero.rs b/orchestration/src/coins/monero.rs index 7df874d2..873c6458 100644 --- a/orchestration/src/coins/monero.rs +++ b/orchestration/src/coins/monero.rs @@ -55,11 +55,9 @@ CMD ["/run.sh"] network.label(), ); - let run = crate::os( - os, - if os == Os::Alpine { "RUN apk --no-cache add gcompat" } else { "" }, - "monero", - ) + &run_monero; + let run = + crate::os(os, if os == Os::Alpine { "RUN apk --no-cache add gcompat" } else { "" }, "monero") + + &run_monero; let res = setup + &run; let mut monero_path = orchestration_path.to_path_buf(); diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index c942efe2..988358bc 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -2,7 +2,14 @@ // TODO: Generate keys for a validator and the infra use core::ops::Deref; -use std::{collections::HashSet, env, path::PathBuf, io::Write, fs, process::Command}; +use std::{ + collections::HashSet, + env, + path::PathBuf, + io::Write, + fs, + process::{Stdio, Command}, +}; use zeroize::Zeroizing; @@ -443,19 +450,23 @@ fn start(network: Network, services: HashSet) { command } else { // Assign a persistent volume if this isn't for Dev - command.arg("--volume").arg(volume); - } + command.arg("--volume").arg(volume) + }; let command = match name { "bitcoin" => { // Expose the RPC for tests if network == Network::Dev { command.arg("-p").arg("8332:8332") + } else { + command } } "monero" => { // Expose the RPC for tests if network == Network::Dev { command.arg("-p").arg("18081:18081") + } else { + command } } "monero-wallet-rpc" => { @@ -466,13 +477,13 @@ fn start(network: Network, services: HashSet) { "coordinator" => { if network == Network::Dev { command - else { + } else { // Publish the port command.arg("-p").arg("30563:30563") } } "serai" => { - let mut command = command.arg("--volume").arg(format!("{serai_runtime_volume}:/runtime"); + let command = command.arg("--volume").arg(format!("{serai_runtime_volume}:/runtime")); if network == Network::Dev { command } else { From 7408e267816f99aeea8bb96e3b9ae809fca31c92 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 23:13:33 -0400 Subject: [PATCH 23/29] Don't regenerate infrastructure keys Enables running setup without invalidating the message queue --- orchestration/src/main.rs | 87 ++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index 988358bc..823e1b61 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -3,7 +3,7 @@ use core::ops::Deref; use std::{ - collections::HashSet, + collections::{HashSet, HashMap}, env, path::PathBuf, io::Write, @@ -212,6 +212,55 @@ fn orchestration_path(network: Network) -> PathBuf { orchestration_path } +type InfrastructureKeys = + HashMap<&'static str, (Zeroizing<::F>, ::G)>; +fn infrastructure_keys(network: Network) -> InfrastructureKeys { + // Generate entropy for the infrastructure keys + + let entropy = if network == Network::Dev { + // Don't use actual entropy if this is a dev environment + Zeroizing::new([0; 32]) + } else { + let path = home::home_dir() + .unwrap() + .join(".serai") + .join(network.label()) + .join("infrastructure_keys_entropy"); + // Check if there's existing entropy + if let Ok(entropy) = fs::read(&path).map(Zeroizing::new) { + assert_eq!(entropy.len(), 32, "entropy saved to disk wasn't 32 bytes"); + let mut res = Zeroizing::new([0; 32]); + res.copy_from_slice(entropy.as_ref()); + res + } else { + // If there isn't, generate fresh entropy + let mut res = Zeroizing::new([0; 32]); + OsRng.fill_bytes(res.as_mut()); + fs::write(&path, &res).unwrap(); + res + } + }; + + let mut transcript = + RecommendedTranscript::new(b"Serai Orchestrator Infrastructure Keys Transcript"); + transcript.append_message(b"network", network.label().as_bytes()); + transcript.append_message(b"entropy", entropy); + let mut rng = ChaCha20Rng::from_seed(transcript.rng_seed(b"infrastructure_keys")); + + let mut key_pair = || { + let key = Zeroizing::new(::F::random(&mut rng)); + let public = Ristretto::generator() * key.deref(); + (key, public) + }; + + HashMap::from([ + ("coordinator", key_pair()), + ("bitcoin", key_pair()), + ("ethereum", key_pair()), + ("monero", key_pair()), + ]) +} + fn dockerfiles(network: Network) { let orchestration_path = orchestration_path(network); @@ -222,28 +271,11 @@ 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 = 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)); - - let mut message_queue_keys_rng = new_rng(b"message_queue_keys"); - let mut key_pair = || { - let key = Zeroizing::new(::F::random(&mut message_queue_keys_rng)); - let public = Ristretto::generator() * key.deref(); - (key, public) - }; - let coordinator_key = key_pair(); - let bitcoin_key = key_pair(); - let ethereum_key = key_pair(); - let monero_key = key_pair(); + let mut infrastructure_keys = infrastructure_keys(network); + let coordinator_key = infrastructure_keys.remove("coordinator").unwrap(); + let bitcoin_key = infrastructure_keys.remove("bitcoin").unwrap(); + let ethereum_key = infrastructure_keys.remove("ethereum").unwrap(); + let monero_key = infrastructure_keys.remove("monero").unwrap(); message_queue( &orchestration_path, @@ -254,10 +286,9 @@ fn dockerfiles(network: Network) { monero_key.1, ); - let mut processor_entropy_rng = new_rng(b"processor_entropy"); - let mut new_entropy = || { + let new_entropy = || { let mut res = Zeroizing::new([0; 32]); - processor_entropy_rng.fill_bytes(res.as_mut()); + OsRng.fill_bytes(res.as_mut()); res }; processor( @@ -514,10 +545,10 @@ Serai Orchestrator v0.0.1 Commands: key_gen *network* - Generates a key for the validator. + Generate a key for the validator. setup *network* - Generate infrastructure keys and the Dockerfiles for every Serai service. + Generate the Dockerfiles for every Serai service. start *network* [service1, service2...] Start the specified services for the specified network ("dev" or "testnet"). From 4cacce5e55087a21174f05732d30662e83cdfd9f Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 23:30:51 -0400 Subject: [PATCH 24/29] Perform key share amortization on-chain to avoid discrepancies --- coordinator/src/substrate/mod.rs | 12 ++------- substrate/validator-sets/pallet/src/lib.rs | 27 +++++++++++-------- .../validator-sets/primitives/src/lib.rs | 10 +++---- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/coordinator/src/substrate/mod.rs b/coordinator/src/substrate/mod.rs index 7a76353c..fb1e3aed 100644 --- a/coordinator/src/substrate/mod.rs +++ b/coordinator/src/substrate/mod.rs @@ -11,10 +11,7 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto}; use serai_client::{ SeraiError, Block, Serai, TemporalSerai, primitives::{BlockHash, NetworkId}, - validator_sets::{ - primitives::{ValidatorSet, amortize_excess_key_shares}, - ValidatorSetsEvent, - }, + validator_sets::{primitives::ValidatorSet, ValidatorSetsEvent}, in_instructions::InInstructionsEvent, coins::CoinsEvent, }; @@ -69,12 +66,7 @@ async fn handle_new_set( let set_participants = serai.participants(set.network).await?.expect("NewSet for set which doesn't exist"); - let mut set_data = set_participants - .into_iter() - .map(|(k, w)| (k, u16::try_from(w).unwrap())) - .collect::>(); - amortize_excess_key_shares(&mut set_data); - set_data + set_participants.into_iter().map(|(k, w)| (k, u16::try_from(w).unwrap())).collect::>() }; let time = if let Ok(time) = block.time() { diff --git a/substrate/validator-sets/pallet/src/lib.rs b/substrate/validator-sets/pallet/src/lib.rs index 76c07e1c..d1385c2d 100644 --- a/substrate/validator-sets/pallet/src/lib.rs +++ b/substrate/validator-sets/pallet/src/lib.rs @@ -363,21 +363,26 @@ pub mod pallet { let allocation_per_key_share = Self::allocation_per_key_share(network).unwrap().0; - let mut iter = SortedAllocationsIter::::new(network); let mut participants = vec![]; - let mut key_shares = 0; let mut total_stake = 0; - while key_shares < u64::from(MAX_KEY_SHARES_PER_SET) { - let Some((key, amount)) = iter.next() else { break }; + { + let mut iter = SortedAllocationsIter::::new(network); + let mut key_shares = 0; + while key_shares < u64::from(MAX_KEY_SHARES_PER_SET) { + let Some((key, amount)) = iter.next() else { break }; - let these_key_shares = amount.0 / allocation_per_key_share; - InSet::::set(network, key, Some(these_key_shares)); - participants.push((key, these_key_shares)); + let these_key_shares = + (amount.0 / allocation_per_key_share).min(u64::from(MAX_KEY_SHARES_PER_SET)); + participants.push((key, these_key_shares)); - // This can technically set key_shares to a value exceeding MAX_KEY_SHARES_PER_SET - // Off-chain, the key shares per validator will be accordingly adjusted - key_shares += these_key_shares; - total_stake += amount.0; + key_shares += these_key_shares; + total_stake += amount.0; + } + amortize_excess_key_shares(&mut participants); + } + + for (key, shares) in &participants { + InSet::::set(network, key, Some(*shares)); } TotalAllocatedStake::::set(network, Some(Amount(total_stake))); diff --git a/substrate/validator-sets/primitives/src/lib.rs b/substrate/validator-sets/primitives/src/lib.rs index 644b19e1..c900b0a9 100644 --- a/substrate/validator-sets/primitives/src/lib.rs +++ b/substrate/validator-sets/primitives/src/lib.rs @@ -115,11 +115,11 @@ pub fn report_slashes_message(set: &ValidatorSet, slashes: &[(Public, u32)]) -> /// maximum. /// /// Reduction occurs by reducing each validator in a reverse round-robin. -pub fn amortize_excess_key_shares(validators: &mut [(Public, u16)]) { - let total_key_shares = validators.iter().map(|(_, shares)| shares).sum::(); - for i in 0 .. usize::from( - total_key_shares.saturating_sub(u16::try_from(MAX_KEY_SHARES_PER_SET).unwrap()), - ) { +pub fn amortize_excess_key_shares(validators: &mut [(Public, u64)]) { + let total_key_shares = validators.iter().map(|(_, shares)| shares).sum::(); + for i in 0 .. usize::try_from(total_key_shares.saturating_sub(u64::from(MAX_KEY_SHARES_PER_SET))) + .unwrap() + { validators[validators.len() - ((i % validators.len()) + 1)].1 -= 1; } } From bc44fbdbac573efb7342bcf59e4144110356fb43 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 23 Mar 2024 23:31:20 -0400 Subject: [PATCH 25/29] Add TODO to coordinator P2P --- coordinator/src/p2p.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coordinator/src/p2p.rs b/coordinator/src/p2p.rs index 65b205d5..19bf299d 100644 --- a/coordinator/src/p2p.rs +++ b/coordinator/src/p2p.rs @@ -322,6 +322,8 @@ impl LibP2p { to_dial_send.send(addr).unwrap(); }; + // TODO: We should also connect to random peers from random nets as needed for + // cosigning let mut to_retry = vec![]; while let Some(network) = pending_p2p_connections_recv.recv().await { if let Ok(mut nodes) = serai.p2p_validators(network).await { From 07df9aa035f85cff83604de41acad3178fc40e63 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 24 Mar 2024 00:03:32 -0400 Subject: [PATCH 26/29] Ensure user is in a group --- orchestration/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index 823e1b61..13b98554 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -96,8 +96,8 @@ ENV LD_PRELOAD=libmimalloc.so RUN apk update && apk upgrade -# System user (not a human), shell of nologin, no password assigned -RUN adduser -S -s /sbin/nologin -D {user} +RUN adduser --system --shell /sbin/nologin --disabled-password {user} +RUN addgroup {user} {user} # Make the /volume directory and transfer it to the user RUN mkdir /volume && chown {user}:{user} /volume @@ -120,7 +120,7 @@ RUN echo "/usr/lib/libmimalloc.so" >> /etc/ld.so.preload RUN apt update && apt upgrade -y && apt autoremove -y && apt clean -RUN useradd --system --create-home --shell /sbin/nologin {user} +RUN useradd --system --user-group --create-home --shell /sbin/nologin {user} # Make the /volume directory and transfer it to the user RUN mkdir /volume && chown {user}:{user} /volume From 3d855c75be7b863ee7171b44adc497a703d9924a Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 24 Mar 2024 00:18:40 -0400 Subject: [PATCH 27/29] Create group before adding to it --- orchestration/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/orchestration/src/main.rs b/orchestration/src/main.rs index 13b98554..4be84cd4 100644 --- a/orchestration/src/main.rs +++ b/orchestration/src/main.rs @@ -97,6 +97,7 @@ ENV LD_PRELOAD=libmimalloc.so RUN apk update && apk upgrade RUN adduser --system --shell /sbin/nologin --disabled-password {user} +RUN addgroup {user} RUN addgroup {user} {user} # Make the /volume directory and transfer it to the user From 63521f6a965107590d5104ae16264169fd59d3e2 Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Sun, 24 Mar 2024 09:00:54 -0400 Subject: [PATCH 28/29] implement Router.sol and associated functions (#92) * start Router contract * use calldata for function args * var name changes * start testing router contract * test with and without abi.encode * cleanup * why tf isn't tests/utils working * cleanup tests * remove unused files * wip * fix router contract and tests, add set/update public keys funcs * impl some Froms * make execute non-reentrant * cleanup * update Router to use ReentrancyGuard * update contract to use errors, use bitfield in Executed event, minor other fixes * wip * fix build issues from merge, tests ok * Router.sol cleanup * cleanup, uncomment stuff * bump ethers.rs version to latest * make contract functions take generic middleware * update build script to assert no compiler errors * hardcode pubkey parity into contract, update tests * Polish coins/ethereum in various ways --------- Co-authored-by: Luke Parker --- .github/actions/build-dependencies/action.yml | 4 +- coins/ethereum/.gitignore | 6 +- coins/ethereum/Cargo.toml | 3 + coins/ethereum/build.rs | 35 ++++- coins/ethereum/contracts/Router.sol | 90 ++++++++++++ coins/ethereum/contracts/Schnorr.sol | 29 ++-- coins/ethereum/src/abi/mod.rs | 6 + coins/ethereum/src/contract.rs | 36 ----- coins/ethereum/src/crypto.rs | 124 +++++++--------- coins/ethereum/src/lib.rs | 16 ++- coins/ethereum/src/router.rs | 30 ++++ coins/ethereum/src/schnorr.rs | 34 +++++ coins/ethereum/src/tests/crypto.rs | 132 ++++++++++++++++++ coins/ethereum/src/tests/mod.rs | 92 ++++++++++++ coins/ethereum/src/tests/router.rs | 109 +++++++++++++++ coins/ethereum/src/tests/schnorr.rs | 67 +++++++++ coins/ethereum/tests/contract.rs | 128 ----------------- coins/ethereum/tests/crypto.rs | 87 ------------ coins/ethereum/tests/mod.rs | 2 - spec/Getting Started.md | 8 +- 20 files changed, 690 insertions(+), 348 deletions(-) create mode 100644 coins/ethereum/contracts/Router.sol create mode 100644 coins/ethereum/src/abi/mod.rs delete mode 100644 coins/ethereum/src/contract.rs create mode 100644 coins/ethereum/src/router.rs create mode 100644 coins/ethereum/src/schnorr.rs create mode 100644 coins/ethereum/src/tests/crypto.rs create mode 100644 coins/ethereum/src/tests/mod.rs create mode 100644 coins/ethereum/src/tests/router.rs create mode 100644 coins/ethereum/src/tests/schnorr.rs delete mode 100644 coins/ethereum/tests/contract.rs delete mode 100644 coins/ethereum/tests/crypto.rs delete mode 100644 coins/ethereum/tests/mod.rs diff --git a/.github/actions/build-dependencies/action.yml b/.github/actions/build-dependencies/action.yml index 2a8e8ed8..5994b723 100644 --- a/.github/actions/build-dependencies/action.yml +++ b/.github/actions/build-dependencies/action.yml @@ -42,8 +42,8 @@ runs: shell: bash run: | cargo install svm-rs - svm install 0.8.16 - svm use 0.8.16 + svm install 0.8.25 + svm use 0.8.25 # - name: Cache Rust # uses: Swatinem/rust-cache@a95ba195448af2da9b00fb742d14ffaaf3c21f43 diff --git a/coins/ethereum/.gitignore b/coins/ethereum/.gitignore index 6ff35861..46365e03 100644 --- a/coins/ethereum/.gitignore +++ b/coins/ethereum/.gitignore @@ -1,3 +1,7 @@ -# solidity build outputs +# Solidity build outputs cache artifacts + +# Auto-generated ABI files +src/abi/schnorr.rs +src/abi/router.rs diff --git a/coins/ethereum/Cargo.toml b/coins/ethereum/Cargo.toml index 1d1c6dbb..bc60d3a4 100644 --- a/coins/ethereum/Cargo.toml +++ b/coins/ethereum/Cargo.toml @@ -30,6 +30,9 @@ ethers-core = { version = "2", default-features = false } ethers-providers = { version = "2", default-features = false } ethers-contract = { version = "2", default-features = false, features = ["abigen", "providers"] } +[build-dependencies] +ethers-contract = { version = "2", default-features = false, features = ["abigen", "providers"] } + [dev-dependencies] rand_core = { version = "0.6", default-features = false, features = ["std"] } diff --git a/coins/ethereum/build.rs b/coins/ethereum/build.rs index 2166f6ad..3590b12f 100644 --- a/coins/ethereum/build.rs +++ b/coins/ethereum/build.rs @@ -1,6 +1,20 @@ +use std::process::Command; + +use ethers_contract::Abigen; + fn main() { - println!("cargo:rerun-if-changed=contracts"); - println!("cargo:rerun-if-changed=artifacts"); + println!("cargo:rerun-if-changed=contracts/*"); + println!("cargo:rerun-if-changed=artifacts/*"); + + for line in String::from_utf8(Command::new("solc").args(["--version"]).output().unwrap().stdout) + .unwrap() + .lines() + { + if let Some(version) = line.strip_prefix("Version: ") { + let version = version.split('+').next().unwrap(); + assert_eq!(version, "0.8.25"); + } + } #[rustfmt::skip] let args = [ @@ -8,8 +22,21 @@ fn main() { "-o", "./artifacts", "--overwrite", "--bin", "--abi", "--optimize", - "./contracts/Schnorr.sol" + "./contracts/Schnorr.sol", "./contracts/Router.sol", ]; + assert!(Command::new("solc").args(args).status().unwrap().success()); - assert!(std::process::Command::new("solc").args(args).status().unwrap().success()); + Abigen::new("Schnorr", "./artifacts/Schnorr.abi") + .unwrap() + .generate() + .unwrap() + .write_to_file("./src/abi/schnorr.rs") + .unwrap(); + + Abigen::new("Router", "./artifacts/Router.abi") + .unwrap() + .generate() + .unwrap() + .write_to_file("./src/abi/router.rs") + .unwrap(); } diff --git a/coins/ethereum/contracts/Router.sol b/coins/ethereum/contracts/Router.sol new file mode 100644 index 00000000..25775ec5 --- /dev/null +++ b/coins/ethereum/contracts/Router.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity ^0.8.0; + +import "./Schnorr.sol"; + +contract Router is Schnorr { + // Contract initializer + // TODO: Replace with a MuSig of the genesis validators + address public initializer; + + // Nonce is incremented for each batch of transactions executed + uint256 public nonce; + + // fixed parity for the public keys used in this contract + uint8 constant public KEY_PARITY = 27; + + // current public key's x-coordinate + // note: this key must always use the fixed parity defined above + bytes32 public seraiKey; + + struct OutInstruction { + address to; + uint256 value; + bytes data; + } + + struct Signature { + bytes32 c; + bytes32 s; + } + + // success is a uint256 representing a bitfield of transaction successes + event Executed(uint256 nonce, bytes32 batch, uint256 success); + + // error types + error NotInitializer(); + error AlreadyInitialized(); + error InvalidKey(); + error TooManyTransactions(); + + constructor() { + initializer = msg.sender; + } + + // initSeraiKey can be called by the contract initializer to set the first + // public key, only if the public key has yet to be set. + function initSeraiKey(bytes32 _seraiKey) external { + if (msg.sender != initializer) revert NotInitializer(); + if (seraiKey != 0) revert AlreadyInitialized(); + if (_seraiKey == bytes32(0)) revert InvalidKey(); + seraiKey = _seraiKey; + } + + // updateSeraiKey validates the given Schnorr signature against the current public key, + // and if successful, updates the contract's public key to the given one. + function updateSeraiKey( + bytes32 _seraiKey, + Signature memory sig + ) public { + if (_seraiKey == bytes32(0)) revert InvalidKey(); + bytes32 message = keccak256(abi.encodePacked("updateSeraiKey", _seraiKey)); + if (!verify(KEY_PARITY, seraiKey, message, sig.c, sig.s)) revert InvalidSignature(); + seraiKey = _seraiKey; + } + + // execute accepts a list of transactions to execute as well as a Schnorr signature. + // if signature verification passes, the given transactions are executed. + // if signature verification fails, this function will revert. + function execute( + OutInstruction[] calldata transactions, + Signature memory sig + ) public { + if (transactions.length > 256) revert TooManyTransactions(); + + bytes32 message = keccak256(abi.encode("execute", nonce, transactions)); + // This prevents re-entrancy from causing double spends yet does allow + // out-of-order execution via re-entrancy + nonce++; + if (!verify(KEY_PARITY, seraiKey, message, sig.c, sig.s)) revert InvalidSignature(); + + uint256 successes; + for(uint256 i = 0; i < transactions.length; i++) { + (bool success, ) = transactions[i].to.call{value: transactions[i].value, gas: 200_000}(transactions[i].data); + assembly { + successes := or(successes, shl(i, success)) + } + } + emit Executed(nonce, message, successes); + } +} diff --git a/coins/ethereum/contracts/Schnorr.sol b/coins/ethereum/contracts/Schnorr.sol index 3f0196b2..47263e66 100644 --- a/coins/ethereum/contracts/Schnorr.sol +++ b/coins/ethereum/contracts/Schnorr.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: AGPLv3 +// SPDX-License-Identifier: AGPLv3 pragma solidity ^0.8.0; // see https://github.com/noot/schnorr-verify for implementation details @@ -7,29 +7,32 @@ contract Schnorr { uint256 constant public Q = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; + error InvalidSOrA(); + error InvalidSignature(); + // parity := public key y-coord parity (27 or 28) // px := public key x-coord - // message := 32-byte message + // message := 32-byte hash of the message + // c := schnorr signature challenge // s := schnorr signature - // e := schnorr signature challenge function verify( uint8 parity, bytes32 px, bytes32 message, - bytes32 s, - bytes32 e + bytes32 c, + bytes32 s ) public view returns (bool) { // ecrecover = (m, v, r, s); - bytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q)); - bytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q)); + bytes32 sa = bytes32(Q - mulmod(uint256(s), uint256(px), Q)); + bytes32 ca = bytes32(Q - mulmod(uint256(c), uint256(px), Q)); - require(sp != 0); + if (sa == 0) revert InvalidSOrA(); // the ecrecover precompile implementation checks that the `r` and `s` - // inputs are non-zero (in this case, `px` and `ep`), thus we don't need to - // check if they're zero.will make me - address R = ecrecover(sp, parity, px, ep); - require(R != address(0), "ecrecover failed"); - return e == keccak256( + // inputs are non-zero (in this case, `px` and `ca`), thus we don't need to + // check if they're zero. + address R = ecrecover(sa, parity, px, ca); + if (R == address(0)) revert InvalidSignature(); + return c == keccak256( abi.encodePacked(R, uint8(parity), px, block.chainid, message) ); } diff --git a/coins/ethereum/src/abi/mod.rs b/coins/ethereum/src/abi/mod.rs new file mode 100644 index 00000000..2d7dd47c --- /dev/null +++ b/coins/ethereum/src/abi/mod.rs @@ -0,0 +1,6 @@ +#[rustfmt::skip] +#[allow(clippy::all)] +pub(crate) mod schnorr; +#[rustfmt::skip] +#[allow(clippy::all)] +pub(crate) mod router; diff --git a/coins/ethereum/src/contract.rs b/coins/ethereum/src/contract.rs deleted file mode 100644 index 80093b08..00000000 --- a/coins/ethereum/src/contract.rs +++ /dev/null @@ -1,36 +0,0 @@ -use thiserror::Error; -use eyre::{eyre, Result}; - -use ethers_providers::{Provider, Http}; -use ethers_contract::abigen; - -use crate::crypto::ProcessedSignature; - -#[derive(Error, Debug)] -pub enum EthereumError { - #[error("failed to verify Schnorr signature")] - VerificationError, -} - -abigen!(Schnorr, "./artifacts/Schnorr.abi"); - -pub async fn call_verify( - contract: &Schnorr>, - params: &ProcessedSignature, -) -> Result<()> { - if contract - .verify( - params.parity + 27, - params.px.to_bytes().into(), - params.message, - params.s.to_bytes().into(), - params.e.to_bytes().into(), - ) - .call() - .await? - { - Ok(()) - } else { - Err(eyre!(EthereumError::VerificationError)) - } -} diff --git a/coins/ethereum/src/crypto.rs b/coins/ethereum/src/crypto.rs index 3e9d50fa..5f681cfa 100644 --- a/coins/ethereum/src/crypto.rs +++ b/coins/ethereum/src/crypto.rs @@ -1,50 +1,54 @@ use sha3::{Digest, Keccak256}; -use group::Group; +use group::ff::PrimeField; use k256::{ elliptic_curve::{ - bigint::ArrayEncoding, ops::Reduce, point::DecompressPoint, sec1::ToEncodedPoint, + bigint::ArrayEncoding, ops::Reduce, point::AffineCoordinates, sec1::ToEncodedPoint, }, - AffinePoint, ProjectivePoint, Scalar, U256, + ProjectivePoint, Scalar, U256, }; -use frost::{algorithm::Hram, curve::Secp256k1}; +use frost::{ + algorithm::{Hram, SchnorrSignature}, + curve::Secp256k1, +}; -pub fn keccak256(data: &[u8]) -> [u8; 32] { +pub(crate) fn keccak256(data: &[u8]) -> [u8; 32] { Keccak256::digest(data).into() } -pub fn hash_to_scalar(data: &[u8]) -> Scalar { - Scalar::reduce(U256::from_be_slice(&keccak256(data))) -} - -pub fn address(point: &ProjectivePoint) -> [u8; 20] { +pub(crate) fn address(point: &ProjectivePoint) -> [u8; 20] { let encoded_point = point.to_encoded_point(false); - keccak256(&encoded_point.as_ref()[1 .. 65])[12 .. 32].try_into().unwrap() + // Last 20 bytes of the hash of the concatenated x and y coordinates + // We obtain the concatenated x and y coordinates via the uncompressed encoding of the point + keccak256(&encoded_point.as_ref()[1 .. 65])[12 ..].try_into().unwrap() } -pub fn ecrecover(message: Scalar, v: u8, r: Scalar, s: Scalar) -> Option<[u8; 20]> { - if r.is_zero().into() || s.is_zero().into() { - return None; - } +#[allow(non_snake_case)] +pub struct PublicKey { + pub A: ProjectivePoint, + pub px: Scalar, + pub parity: u8, +} +impl PublicKey { #[allow(non_snake_case)] - let R = AffinePoint::decompress(&r.to_bytes(), v.into()); - #[allow(non_snake_case)] - if let Some(R) = Option::::from(R) { - #[allow(non_snake_case)] - let R = ProjectivePoint::from(R); - - let r = r.invert().unwrap(); - let u1 = ProjectivePoint::GENERATOR * (-message * r); - let u2 = R * (s * r); - let key: ProjectivePoint = u1 + u2; - if !bool::from(key.is_identity()) { - return Some(address(&key)); + pub fn new(A: ProjectivePoint) -> Option { + let affine = A.to_affine(); + let parity = u8::from(bool::from(affine.y_is_odd())) + 27; + if parity != 27 { + None?; } - } - None + let x_coord = affine.x(); + let x_coord_scalar = >::reduce_bytes(&x_coord); + // Return None if a reduction would occur + if x_coord_scalar.to_repr() != x_coord { + None?; + } + + Some(PublicKey { A, px: x_coord_scalar, parity }) + } } #[derive(Clone, Default)] @@ -55,53 +59,33 @@ impl Hram for EthereumHram { let a_encoded_point = A.to_encoded_point(true); let mut a_encoded = a_encoded_point.as_ref().to_owned(); a_encoded[0] += 25; // Ethereum uses 27/28 for point parity + assert!((a_encoded[0] == 27) || (a_encoded[0] == 28)); let mut data = address(R).to_vec(); data.append(&mut a_encoded); - data.append(&mut m.to_vec()); + data.extend(m); Scalar::reduce(U256::from_be_slice(&keccak256(&data))) } } -pub struct ProcessedSignature { - pub s: Scalar, - pub px: Scalar, - pub parity: u8, - pub message: [u8; 32], - pub e: Scalar, +pub struct Signature { + pub(crate) c: Scalar, + pub(crate) s: Scalar, } - -#[allow(non_snake_case)] -pub fn preprocess_signature_for_ecrecover( - m: [u8; 32], - R: &ProjectivePoint, - s: Scalar, - A: &ProjectivePoint, - chain_id: U256, -) -> (Scalar, Scalar) { - let processed_sig = process_signature_for_contract(m, R, s, A, chain_id); - let sr = processed_sig.s.mul(&processed_sig.px).negate(); - let er = processed_sig.e.mul(&processed_sig.px).negate(); - (sr, er) -} - -#[allow(non_snake_case)] -pub fn process_signature_for_contract( - m: [u8; 32], - R: &ProjectivePoint, - s: Scalar, - A: &ProjectivePoint, - chain_id: U256, -) -> ProcessedSignature { - let encoded_pk = A.to_encoded_point(true); - let px = &encoded_pk.as_ref()[1 .. 33]; - let px_scalar = Scalar::reduce(U256::from_be_slice(px)); - let e = EthereumHram::hram(R, A, &[chain_id.to_be_byte_array().as_slice(), &m].concat()); - ProcessedSignature { - s, - px: px_scalar, - parity: &encoded_pk.as_ref()[0] - 2, - #[allow(non_snake_case)] - message: m, - e, +impl Signature { + pub fn new( + public_key: &PublicKey, + chain_id: U256, + m: &[u8], + signature: SchnorrSignature, + ) -> Option { + let c = EthereumHram::hram( + &signature.R, + &public_key.A, + &[chain_id.to_be_byte_array().as_slice(), &keccak256(m)].concat(), + ); + if !signature.verify(public_key.A, c) { + None?; + } + Some(Signature { c, s: signature.s }) } } diff --git a/coins/ethereum/src/lib.rs b/coins/ethereum/src/lib.rs index 75a58525..505de38e 100644 --- a/coins/ethereum/src/lib.rs +++ b/coins/ethereum/src/lib.rs @@ -1,2 +1,16 @@ -pub mod contract; +use thiserror::Error; + pub mod crypto; + +pub(crate) mod abi; +pub mod schnorr; +pub mod router; + +#[cfg(test)] +mod tests; + +#[derive(Error, Debug)] +pub enum Error { + #[error("failed to verify Schnorr signature")] + InvalidSignature, +} diff --git a/coins/ethereum/src/router.rs b/coins/ethereum/src/router.rs new file mode 100644 index 00000000..3696fd9b --- /dev/null +++ b/coins/ethereum/src/router.rs @@ -0,0 +1,30 @@ +pub use crate::abi::router::*; + +/* +use crate::crypto::{ProcessedSignature, PublicKey}; +use ethers::{contract::ContractFactory, prelude::*, solc::artifacts::contract::ContractBytecode}; +use eyre::Result; +use std::{convert::From, fs::File, sync::Arc}; + +pub async fn router_update_public_key( + contract: &Router, + public_key: &PublicKey, + signature: &ProcessedSignature, +) -> std::result::Result, eyre::ErrReport> { + let tx = contract.update_public_key(public_key.px.to_bytes().into(), signature.into()); + let pending_tx = tx.send().await?; + let receipt = pending_tx.await?; + Ok(receipt) +} + +pub async fn router_execute( + contract: &Router, + txs: Vec, + signature: &ProcessedSignature, +) -> std::result::Result, eyre::ErrReport> { + let tx = contract.execute(txs, signature.into()).send(); + let pending_tx = tx.send().await?; + let receipt = pending_tx.await?; + Ok(receipt) +} +*/ diff --git a/coins/ethereum/src/schnorr.rs b/coins/ethereum/src/schnorr.rs new file mode 100644 index 00000000..0e4495ec --- /dev/null +++ b/coins/ethereum/src/schnorr.rs @@ -0,0 +1,34 @@ +use eyre::{eyre, Result}; + +use group::ff::PrimeField; + +use ethers_providers::{Provider, Http}; + +use crate::{ + Error, + crypto::{keccak256, PublicKey, Signature}, +}; +pub use crate::abi::schnorr::*; + +pub async fn call_verify( + contract: &Schnorr>, + public_key: &PublicKey, + message: &[u8], + signature: &Signature, +) -> Result<()> { + if contract + .verify( + public_key.parity, + public_key.px.to_repr().into(), + keccak256(message), + signature.c.to_repr().into(), + signature.s.to_repr().into(), + ) + .call() + .await? + { + Ok(()) + } else { + Err(eyre!(Error::InvalidSignature)) + } +} diff --git a/coins/ethereum/src/tests/crypto.rs b/coins/ethereum/src/tests/crypto.rs new file mode 100644 index 00000000..6dced933 --- /dev/null +++ b/coins/ethereum/src/tests/crypto.rs @@ -0,0 +1,132 @@ +use rand_core::OsRng; + +use sha2::Sha256; +use sha3::{Digest, Keccak256}; + +use group::Group; +use k256::{ + ecdsa::{hazmat::SignPrimitive, signature::DigestVerifier, SigningKey, VerifyingKey}, + elliptic_curve::{bigint::ArrayEncoding, ops::Reduce, point::DecompressPoint}, + U256, Scalar, AffinePoint, ProjectivePoint, +}; + +use frost::{ + curve::Secp256k1, + algorithm::{Hram, IetfSchnorr}, + tests::{algorithm_machines, sign}, +}; + +use crate::{crypto::*, tests::key_gen}; + +pub fn hash_to_scalar(data: &[u8]) -> Scalar { + Scalar::reduce(U256::from_be_slice(&keccak256(data))) +} + +pub(crate) fn ecrecover(message: Scalar, v: u8, r: Scalar, s: Scalar) -> Option<[u8; 20]> { + if r.is_zero().into() || s.is_zero().into() || !((v == 27) || (v == 28)) { + return None; + } + + #[allow(non_snake_case)] + let R = AffinePoint::decompress(&r.to_bytes(), (v - 27).into()); + #[allow(non_snake_case)] + if let Some(R) = Option::::from(R) { + #[allow(non_snake_case)] + let R = ProjectivePoint::from(R); + + let r = r.invert().unwrap(); + let u1 = ProjectivePoint::GENERATOR * (-message * r); + let u2 = R * (s * r); + let key: ProjectivePoint = u1 + u2; + if !bool::from(key.is_identity()) { + return Some(address(&key)); + } + } + + None +} + +#[test] +fn test_ecrecover() { + let private = SigningKey::random(&mut OsRng); + let public = VerifyingKey::from(&private); + + // Sign the signature + const MESSAGE: &[u8] = b"Hello, World!"; + let (sig, recovery_id) = private + .as_nonzero_scalar() + .try_sign_prehashed_rfc6979::(&Keccak256::digest(MESSAGE), b"") + .unwrap(); + + // Sanity check the signature verifies + #[allow(clippy::unit_cmp)] // Intended to assert this wasn't changed to Result + { + assert_eq!(public.verify_digest(Keccak256::new_with_prefix(MESSAGE), &sig).unwrap(), ()); + } + + // Perform the ecrecover + assert_eq!( + ecrecover( + hash_to_scalar(MESSAGE), + u8::from(recovery_id.unwrap().is_y_odd()) + 27, + *sig.r(), + *sig.s() + ) + .unwrap(), + address(&ProjectivePoint::from(public.as_affine())) + ); +} + +// Run the sign test with the EthereumHram +#[test] +fn test_signing() { + let (keys, _) = key_gen(); + + const MESSAGE: &[u8] = b"Hello, World!"; + + let algo = IetfSchnorr::::ietf(); + let _sig = + sign(&mut OsRng, &algo, keys.clone(), algorithm_machines(&mut OsRng, &algo, &keys), MESSAGE); +} + +#[allow(non_snake_case)] +pub fn preprocess_signature_for_ecrecover( + R: ProjectivePoint, + public_key: &PublicKey, + chain_id: U256, + m: &[u8], + s: Scalar, +) -> (u8, Scalar, Scalar) { + let c = EthereumHram::hram( + &R, + &public_key.A, + &[chain_id.to_be_byte_array().as_slice(), &keccak256(m)].concat(), + ); + let sa = -(s * public_key.px); + let ca = -(c * public_key.px); + (public_key.parity, sa, ca) +} + +#[test] +fn test_ecrecover_hack() { + let (keys, public_key) = key_gen(); + + const MESSAGE: &[u8] = b"Hello, World!"; + let hashed_message = keccak256(MESSAGE); + let chain_id = U256::ONE; + let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat(); + + let algo = IetfSchnorr::::ietf(); + let sig = sign( + &mut OsRng, + &algo, + keys.clone(), + algorithm_machines(&mut OsRng, &algo, &keys), + full_message, + ); + + let (parity, sa, ca) = + preprocess_signature_for_ecrecover(sig.R, &public_key, chain_id, MESSAGE, sig.s); + let q = ecrecover(sa, parity, public_key.px, ca).unwrap(); + assert_eq!(q, address(&sig.R)); +} diff --git a/coins/ethereum/src/tests/mod.rs b/coins/ethereum/src/tests/mod.rs new file mode 100644 index 00000000..c468cfb6 --- /dev/null +++ b/coins/ethereum/src/tests/mod.rs @@ -0,0 +1,92 @@ +use std::{sync::Arc, time::Duration, fs::File, collections::HashMap}; + +use rand_core::OsRng; + +use group::ff::PrimeField; +use k256::{Scalar, ProjectivePoint}; +use frost::{curve::Secp256k1, Participant, ThresholdKeys, tests::key_gen as frost_key_gen}; + +use ethers_core::{ + types::{H160, Signature as EthersSignature}, + abi::Abi, +}; +use ethers_contract::ContractFactory; +use ethers_providers::{Middleware, Provider, Http}; + +use crate::crypto::PublicKey; + +mod crypto; +mod schnorr; +mod router; + +pub fn key_gen() -> (HashMap>, PublicKey) { + let mut keys = frost_key_gen::<_, Secp256k1>(&mut OsRng); + let mut group_key = keys[&Participant::new(1).unwrap()].group_key(); + + let mut offset = Scalar::ZERO; + while PublicKey::new(group_key).is_none() { + offset += Scalar::ONE; + group_key += ProjectivePoint::GENERATOR; + } + for keys in keys.values_mut() { + *keys = keys.offset(offset); + } + let public_key = PublicKey::new(group_key).unwrap(); + + (keys, public_key) +} + +// TODO: Replace with a contract deployment from an unknown account, so the environment solely has +// to fund the deployer, not create/pass a wallet +// TODO: Deterministic deployments across chains +pub async fn deploy_contract( + chain_id: u32, + client: Arc>, + wallet: &k256::ecdsa::SigningKey, + name: &str, +) -> eyre::Result { + let abi: Abi = + serde_json::from_reader(File::open(format!("./artifacts/{name}.abi")).unwrap()).unwrap(); + + let hex_bin_buf = std::fs::read_to_string(format!("./artifacts/{name}.bin")).unwrap(); + let hex_bin = + if let Some(stripped) = hex_bin_buf.strip_prefix("0x") { stripped } else { &hex_bin_buf }; + let bin = hex::decode(hex_bin).unwrap(); + let factory = ContractFactory::new(abi, bin.into(), client.clone()); + + let mut deployment_tx = factory.deploy(())?.tx; + deployment_tx.set_chain_id(chain_id); + deployment_tx.set_gas(1_000_000); + let (max_fee_per_gas, max_priority_fee_per_gas) = client.estimate_eip1559_fees(None).await?; + deployment_tx.as_eip1559_mut().unwrap().max_fee_per_gas = Some(max_fee_per_gas); + deployment_tx.as_eip1559_mut().unwrap().max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + + let sig_hash = deployment_tx.sighash(); + let (sig, rid) = wallet.sign_prehash_recoverable(sig_hash.as_ref()).unwrap(); + + // EIP-155 v + let mut v = u64::from(rid.to_byte()); + assert!((v == 0) || (v == 1)); + v += u64::from((chain_id * 2) + 35); + + let r = sig.r().to_repr(); + let r_ref: &[u8] = r.as_ref(); + let s = sig.s().to_repr(); + let s_ref: &[u8] = s.as_ref(); + let deployment_tx = + deployment_tx.rlp_signed(&EthersSignature { r: r_ref.into(), s: s_ref.into(), v }); + + let pending_tx = client.send_raw_transaction(deployment_tx).await?; + + let mut receipt; + while { + receipt = client.get_transaction_receipt(pending_tx.tx_hash()).await?; + receipt.is_none() + } { + tokio::time::sleep(Duration::from_secs(6)).await; + } + let receipt = receipt.unwrap(); + assert!(receipt.status == Some(1.into())); + + Ok(receipt.contract_address.unwrap()) +} diff --git a/coins/ethereum/src/tests/router.rs b/coins/ethereum/src/tests/router.rs new file mode 100644 index 00000000..c9be93be --- /dev/null +++ b/coins/ethereum/src/tests/router.rs @@ -0,0 +1,109 @@ +use std::{convert::TryFrom, sync::Arc, collections::HashMap}; + +use rand_core::OsRng; + +use group::ff::PrimeField; +use frost::{ + curve::Secp256k1, + Participant, ThresholdKeys, + algorithm::IetfSchnorr, + tests::{algorithm_machines, sign}, +}; + +use ethers_core::{ + types::{H160, U256, Bytes}, + abi::AbiEncode, + utils::{Anvil, AnvilInstance}, +}; +use ethers_providers::{Middleware, Provider, Http}; + +use crate::{ + crypto::{keccak256, PublicKey, EthereumHram, Signature}, + router::{self, *}, + tests::{key_gen, deploy_contract}, +}; + +async fn setup_test() -> ( + u32, + AnvilInstance, + Router>, + HashMap>, + PublicKey, +) { + let anvil = Anvil::new().spawn(); + + let provider = Provider::::try_from(anvil.endpoint()).unwrap(); + let chain_id = provider.get_chainid().await.unwrap().as_u32(); + let wallet = anvil.keys()[0].clone().into(); + let client = Arc::new(provider); + + let contract_address = + deploy_contract(chain_id, client.clone(), &wallet, "Router").await.unwrap(); + let contract = Router::new(contract_address, client.clone()); + + let (keys, public_key) = key_gen(); + + // Set the key to the threshold keys + let tx = contract.init_serai_key(public_key.px.to_repr().into()).gas(100_000); + let pending_tx = tx.send().await.unwrap(); + let receipt = pending_tx.await.unwrap().unwrap(); + assert!(receipt.status == Some(1.into())); + + (chain_id, anvil, contract, keys, public_key) +} + +#[tokio::test] +async fn test_deploy_contract() { + setup_test().await; +} + +pub fn hash_and_sign( + keys: &HashMap>, + public_key: &PublicKey, + chain_id: U256, + message: &[u8], +) -> Signature { + let hashed_message = keccak256(message); + + let mut chain_id_bytes = [0; 32]; + chain_id.to_big_endian(&mut chain_id_bytes); + let full_message = &[chain_id_bytes.as_slice(), &hashed_message].concat(); + + let algo = IetfSchnorr::::ietf(); + let sig = sign( + &mut OsRng, + &algo, + keys.clone(), + algorithm_machines(&mut OsRng, &algo, keys), + full_message, + ); + + Signature::new(public_key, k256::U256::from_words(chain_id.0), message, sig).unwrap() +} + +#[tokio::test] +async fn test_router_execute() { + let (chain_id, _anvil, contract, keys, public_key) = setup_test().await; + + let to = H160([0u8; 20]); + let value = U256([0u64; 4]); + let data = Bytes::from([0]); + let tx = OutInstruction { to, value, data: data.clone() }; + + let nonce_call = contract.nonce(); + let nonce = nonce_call.call().await.unwrap(); + + let encoded = + ("execute".to_string(), nonce, vec![router::OutInstruction { to, value, data }]).encode(); + let sig = hash_and_sign(&keys, &public_key, chain_id.into(), &encoded); + + let tx = contract + .execute(vec![tx], router::Signature { c: sig.c.to_repr().into(), s: sig.s.to_repr().into() }) + .gas(300_000); + let pending_tx = tx.send().await.unwrap(); + let receipt = dbg!(pending_tx.await.unwrap().unwrap()); + assert!(receipt.status == Some(1.into())); + + println!("gas used: {:?}", receipt.cumulative_gas_used); + println!("logs: {:?}", receipt.logs); +} diff --git a/coins/ethereum/src/tests/schnorr.rs b/coins/ethereum/src/tests/schnorr.rs new file mode 100644 index 00000000..9525e4d6 --- /dev/null +++ b/coins/ethereum/src/tests/schnorr.rs @@ -0,0 +1,67 @@ +use std::{convert::TryFrom, sync::Arc}; + +use rand_core::OsRng; + +use ::k256::{elliptic_curve::bigint::ArrayEncoding, U256, Scalar}; + +use ethers_core::utils::{keccak256, Anvil, AnvilInstance}; +use ethers_providers::{Middleware, Provider, Http}; + +use frost::{ + curve::Secp256k1, + algorithm::IetfSchnorr, + tests::{algorithm_machines, sign}, +}; + +use crate::{ + crypto::*, + schnorr::*, + tests::{key_gen, deploy_contract}, +}; + +async fn setup_test() -> (u32, AnvilInstance, Schnorr>) { + let anvil = Anvil::new().spawn(); + + let provider = Provider::::try_from(anvil.endpoint()).unwrap(); + let chain_id = provider.get_chainid().await.unwrap().as_u32(); + let wallet = anvil.keys()[0].clone().into(); + let client = Arc::new(provider); + + let contract_address = + deploy_contract(chain_id, client.clone(), &wallet, "Schnorr").await.unwrap(); + let contract = Schnorr::new(contract_address, client.clone()); + (chain_id, anvil, contract) +} + +#[tokio::test] +async fn test_deploy_contract() { + setup_test().await; +} + +#[tokio::test] +async fn test_ecrecover_hack() { + let (chain_id, _anvil, contract) = setup_test().await; + let chain_id = U256::from(chain_id); + + let (keys, public_key) = key_gen(); + + const MESSAGE: &[u8] = b"Hello, World!"; + let hashed_message = keccak256(MESSAGE); + let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat(); + + let algo = IetfSchnorr::::ietf(); + let sig = sign( + &mut OsRng, + &algo, + keys.clone(), + algorithm_machines(&mut OsRng, &algo, &keys), + full_message, + ); + let sig = Signature::new(&public_key, chain_id, MESSAGE, sig).unwrap(); + + call_verify(&contract, &public_key, MESSAGE, &sig).await.unwrap(); + // Test an invalid signature fails + let mut sig = sig; + sig.s += Scalar::ONE; + assert!(call_verify(&contract, &public_key, MESSAGE, &sig).await.is_err()); +} diff --git a/coins/ethereum/tests/contract.rs b/coins/ethereum/tests/contract.rs deleted file mode 100644 index 37875819..00000000 --- a/coins/ethereum/tests/contract.rs +++ /dev/null @@ -1,128 +0,0 @@ -use std::{convert::TryFrom, sync::Arc, time::Duration, fs::File}; - -use rand_core::OsRng; - -use ::k256::{ - elliptic_curve::{bigint::ArrayEncoding, PrimeField}, - U256, -}; - -use ethers_core::{ - types::Signature, - abi::Abi, - utils::{keccak256, Anvil, AnvilInstance}, -}; -use ethers_contract::ContractFactory; -use ethers_providers::{Middleware, Provider, Http}; - -use frost::{ - curve::Secp256k1, - Participant, - algorithm::IetfSchnorr, - tests::{key_gen, algorithm_machines, sign}, -}; - -use ethereum_serai::{ - crypto, - contract::{Schnorr, call_verify}, -}; - -// TODO: Replace with a contract deployment from an unknown account, so the environment solely has -// to fund the deployer, not create/pass a wallet -pub async fn deploy_schnorr_verifier_contract( - chain_id: u32, - client: Arc>, - wallet: &k256::ecdsa::SigningKey, -) -> eyre::Result>> { - let abi: Abi = serde_json::from_reader(File::open("./artifacts/Schnorr.abi").unwrap()).unwrap(); - - let hex_bin_buf = std::fs::read_to_string("./artifacts/Schnorr.bin").unwrap(); - let hex_bin = - if let Some(stripped) = hex_bin_buf.strip_prefix("0x") { stripped } else { &hex_bin_buf }; - let bin = hex::decode(hex_bin).unwrap(); - let factory = ContractFactory::new(abi, bin.into(), client.clone()); - - let mut deployment_tx = factory.deploy(())?.tx; - deployment_tx.set_chain_id(chain_id); - deployment_tx.set_gas(500_000); - let (max_fee_per_gas, max_priority_fee_per_gas) = client.estimate_eip1559_fees(None).await?; - deployment_tx.as_eip1559_mut().unwrap().max_fee_per_gas = Some(max_fee_per_gas); - deployment_tx.as_eip1559_mut().unwrap().max_priority_fee_per_gas = Some(max_priority_fee_per_gas); - - let sig_hash = deployment_tx.sighash(); - let (sig, rid) = wallet.sign_prehash_recoverable(sig_hash.as_ref()).unwrap(); - - // EIP-155 v - let mut v = u64::from(rid.to_byte()); - assert!((v == 0) || (v == 1)); - v += u64::from((chain_id * 2) + 35); - - let r = sig.r().to_repr(); - let r_ref: &[u8] = r.as_ref(); - let s = sig.s().to_repr(); - let s_ref: &[u8] = s.as_ref(); - let deployment_tx = deployment_tx.rlp_signed(&Signature { r: r_ref.into(), s: s_ref.into(), v }); - - let pending_tx = client.send_raw_transaction(deployment_tx).await?; - - let mut receipt; - while { - receipt = client.get_transaction_receipt(pending_tx.tx_hash()).await?; - receipt.is_none() - } { - tokio::time::sleep(Duration::from_secs(6)).await; - } - let receipt = receipt.unwrap(); - assert!(receipt.status == Some(1.into())); - - let contract = Schnorr::new(receipt.contract_address.unwrap(), client.clone()); - Ok(contract) -} - -async fn deploy_test_contract() -> (u32, AnvilInstance, Schnorr>) { - let anvil = Anvil::new().spawn(); - - let provider = - Provider::::try_from(anvil.endpoint()).unwrap().interval(Duration::from_millis(10u64)); - let chain_id = provider.get_chainid().await.unwrap().as_u32(); - let wallet = anvil.keys()[0].clone().into(); - let client = Arc::new(provider); - - (chain_id, anvil, deploy_schnorr_verifier_contract(chain_id, client, &wallet).await.unwrap()) -} - -#[tokio::test] -async fn test_deploy_contract() { - deploy_test_contract().await; -} - -#[tokio::test] -async fn test_ecrecover_hack() { - let (chain_id, _anvil, contract) = deploy_test_contract().await; - let chain_id = U256::from(chain_id); - - let keys = key_gen::<_, Secp256k1>(&mut OsRng); - let group_key = keys[&Participant::new(1).unwrap()].group_key(); - - const MESSAGE: &[u8] = b"Hello, World!"; - let hashed_message = keccak256(MESSAGE); - - let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat(); - - let algo = IetfSchnorr::::ietf(); - let sig = sign( - &mut OsRng, - &algo, - keys.clone(), - algorithm_machines(&mut OsRng, &algo, &keys), - full_message, - ); - let mut processed_sig = - crypto::process_signature_for_contract(hashed_message, &sig.R, sig.s, &group_key, chain_id); - - call_verify(&contract, &processed_sig).await.unwrap(); - - // test invalid signature fails - processed_sig.message[0] = 0; - assert!(call_verify(&contract, &processed_sig).await.is_err()); -} diff --git a/coins/ethereum/tests/crypto.rs b/coins/ethereum/tests/crypto.rs deleted file mode 100644 index f1ab08b0..00000000 --- a/coins/ethereum/tests/crypto.rs +++ /dev/null @@ -1,87 +0,0 @@ -use k256::{ - elliptic_curve::{bigint::ArrayEncoding, ops::Reduce, sec1::ToEncodedPoint}, - ProjectivePoint, Scalar, U256, -}; -use frost::{curve::Secp256k1, Participant}; - -use ethereum_serai::crypto::*; - -#[test] -fn test_ecrecover() { - use rand_core::OsRng; - use sha2::Sha256; - use sha3::{Digest, Keccak256}; - use k256::ecdsa::{hazmat::SignPrimitive, signature::DigestVerifier, SigningKey, VerifyingKey}; - - let private = SigningKey::random(&mut OsRng); - let public = VerifyingKey::from(&private); - - const MESSAGE: &[u8] = b"Hello, World!"; - let (sig, recovery_id) = private - .as_nonzero_scalar() - .try_sign_prehashed_rfc6979::(&Keccak256::digest(MESSAGE), b"") - .unwrap(); - #[allow(clippy::unit_cmp)] // Intended to assert this wasn't changed to Result - { - assert_eq!(public.verify_digest(Keccak256::new_with_prefix(MESSAGE), &sig).unwrap(), ()); - } - - assert_eq!( - ecrecover(hash_to_scalar(MESSAGE), recovery_id.unwrap().is_y_odd().into(), *sig.r(), *sig.s()) - .unwrap(), - address(&ProjectivePoint::from(public.as_affine())) - ); -} - -#[test] -fn test_signing() { - use frost::{ - algorithm::IetfSchnorr, - tests::{algorithm_machines, key_gen, sign}, - }; - use rand_core::OsRng; - - let keys = key_gen::<_, Secp256k1>(&mut OsRng); - let _group_key = keys[&Participant::new(1).unwrap()].group_key(); - - const MESSAGE: &[u8] = b"Hello, World!"; - - let algo = IetfSchnorr::::ietf(); - let _sig = - sign(&mut OsRng, &algo, keys.clone(), algorithm_machines(&mut OsRng, &algo, &keys), MESSAGE); -} - -#[test] -fn test_ecrecover_hack() { - use frost::{ - algorithm::IetfSchnorr, - tests::{algorithm_machines, key_gen, sign}, - }; - use rand_core::OsRng; - - let keys = key_gen::<_, Secp256k1>(&mut OsRng); - let group_key = keys[&Participant::new(1).unwrap()].group_key(); - let group_key_encoded = group_key.to_encoded_point(true); - let group_key_compressed = group_key_encoded.as_ref(); - let group_key_x = Scalar::reduce(U256::from_be_slice(&group_key_compressed[1 .. 33])); - - const MESSAGE: &[u8] = b"Hello, World!"; - let hashed_message = keccak256(MESSAGE); - let chain_id = U256::ONE; - - let full_message = &[chain_id.to_be_byte_array().as_slice(), &hashed_message].concat(); - - let algo = IetfSchnorr::::ietf(); - let sig = sign( - &mut OsRng, - &algo, - keys.clone(), - algorithm_machines(&mut OsRng, &algo, &keys), - full_message, - ); - - let (sr, er) = - preprocess_signature_for_ecrecover(hashed_message, &sig.R, sig.s, &group_key, chain_id); - let q = ecrecover(sr, group_key_compressed[0] - 2, group_key_x, er).unwrap(); - assert_eq!(q, address(&sig.R)); -} diff --git a/coins/ethereum/tests/mod.rs b/coins/ethereum/tests/mod.rs deleted file mode 100644 index 257fb61f..00000000 --- a/coins/ethereum/tests/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod contract; -mod crypto; diff --git a/spec/Getting Started.md b/spec/Getting Started.md index 0034d69d..c2530b2a 100644 --- a/spec/Getting Started.md +++ b/spec/Getting Started.md @@ -36,16 +36,16 @@ rustup target add wasm32-unknown-unknown --toolchain nightly ``` cargo install svm-rs -svm install 0.8.16 -svm use 0.8.16 +svm install 0.8.25 +svm use 0.8.25 ``` ### Install Solidity Compiler Version Manager ``` cargo install svm-rs -svm install 0.8.16 -svm use 0.8.16 +svm install 0.8.25 +svm use 0.8.25 ``` ### Install foundry (for tests) From 93be7a30674ecedfb325b6d09dc22d550d7c13f8 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 27 Mar 2024 00:17:04 -0400 Subject: [PATCH 29/29] Latest hyper-rustls, remove async-recursion I didn't remove async-recursion when I updated the repo to 1.77 as I forgot we used it in the tests. I still had to add some Box::pins, which may have been a valid option, on the prior Rust version, yet at least resolves everything now. Also updates everything which doesn't introduce further depends. --- Cargo.lock | 371 ++++++++++++++--------------- common/request/Cargo.toml | 2 +- tests/coordinator/Cargo.toml | 1 - tests/coordinator/src/tests/mod.rs | 6 +- tests/full-stack/Cargo.toml | 1 - tests/full-stack/src/tests/mod.rs | 93 ++++---- 6 files changed, 233 insertions(+), 241 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ae1d463..ee0c2692 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,9 +86,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -173,9 +173,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "approx" @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock", "cfg-if", @@ -290,26 +290,15 @@ dependencies = [ "pin-project-lite 0.2.13", ] -[[package]] -name = "async-recursion" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.52", -] - [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -342,7 +331,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http 0.2.11", + "http 0.2.12", "log", "url", ] @@ -355,14 +344,14 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" @@ -449,7 +438,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cexpr", "clang-sys", "itertools", @@ -460,7 +449,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -528,9 +517,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitvec" @@ -577,9 +566,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -640,7 +629,7 @@ dependencies = [ "futures-core", "futures-util", "hex", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.28", "hyperlocal", "log", @@ -670,9 +659,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" dependencies = [ "borsh-derive", "cfg_aliases", @@ -680,15 +669,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "syn_derive", ] @@ -706,9 +695,9 @@ dependencies = [ [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "tinyvec", ] @@ -746,9 +735,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -758,9 +747,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] @@ -787,9 +776,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -975,7 +964,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1312,14 +1301,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "cxx" -version = "1.0.119" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "635179be18797d7e10edb9cd06c859580237750c7351f39ed9b298bfc17544ad" +checksum = "ff4dc7287237dd438b926a81a1a5605dad33d286870e5eee2db17bf2bcd9e92a" dependencies = [ "cc", "cxxbridge-flags", @@ -1329,9 +1318,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.119" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9324397d262f63ef77eb795d900c0d682a34a43ac0932bec049ed73055d52f63" +checksum = "f47c6c8ad7c1a10d3ef0fe3ff6733f4db0d78f08ef0b13121543163ef327058b" dependencies = [ "cc", "codespan-reporting", @@ -1339,24 +1328,24 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "cxxbridge-flags" -version = "1.0.119" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87ff7342ffaa54b7c61618e0ce2bbcf827eba6d55b923b83d82551acbbecfe5" +checksum = "701a1ac7a697e249cdd8dc026d7a7dafbfd0dbcd8bd24ec55889f2bc13dd6287" [[package]] name = "cxxbridge-macro" -version = "1.0.119" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b5b86cf65fa0626d85720619d80b288013477a91a0389fa8bc716bf4903ad1" +checksum = "b404f596046b0bb2d903a9c786b875a126261b52b7c3a64bbb66382c41c771df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1542,7 +1531,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1775,7 +1764,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1918,7 +1907,7 @@ dependencies = [ "regex", "serde", "serde_json", - "syn 2.0.52", + "syn 2.0.55", "toml 0.7.8", "walkdir", ] @@ -1936,7 +1925,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -1962,7 +1951,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.52", + "syn 2.0.55", "tempfile", "thiserror", "tiny-keccak", @@ -1986,7 +1975,7 @@ dependencies = [ "futures-timer", "futures-util", "hashers", - "http 0.2.11", + "http 0.2.12", "instant", "jsonwebtoken", "once_cell", @@ -2051,7 +2040,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2072,9 +2061,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fdlimit" @@ -2112,9 +2101,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] name = "file-per-thread-logger" @@ -2325,7 +2314,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2337,7 +2326,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2347,7 +2336,7 @@ source = "git+https://github.com/serai-dex/substrate#6e3f07bf5c98a6a3ec15f2b1a46 dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2490,9 +2479,9 @@ checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "futures-core", "pin-project-lite 0.2.13", @@ -2506,7 +2495,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -2585,7 +2574,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "debugid", "fxhash", "serde", @@ -2679,7 +2668,7 @@ dependencies = [ "bstr", "log", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -2707,17 +2696,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.11", - "indexmap 2.2.5", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2858,9 +2847,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -2885,7 +2874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite 0.2.13", ] @@ -2901,12 +2890,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", - "futures-util", + "futures-core", "http 1.1.0", "http-body 1.0.0", "pin-project-lite 0.2.13", @@ -2947,13 +2936,13 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite 0.2.13", - "socket2 0.5.6", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -2981,15 +2970,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +checksum = "736f15a50e749d033164c56c09783b6102c4ff8da79ad77dbddbbaea0f8567f7" dependencies = [ "futures-util", "http 1.1.0", "hyper 1.2.0", "hyper-util", - "rustls 0.22.2", + "rustls 0.23.4", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -3113,7 +3102,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.28", "log", "rand", @@ -3179,9 +3168,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -3255,9 +3244,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" @@ -3328,7 +3317,7 @@ checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba" dependencies = [ "futures-channel", "futures-util", - "http 0.2.11", + "http 0.2.12", "hyper 0.14.28", "jsonrpsee-core", "jsonrpsee-types", @@ -3833,7 +3822,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -3953,9 +3942,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.15" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "pkg-config", @@ -4086,7 +4075,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4100,7 +4089,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4111,7 +4100,7 @@ checksum = "d710e1214dffbab3b5dacb21475dde7d6ed84c69ff722b3a47a782668d44fbac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4122,7 +4111,7 @@ checksum = "b8fb85ec1620619edf2984a7693497d4ec88a9665d8b87e942856884c92dbf2a" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -4199,9 +4188,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -4761,7 +4750,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5200,7 +5189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.5", + "indexmap 2.2.6", ] [[package]] @@ -5230,7 +5219,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5269,18 +5258,19 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "polling" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi", "pin-project-lite 0.2.13", "rustix", "tracing", @@ -5369,7 +5359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5453,14 +5443,14 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -5499,7 +5489,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5724,9 +5714,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -5800,7 +5790,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -5818,14 +5808,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.6", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -5845,7 +5835,7 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -5856,9 +5846,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" @@ -5872,7 +5862,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 0.2.11", + "http 0.2.12", "http-body 0.4.6", "hyper 0.14.28", "ipnet", @@ -6067,11 +6057,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -6092,10 +6082,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1" dependencies = [ + "once_cell", "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.2", @@ -6128,9 +6119,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" +checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da" [[package]] name = "rustls-webpki" @@ -6298,7 +6289,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -6875,7 +6866,7 @@ name = "sc-rpc-server" version = "4.0.0-dev" source = "git+https://github.com/serai-dex/substrate#6e3f07bf5c98a6a3ec15f2b1a46148aa8c7d737a" dependencies = [ - "http 0.2.11", + "http 0.2.12", "jsonrpsee", "log", "serde_json", @@ -7059,7 +7050,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -7121,9 +7112,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +checksum = "788745a868b0e751750388f4e6546eb921ef714a4317fa6954f7cde114eb2eb7" dependencies = [ "bitvec", "cfg-if", @@ -7135,9 +7126,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +checksum = "7dc2f4e8bc344b9fc3d5f74f72c2e55bfc38d28dc2ebc69c194a3df424e4d9ac" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -7443,7 +7434,6 @@ dependencies = [ name = "serai-coordinator-tests" version = "0.1.0" dependencies = [ - "async-recursion", "async-trait", "blake2", "borsh", @@ -7505,7 +7495,6 @@ version = "0.1.0" name = "serai-full-stack-tests" version = "0.1.0" dependencies = [ - "async-recursion", "async-trait", "bitcoin-serai", "curve25519-dalek", @@ -7911,14 +7900,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -7933,7 +7922,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -7959,15 +7948,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" dependencies = [ "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", @@ -8115,9 +8104,9 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snap" @@ -8171,7 +8160,7 @@ dependencies = [ "base64 0.13.1", "bytes", "futures", - "http 0.2.11", + "http 0.2.12", "httparse", "log", "rand", @@ -8210,7 +8199,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -8406,7 +8395,7 @@ source = "git+https://github.com/serai-dex/substrate#6e3f07bf5c98a6a3ec15f2b1a46 dependencies = [ "quote", "sp-core-hashing", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -8425,7 +8414,7 @@ source = "git+https://github.com/serai-dex/substrate#6e3f07bf5c98a6a3ec15f2b1a46 dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -8597,7 +8586,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -8750,7 +8739,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -8811,9 +8800,9 @@ checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" [[package]] name = "ss58-registry" -version = "1.46.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1114ee5900b8569bbc8b1a014a942f937b752af4b44f4607430b5f86cedaac0" +checksum = "4743ce898933fbff7bbf414f497c459a782d496269644b3d650a398ae6a487ba" dependencies = [ "Inflector", "num-format", @@ -8929,7 +8918,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -9017,9 +9006,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -9035,7 +9024,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -9127,22 +9116,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -9263,25 +9252,25 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.22.2", + "rustls 0.23.4", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite 0.2.13", @@ -9340,7 +9329,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -9353,7 +9342,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -9380,11 +9369,11 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "bytes", "futures-core", "futures-util", - "http 0.2.11", + "http 0.2.12", "http-body 0.4.6", "http-range-header", "pin-project-lite 0.2.13", @@ -9424,7 +9413,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -9755,9 +9744,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" [[package]] name = "valuable" @@ -9829,7 +9818,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "wasm-bindgen-shared", ] @@ -9863,7 +9852,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -9953,7 +9942,7 @@ version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dfcdb72d96f01e6c85b6bf20102e7423bdbaad5c337301bab2bbf253d26413c" dependencies = [ - "indexmap 2.2.5", + "indexmap 2.2.6", "semver 1.0.22", ] @@ -9968,7 +9957,7 @@ dependencies = [ "bumpalo", "cfg-if", "fxprof-processed-profile", - "indexmap 2.2.5", + "indexmap 2.2.6", "libc", "log", "object 0.31.1", @@ -10067,7 +10056,7 @@ dependencies = [ "anyhow", "cranelift-entity", "gimli 0.27.3", - "indexmap 2.2.5", + "indexmap 2.2.6", "log", "object 0.31.1", "serde", @@ -10134,7 +10123,7 @@ dependencies = [ "anyhow", "cc", "cfg-if", - "indexmap 2.2.5", + "indexmap 2.2.6", "libc", "log", "mach", @@ -10172,7 +10161,7 @@ checksum = "ca7af9bb3ee875c4907835e607a275d10b04d15623d3aebe01afe8fbd3f85050" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -10549,7 +10538,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] @@ -10569,7 +10558,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.55", ] [[package]] diff --git a/common/request/Cargo.toml b/common/request/Cargo.toml index 0fe9ae5c..e5018056 100644 --- a/common/request/Cargo.toml +++ b/common/request/Cargo.toml @@ -23,7 +23,7 @@ hyper-util = { version = "0.1", default-features = false, features = ["http1", " http-body-util = { version = "0.1", default-features = false } tokio = { version = "1", default-features = false } -hyper-rustls = { version = "0.26", default-features = false, features = ["http1", "ring", "rustls-native-certs", "native-tokio"], optional = true } +hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "ring", "rustls-native-certs", "native-tokio"], optional = true } zeroize = { version = "1", optional = true } base64ct = { version = "1", features = ["alloc"], optional = true } diff --git a/tests/coordinator/Cargo.toml b/tests/coordinator/Cargo.toml index a331d484..f5bc6426 100644 --- a/tests/coordinator/Cargo.toml +++ b/tests/coordinator/Cargo.toml @@ -20,7 +20,6 @@ workspace = true hex = "0.4" async-trait = "0.1" -async-recursion = "1" zeroize = { version = "1", default-features = false } rand_core = { version = "0.6", default-features = false } diff --git a/tests/coordinator/src/tests/mod.rs b/tests/coordinator/src/tests/mod.rs index 5f0acab6..b564a26b 100644 --- a/tests/coordinator/src/tests/mod.rs +++ b/tests/coordinator/src/tests/mod.rs @@ -135,7 +135,6 @@ pub(crate) async fn new_test(test_body: impl TestBody) { *OUTER_OPS.get_or_init(|| Mutex::new(None)).lock().await = None; // Spawns a coordinator, if one has yet to be spawned, or else runs the test. - #[async_recursion::async_recursion] async fn spawn_coordinator_or_run_test(inner_ops: DockerOperations) { // If the outer operations have yet to be set, these *are* the outer operations let outer_ops = OUTER_OPS.get().unwrap(); @@ -178,7 +177,10 @@ pub(crate) async fn new_test(test_body: impl TestBody) { test.provide_container(composition); drop(context_lock); - test.run_async(spawn_coordinator_or_run_test).await; + fn recurse(ops: DockerOperations) -> core::pin::Pin>> { + Box::pin(spawn_coordinator_or_run_test(ops)) + } + test.run_async(recurse).await; } else { let outer_ops = outer_ops.lock().await.take().unwrap(); diff --git a/tests/full-stack/Cargo.toml b/tests/full-stack/Cargo.toml index b45d7b53..58e6de28 100644 --- a/tests/full-stack/Cargo.toml +++ b/tests/full-stack/Cargo.toml @@ -20,7 +20,6 @@ workspace = true hex = "0.4" async-trait = "0.1" -async-recursion = "1" zeroize = { version = "1", default-features = false } rand_core = { version = "0.6", default-features = false } diff --git a/tests/full-stack/src/tests/mod.rs b/tests/full-stack/src/tests/mod.rs index 31c98952..1fae8c48 100644 --- a/tests/full-stack/src/tests/mod.rs +++ b/tests/full-stack/src/tests/mod.rs @@ -161,54 +161,57 @@ pub(crate) async fn new_test(test_body: impl TestBody) { *OUTER_OPS.get_or_init(|| Mutex::new(None)).lock().await = None; // Spawns a coordinator, if one has yet to be spawned, or else runs the test. - #[async_recursion::async_recursion] - async fn spawn_coordinator_or_run_test(inner_ops: DockerOperations) { - // If the outer operations have yet to be set, these *are* the outer operations - let outer_ops = OUTER_OPS.get().unwrap(); - if outer_ops.lock().await.is_none() { - *outer_ops.lock().await = Some(inner_ops); - } + pub(crate) fn spawn_coordinator_or_run_test( + inner_ops: DockerOperations, + ) -> core::pin::Pin>> { + Box::pin(async { + // If the outer operations have yet to be set, these *are* the outer operations + let outer_ops = OUTER_OPS.get().unwrap(); + if outer_ops.lock().await.is_none() { + *outer_ops.lock().await = Some(inner_ops); + } - let context_lock = CONTEXT.get().unwrap().lock().await; - let Context { pending_coordinator_compositions, handles, test_body } = - context_lock.as_ref().unwrap(); + let context_lock = CONTEXT.get().unwrap().lock().await; + let Context { pending_coordinator_compositions, handles, test_body } = + context_lock.as_ref().unwrap(); - // Check if there is a coordinator left - let maybe_coordinator = { - let mut remaining = pending_coordinator_compositions.lock().await; - let maybe_coordinator = if !remaining.is_empty() { - let handles = handles[handles.len() - remaining.len()].clone(); - let composition = remaining.remove(0); - Some((composition, handles)) + // Check if there is a coordinator left + let maybe_coordinator = { + let mut remaining = pending_coordinator_compositions.lock().await; + let maybe_coordinator = if !remaining.is_empty() { + let handles = handles[handles.len() - remaining.len()].clone(); + let composition = remaining.remove(0); + Some((composition, handles)) + } else { + None + }; + drop(remaining); + maybe_coordinator + }; + + if let Some((mut composition, handles)) = maybe_coordinator { + let network = { + let outer_ops = outer_ops.lock().await; + let outer_ops = outer_ops.as_ref().unwrap(); + // Spawn it by building another DockerTest which recursively calls this function + // TODO: Spawn this outside of DockerTest so we can remove the recursion + let serai_container = outer_ops.handle(&handles.serai); + composition.modify_env("SERAI_HOSTNAME", serai_container.ip()); + let message_queue_container = outer_ops.handle(&handles.message_queue); + composition.modify_env("MESSAGE_QUEUE_RPC", message_queue_container.ip()); + + format!("container:{}", serai_container.name()) + }; + let mut test = DockerTest::new().with_network(dockertest::Network::External(network)); + test.provide_container(composition); + + drop(context_lock); + test.run_async(spawn_coordinator_or_run_test).await; } else { - None - }; - drop(remaining); - maybe_coordinator - }; - - if let Some((mut composition, handles)) = maybe_coordinator { - let network = { - let outer_ops = outer_ops.lock().await; - let outer_ops = outer_ops.as_ref().unwrap(); - // Spawn it by building another DockerTest which recursively calls this function - // TODO: Spawn this outside of DockerTest so we can remove the recursion - let serai_container = outer_ops.handle(&handles.serai); - composition.modify_env("SERAI_HOSTNAME", serai_container.ip()); - let message_queue_container = outer_ops.handle(&handles.message_queue); - composition.modify_env("MESSAGE_QUEUE_RPC", message_queue_container.ip()); - - format!("container:{}", serai_container.name()) - }; - let mut test = DockerTest::new().with_network(dockertest::Network::External(network)); - test.provide_container(composition); - - drop(context_lock); - test.run_async(spawn_coordinator_or_run_test).await; - } else { - let outer_ops = outer_ops.lock().await.take().unwrap(); - test_body.body(outer_ops, handles.clone()).await; - } + let outer_ops = outer_ops.lock().await.take().unwrap(); + test_body.body(outer_ops, handles.clone()).await; + } + }) } test.run_async(spawn_coordinator_or_run_test).await;