From 376a66b0003537a23fcefa61d3f6278b72eb6387 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 8 Jan 2025 16:41:11 -0500 Subject: [PATCH] Remove async-trait from tendermint-machine, tributary-chain --- Cargo.lock | 2 - coordinator/tributary/Cargo.toml | 1 - coordinator/tributary/src/lib.rs | 12 +- coordinator/tributary/src/tendermint/mod.rs | 262 +++++++++--------- coordinator/tributary/src/tests/p2p.rs | 7 +- coordinator/tributary/src/tests/tendermint.rs | 8 +- coordinator/tributary/tendermint/Cargo.toml | 1 - coordinator/tributary/tendermint/src/ext.rs | 33 +-- coordinator/tributary/tendermint/tests/ext.rs | 21 +- 9 files changed, 178 insertions(+), 169 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcb9b442..3902b794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10498,7 +10498,6 @@ dependencies = [ name = "tendermint-machine" version = "0.2.0" dependencies = [ - "async-trait", "futures-channel", "futures-util", "hex", @@ -10941,7 +10940,6 @@ dependencies = [ name = "tributary-chain" version = "0.1.0" dependencies = [ - "async-trait", "blake2", "ciphersuite", "flexible-transcript", diff --git a/coordinator/tributary/Cargo.toml b/coordinator/tributary/Cargo.toml index 28beb2ab..d88c3b33 100644 --- a/coordinator/tributary/Cargo.toml +++ b/coordinator/tributary/Cargo.toml @@ -16,7 +16,6 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] -async-trait = { version = "0.1", default-features = false } thiserror = { version = "2", default-features = false, features = ["std"] } subtle = { version = "^2", default-features = false, features = ["std"] } diff --git a/coordinator/tributary/src/lib.rs b/coordinator/tributary/src/lib.rs index 476dbf93..3e946381 100644 --- a/coordinator/tributary/src/lib.rs +++ b/coordinator/tributary/src/lib.rs @@ -1,8 +1,6 @@ -use core::{marker::PhantomData, fmt::Debug}; +use core::{marker::PhantomData, fmt::Debug, future::Future}; use std::{sync::Arc, io}; -use async_trait::async_trait; - use zeroize::Zeroizing; use ciphersuite::{Ciphersuite, Ristretto}; @@ -131,20 +129,18 @@ pub trait ReadWrite: Sized { } } -#[async_trait] pub trait P2p: 'static + Send + Sync + Clone + Debug { /// Broadcast a message to all other members of the Tributary with the specified genesis. /// /// The Tributary will re-broadcast consensus messages on a fixed interval to ensure they aren't /// prematurely dropped from the P2P layer. THe P2P layer SHOULD perform content-based /// deduplication to ensure a sane amount of load. - async fn broadcast(&self, genesis: [u8; 32], msg: Vec); + fn broadcast(&self, genesis: [u8; 32], msg: Vec) -> impl Send + Future; } -#[async_trait] impl P2p for Arc

{ - async fn broadcast(&self, genesis: [u8; 32], msg: Vec) { - (*self).broadcast(genesis, msg).await + fn broadcast(&self, genesis: [u8; 32], msg: Vec) -> impl Send + Future { + P::broadcast(self, genesis, msg) } } diff --git a/coordinator/tributary/src/tendermint/mod.rs b/coordinator/tributary/src/tendermint/mod.rs index 0ce6232c..0fd618ca 100644 --- a/coordinator/tributary/src/tendermint/mod.rs +++ b/coordinator/tributary/src/tendermint/mod.rs @@ -1,8 +1,6 @@ -use core::ops::Deref; +use core::{ops::Deref, future::Future}; use std::{sync::Arc, collections::HashMap}; -use async_trait::async_trait; - use subtle::ConstantTimeEq; use zeroize::{Zeroize, Zeroizing}; @@ -74,50 +72,52 @@ impl Signer { } } -#[async_trait] impl SignerTrait for Signer { type ValidatorId = [u8; 32]; type Signature = [u8; 64]; /// Returns the validator's current ID. Returns None if they aren't a current validator. - async fn validator_id(&self) -> Option { - Some((Ristretto::generator() * self.key.deref()).to_bytes()) + fn validator_id(&self) -> impl Send + Future> { + async move { Some((Ristretto::generator() * self.key.deref()).to_bytes()) } } /// Sign a signature with the current validator's private key. - async fn sign(&self, msg: &[u8]) -> Self::Signature { - let mut nonce = Zeroizing::new(RecommendedTranscript::new(b"Tributary Chain Tendermint Nonce")); - nonce.append_message(b"genesis", self.genesis); - nonce.append_message(b"key", Zeroizing::new(self.key.deref().to_repr()).as_ref()); - nonce.append_message(b"message", msg); - let mut nonce = nonce.challenge(b"nonce"); + fn sign(&self, msg: &[u8]) -> impl Send + Future { + async move { + let mut nonce = + Zeroizing::new(RecommendedTranscript::new(b"Tributary Chain Tendermint Nonce")); + nonce.append_message(b"genesis", self.genesis); + nonce.append_message(b"key", Zeroizing::new(self.key.deref().to_repr()).as_ref()); + nonce.append_message(b"message", msg); + let mut nonce = nonce.challenge(b"nonce"); - let mut nonce_arr = [0; 64]; - nonce_arr.copy_from_slice(nonce.as_ref()); + let mut nonce_arr = [0; 64]; + nonce_arr.copy_from_slice(nonce.as_ref()); - let nonce_ref: &mut [u8] = nonce.as_mut(); - nonce_ref.zeroize(); - let nonce_ref: &[u8] = nonce.as_ref(); - assert_eq!(nonce_ref, [0; 64].as_ref()); + let nonce_ref: &mut [u8] = nonce.as_mut(); + nonce_ref.zeroize(); + let nonce_ref: &[u8] = nonce.as_ref(); + assert_eq!(nonce_ref, [0; 64].as_ref()); - let nonce = - Zeroizing::new(::F::from_bytes_mod_order_wide(&nonce_arr)); - nonce_arr.zeroize(); + let nonce = + Zeroizing::new(::F::from_bytes_mod_order_wide(&nonce_arr)); + nonce_arr.zeroize(); - assert!(!bool::from(nonce.ct_eq(&::F::ZERO))); + assert!(!bool::from(nonce.ct_eq(&::F::ZERO))); - let challenge = challenge( - self.genesis, - (Ristretto::generator() * self.key.deref()).to_bytes(), - (Ristretto::generator() * nonce.deref()).to_bytes().as_ref(), - msg, - ); + let challenge = challenge( + self.genesis, + (Ristretto::generator() * self.key.deref()).to_bytes(), + (Ristretto::generator() * nonce.deref()).to_bytes().as_ref(), + msg, + ); - let sig = SchnorrSignature::::sign(&self.key, nonce, challenge).serialize(); + let sig = SchnorrSignature::::sign(&self.key, nonce, challenge).serialize(); - let mut res = [0; 64]; - res.copy_from_slice(&sig); - res + let mut res = [0; 64]; + res.copy_from_slice(&sig); + res + } } } @@ -274,7 +274,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); -#[async_trait] impl Network for TendermintNetwork { type Db = D; @@ -300,111 +299,126 @@ impl Network for TendermintNetwork self.validators.clone() } - async fn broadcast(&mut self, msg: SignedMessageFor) { - let mut to_broadcast = vec![TENDERMINT_MESSAGE]; - to_broadcast.extend(msg.encode()); - self.p2p.broadcast(self.genesis, to_broadcast).await - } - - async fn slash(&mut self, validator: Self::ValidatorId, slash_event: SlashEvent) { - log::error!( - "validator {} triggered a slash event on tributary {} (with evidence: {})", - hex::encode(validator), - hex::encode(self.genesis), - matches!(slash_event, SlashEvent::WithEvidence(_)), - ); - - let signer = self.signer(); - let Some(tx) = (match slash_event { - SlashEvent::WithEvidence(evidence) => { - // create an unsigned evidence tx - Some(TendermintTx::SlashEvidence(evidence)) - } - SlashEvent::Id(_reason, _block, _round) => { - // TODO: Increase locally observed slash points - None - } - }) else { - return; - }; - - // add tx to blockchain and broadcast to peers - let mut to_broadcast = vec![TRANSACTION_MESSAGE]; - tx.write(&mut to_broadcast).unwrap(); - if self.blockchain.write().await.add_transaction::( - true, - Transaction::Tendermint(tx), - &self.signature_scheme(), - ) == Ok(true) - { - self.p2p.broadcast(signer.genesis, to_broadcast).await; + fn broadcast(&mut self, msg: SignedMessageFor) -> impl Send + Future { + async move { + let mut to_broadcast = vec![TENDERMINT_MESSAGE]; + to_broadcast.extend(msg.encode()); + self.p2p.broadcast(self.genesis, to_broadcast).await } } - async fn validate(&self, block: &Self::Block) -> Result<(), TendermintBlockError> { - let block = - Block::read::<&[u8]>(&mut block.0.as_ref()).map_err(|_| TendermintBlockError::Fatal)?; - self - .blockchain - .read() - .await - .verify_block::(&block, &self.signature_scheme(), false) - .map_err(|e| match e { - BlockError::NonLocalProvided(_) => TendermintBlockError::Temporal, - _ => { - log::warn!("Tributary Tendermint validate returning BlockError::Fatal due to {e:?}"); - TendermintBlockError::Fatal + fn slash( + &mut self, + validator: Self::ValidatorId, + slash_event: SlashEvent, + ) -> impl Send + Future { + async move { + log::error!( + "validator {} triggered a slash event on tributary {} (with evidence: {})", + hex::encode(validator), + hex::encode(self.genesis), + matches!(slash_event, SlashEvent::WithEvidence(_)), + ); + + let signer = self.signer(); + let Some(tx) = (match slash_event { + SlashEvent::WithEvidence(evidence) => { + // create an unsigned evidence tx + Some(TendermintTx::SlashEvidence(evidence)) } - }) + SlashEvent::Id(_reason, _block, _round) => { + // TODO: Increase locally observed slash points + None + } + }) else { + return; + }; + + // add tx to blockchain and broadcast to peers + let mut to_broadcast = vec![TRANSACTION_MESSAGE]; + tx.write(&mut to_broadcast).unwrap(); + if self.blockchain.write().await.add_transaction::( + true, + Transaction::Tendermint(tx), + &self.signature_scheme(), + ) == Ok(true) + { + self.p2p.broadcast(signer.genesis, to_broadcast).await; + } + } } - async fn add_block( + fn validate( + &self, + block: &Self::Block, + ) -> impl Send + Future> { + async move { + let block = + Block::read::<&[u8]>(&mut block.0.as_ref()).map_err(|_| TendermintBlockError::Fatal)?; + self + .blockchain + .read() + .await + .verify_block::(&block, &self.signature_scheme(), false) + .map_err(|e| match e { + BlockError::NonLocalProvided(_) => TendermintBlockError::Temporal, + _ => { + log::warn!("Tributary Tendermint validate returning BlockError::Fatal due to {e:?}"); + TendermintBlockError::Fatal + } + }) + } + } + + fn add_block( &mut self, serialized_block: Self::Block, commit: Commit, - ) -> Option { - let invalid_block = || { - // There's a fatal flaw in the code, it's behind a hard fork, or the validators turned - // malicious - // All justify a halt to then achieve social consensus from - // TODO: Under multiple validator sets, a small validator set turning malicious knocks - // off the entire network. That's an unacceptable DoS. - panic!("validators added invalid block to tributary {}", hex::encode(self.genesis)); - }; + ) -> impl Send + Future> { + async move { + let invalid_block = || { + // There's a fatal flaw in the code, it's behind a hard fork, or the validators turned + // malicious + // All justify a halt to then achieve social consensus from + // TODO: Under multiple validator sets, a small validator set turning malicious knocks + // off the entire network. That's an unacceptable DoS. + panic!("validators added invalid block to tributary {}", hex::encode(self.genesis)); + }; - // Tendermint should only produce valid commits - assert!(self.verify_commit(serialized_block.id(), &commit)); + // Tendermint should only produce valid commits + assert!(self.verify_commit(serialized_block.id(), &commit)); - let Ok(block) = Block::read::<&[u8]>(&mut serialized_block.0.as_ref()) else { - return invalid_block(); - }; + let Ok(block) = Block::read::<&[u8]>(&mut serialized_block.0.as_ref()) else { + return invalid_block(); + }; - let encoded_commit = commit.encode(); - loop { - let block_res = self.blockchain.write().await.add_block::( - &block, - encoded_commit.clone(), - &self.signature_scheme(), - ); - match block_res { - Ok(()) => { - // If we successfully added this block, break - break; + let encoded_commit = commit.encode(); + loop { + let block_res = self.blockchain.write().await.add_block::( + &block, + encoded_commit.clone(), + &self.signature_scheme(), + ); + match block_res { + Ok(()) => { + // If we successfully added this block, break + break; + } + Err(BlockError::NonLocalProvided(hash)) => { + log::error!( + "missing provided transaction {} which other validators on tributary {} had", + hex::encode(hash), + hex::encode(self.genesis) + ); + tokio::time::sleep(core::time::Duration::from_secs(5)).await; + } + _ => return invalid_block(), } - Err(BlockError::NonLocalProvided(hash)) => { - log::error!( - "missing provided transaction {} which other validators on tributary {} had", - hex::encode(hash), - hex::encode(self.genesis) - ); - tokio::time::sleep(core::time::Duration::from_secs(5)).await; - } - _ => return invalid_block(), } - } - Some(TendermintBlock( - self.blockchain.write().await.build_block::(&self.signature_scheme()).serialize(), - )) + Some(TendermintBlock( + self.blockchain.write().await.build_block::(&self.signature_scheme()).serialize(), + )) + } } } diff --git a/coordinator/tributary/src/tests/p2p.rs b/coordinator/tributary/src/tests/p2p.rs index d3e3b74c..32bca7d1 100644 --- a/coordinator/tributary/src/tests/p2p.rs +++ b/coordinator/tributary/src/tests/p2p.rs @@ -1,11 +1,12 @@ +use core::future::Future; + pub use crate::P2p; #[derive(Clone, Debug)] pub struct DummyP2p; -#[async_trait::async_trait] impl P2p for DummyP2p { - async fn broadcast(&self, _: [u8; 32], _: Vec) { - unimplemented!() + fn broadcast(&self, _: [u8; 32], _: Vec) -> impl Send + Future { + async move { unimplemented!() } } } diff --git a/coordinator/tributary/src/tests/tendermint.rs b/coordinator/tributary/src/tests/tendermint.rs index 77dfc9e5..fc8f190e 100644 --- a/coordinator/tributary/src/tests/tendermint.rs +++ b/coordinator/tributary/src/tests/tendermint.rs @@ -1,4 +1,7 @@ +use core::future::Future; + use tendermint::ext::Network; + use crate::{ P2p, TendermintTx, tendermint::{TARGET_BLOCK_TIME, TendermintNetwork}, @@ -11,10 +14,9 @@ fn assert_target_block_time() { #[derive(Clone, Debug)] pub struct DummyP2p; - #[async_trait::async_trait] impl P2p for DummyP2p { - async fn broadcast(&self, _: [u8; 32], _: Vec) { - unimplemented!() + fn broadcast(&self, _: [u8; 32], _: Vec) -> impl Send + Future { + async move { unimplemented!() } } } diff --git a/coordinator/tributary/tendermint/Cargo.toml b/coordinator/tributary/tendermint/Cargo.toml index 807938c8..7f7e2186 100644 --- a/coordinator/tributary/tendermint/Cargo.toml +++ b/coordinator/tributary/tendermint/Cargo.toml @@ -16,7 +16,6 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] -async-trait = { version = "0.1", default-features = false } thiserror = { version = "2", default-features = false, features = ["std"] } hex = { version = "0.4", default-features = false, features = ["std"] } diff --git a/coordinator/tributary/tendermint/src/ext.rs b/coordinator/tributary/tendermint/src/ext.rs index 3869d9d9..67b8b07d 100644 --- a/coordinator/tributary/tendermint/src/ext.rs +++ b/coordinator/tributary/tendermint/src/ext.rs @@ -1,7 +1,6 @@ -use core::{hash::Hash, fmt::Debug}; +use core::{hash::Hash, fmt::Debug, future::Future}; use std::{sync::Arc, collections::HashSet}; -use async_trait::async_trait; use thiserror::Error; use parity_scale_codec::{Encode, Decode}; @@ -34,7 +33,6 @@ pub struct BlockNumber(pub u64); pub struct RoundNumber(pub u32); /// A signer for a validator. -#[async_trait] pub trait Signer: Send + Sync { // Type used to identify validators. type ValidatorId: ValidatorId; @@ -42,22 +40,21 @@ pub trait Signer: Send + Sync { type Signature: Signature; /// Returns the validator's current ID. Returns None if they aren't a current validator. - async fn validator_id(&self) -> Option; + fn validator_id(&self) -> impl Send + Future>; /// Sign a signature with the current validator's private key. - async fn sign(&self, msg: &[u8]) -> Self::Signature; + fn sign(&self, msg: &[u8]) -> impl Send + Future; } -#[async_trait] impl Signer for Arc { type ValidatorId = S::ValidatorId; type Signature = S::Signature; - async fn validator_id(&self) -> Option { - self.as_ref().validator_id().await + fn validator_id(&self) -> impl Send + Future> { + self.as_ref().validator_id() } - async fn sign(&self, msg: &[u8]) -> Self::Signature { - self.as_ref().sign(msg).await + fn sign(&self, msg: &[u8]) -> impl Send + Future { + self.as_ref().sign(msg) } } @@ -210,7 +207,6 @@ pub trait Block: Send + Sync + Clone + PartialEq + Eq + Debug + Encode + Decode } /// Trait representing the distributed system Tendermint is providing consensus over. -#[async_trait] pub trait Network: Sized + Send + Sync { /// The database used to back this. type Db: serai_db::Db; @@ -229,6 +225,7 @@ pub trait Network: Sized + Send + Sync { /// This should include both the time to download the block and the actual processing time. /// /// BLOCK_PROCESSING_TIME + (3 * LATENCY_TIME) must be divisible by 1000. + // TODO: Redefine as Duration const BLOCK_PROCESSING_TIME: u32; /// Network latency time in milliseconds. /// @@ -280,15 +277,19 @@ pub trait Network: Sized + Send + Sync { /// Switching to unauthenticated channels in a system already providing authenticated channels is /// not recommended as this is a minor, temporal inefficiency, while downgrading channels may /// have wider implications. - async fn broadcast(&mut self, msg: SignedMessageFor); + fn broadcast(&mut self, msg: SignedMessageFor) -> impl Send + Future; /// Trigger a slash for the validator in question who was definitively malicious. /// /// The exact process of triggering a slash is undefined and left to the network as a whole. - async fn slash(&mut self, validator: Self::ValidatorId, slash_event: SlashEvent); + fn slash( + &mut self, + validator: Self::ValidatorId, + slash_event: SlashEvent, + ) -> impl Send + Future; /// Validate a block. - async fn validate(&self, block: &Self::Block) -> Result<(), BlockError>; + fn validate(&self, block: &Self::Block) -> impl Send + Future>; /// Add a block, returning the proposal for the next one. /// @@ -298,9 +299,9 @@ pub trait Network: Sized + Send + Sync { /// This deviates from the paper which will have a local node refuse to decide on a block it /// considers invalid. This library acknowledges the network did decide on it, leaving handling /// of it to the network, and outside of this scope. - async fn add_block( + fn add_block( &mut self, block: Self::Block, commit: Commit, - ) -> Option; + ) -> impl Send + Future>; } diff --git a/coordinator/tributary/tendermint/tests/ext.rs b/coordinator/tributary/tendermint/tests/ext.rs index bec95ddc..58a5d468 100644 --- a/coordinator/tributary/tendermint/tests/ext.rs +++ b/coordinator/tributary/tendermint/tests/ext.rs @@ -1,10 +1,9 @@ +use core::future::Future; use std::{ sync::Arc, time::{UNIX_EPOCH, SystemTime, Duration}, }; -use async_trait::async_trait; - use parity_scale_codec::{Encode, Decode}; use futures_util::sink::SinkExt; @@ -21,20 +20,21 @@ type TestValidatorId = u16; type TestBlockId = [u8; 4]; struct TestSigner(u16); -#[async_trait] impl Signer for TestSigner { type ValidatorId = TestValidatorId; type Signature = [u8; 32]; - async fn validator_id(&self) -> Option { - Some(self.0) + fn validator_id(&self) -> impl Send + Future> { + async move { Some(self.0) } } - async fn sign(&self, msg: &[u8]) -> [u8; 32] { - let mut sig = [0; 32]; - sig[.. 2].copy_from_slice(&self.0.to_le_bytes()); - sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(&msg[.. 30.min(msg.len())]); - sig + fn sign(&self, msg: &[u8]) -> impl Send + Future { + async move { + let mut sig = [0; 32]; + sig[.. 2].copy_from_slice(&self.0.to_le_bytes()); + sig[2 .. (2 + 30.min(msg.len()))].copy_from_slice(&msg[.. 30.min(msg.len())]); + sig + } } } @@ -111,7 +111,6 @@ struct TestNetwork( Arc, SyncedBlockSender, SyncedBlockResultReceiver)>>>, ); -#[async_trait] impl Network for TestNetwork { type Db = MemDb;