diff --git a/Cargo.lock b/Cargo.lock index d596e7b1..df6df0fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7426,6 +7426,7 @@ checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7" name = "serai-consensus" version = "0.1.0" dependencies = [ + "async-trait", "sc-basic-authorship", "sc-client-api", "sc-consensus", @@ -7436,13 +7437,17 @@ dependencies = [ "sc-transaction-pool", "serai-runtime", "sp-api", + "sp-application-crypto", + "sp-blockchain", "sp-consensus", "sp-consensus-pow", "sp-core", + "sp-inherents", "sp-runtime", "sp-timestamp", "sp-trie", "substrate-prometheus-endpoint", + "tendermint-machine", "tokio", ] diff --git a/substrate/consensus/Cargo.toml b/substrate/consensus/Cargo.toml index 892b648e..4f58cde5 100644 --- a/substrate/consensus/Cargo.toml +++ b/substrate/consensus/Cargo.toml @@ -13,11 +13,24 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +async-trait = "0.1" + sp-core = { git = "https://github.com/serai-dex/substrate" } +sp-application-crypto = { git = "https://github.com/serai-dex/substrate" } +sp-inherents = { git = "https://github.com/serai-dex/substrate" } +sp-runtime = { git = "https://github.com/serai-dex/substrate" } +sp-blockchain = { git = "https://github.com/serai-dex/substrate" } +sp-api = { git = "https://github.com/serai-dex/substrate" } + +sp-consensus = { git = "https://github.com/serai-dex/substrate" } +sc-consensus = { git = "https://github.com/serai-dex/substrate" } + +tendermint-machine = { path = "../tendermint" } + +# -- + sp-trie = { git = "https://github.com/serai-dex/substrate" } sp-timestamp = { git = "https://github.com/serai-dex/substrate" } -sc-consensus = { git = "https://github.com/serai-dex/substrate" } -sp-consensus = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" } sc-consensus-pow = { git = "https://github.com/serai-dex/substrate" } @@ -26,12 +39,10 @@ sp-consensus-pow = { git = "https://github.com/serai-dex/substrate" } sc-network = { git = "https://github.com/serai-dex/substrate" } sc-service = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } sc-executor = { git = "https://github.com/serai-dex/substrate", features = ["wasmtime"] } -sp-runtime = { git = "https://github.com/serai-dex/substrate" } substrate-prometheus-endpoint = { git = "https://github.com/serai-dex/substrate" } sc-client-api = { git = "https://github.com/serai-dex/substrate" } -sp-api = { git = "https://github.com/serai-dex/substrate" } serai-runtime = { path = "../runtime" } diff --git a/substrate/consensus/src/import.rs b/substrate/consensus/src/import.rs new file mode 100644 index 00000000..0a02976c --- /dev/null +++ b/substrate/consensus/src/import.rs @@ -0,0 +1,131 @@ +use std::{marker::PhantomData, sync::Arc, collections::HashMap}; + +use sp_core::Decode; +use sp_inherents::CreateInherentDataProviders; +use sp_runtime::traits::{Header, Block}; +use sp_blockchain::HeaderBackend; +use sp_api::{ProvideRuntimeApi, TransactionFor}; + +use sp_consensus::{Error, CacheKeyId}; +#[rustfmt::skip] +use sc_consensus::{ + ForkChoiceStrategy, + BlockCheckParams, + BlockImportParams, + ImportResult, + BlockImport, +}; + +use tendermint_machine::ext::*; + +use crate::signature_scheme::TendermintSigner; + +struct TendermintBlockImport< + B: Block, + C: Send + Sync + HeaderBackend + ProvideRuntimeApi + 'static, + I: Send + Sync + BlockImport>, + CIDP: CreateInherentDataProviders, +> { + _block: PhantomData, + client: Arc, + inner: I, + providers: Arc, +} + +impl< + B: Block, + C: Send + Sync + HeaderBackend + ProvideRuntimeApi, + I: Send + Sync + BlockImport>, + CIDP: CreateInherentDataProviders, + > TendermintBlockImport +{ + async fn check_inherents( + &self, + block: B, + providers: CIDP::InherentDataProviders, + ) -> Result<(), Error> { + todo!() + } +} + +// The Tendermint machine will call add_block for any block which is committed to, regardless of +// validity. To determine validity, it expects a validate function, which Substrate doesn't +// directly offer, and an add function. In order to comply with Serai's modified view of inherent +// transactions, validate MUST check inherents, yet add_block must not. +// +// In order to acquire a validate function, any block proposed by a legitimate proposer is +// imported. This performs full validation and makes the block available as a tip. While this would +// be incredibly unsafe thanks to the unchecked inherents, it's defined as a tip with less work, +// despite being a child of some parent. This means it won't be moved to nor operated on by the +// node. +// +// When Tendermint completes, the block is finalized, setting it as the tip regardless of work. + +const CONSENSUS_ID: [u8; 4] = *b"tend"; + +#[async_trait::async_trait] +impl< + B: Block, + C: Send + Sync + HeaderBackend + ProvideRuntimeApi, + I: Send + Sync + BlockImport>, + CIDP: CreateInherentDataProviders, + > BlockImport for TendermintBlockImport +where + I::Error: Into, +{ + type Error = Error; + type Transaction = TransactionFor; + + async fn check_block(&mut self, block: BlockCheckParams) -> Result { + self.inner.check_block(block).await.map_err(Into::into) + } + + async fn import_block( + &mut self, + mut block: BlockImportParams, + new_cache: HashMap>, + ) -> Result { + let parent_hash = *block.header.parent_hash(); + if self.client.info().best_hash != parent_hash { + Err(Error::Other("non-sequential import".into()))?; + } + + if let Some(body) = block.body.clone() { + if let Some(justifications) = block.justifications { + let mut iter = justifications.iter(); + let next = iter.next(); + if next.is_none() || iter.next().is_some() { + Err(Error::InvalidJustification)?; + } + let justification = next.unwrap(); + + let commit: Commit = + Commit::decode(&mut justification.1.as_ref()).map_err(|_| Error::InvalidJustification)?; + if justification.0 != CONSENSUS_ID { + Err(Error::InvalidJustification)?; + } + + // verify_commit + todo!() + } else { + self + .check_inherents( + B::new(block.header.clone(), body), + self.providers.create_inherent_data_providers(parent_hash, ()).await?, + ) + .await?; + } + } + + if !block.post_digests.is_empty() { + Err(Error::Other("post-digests included".into()))?; + } + if !block.auxiliary.is_empty() { + Err(Error::Other("auxiliary included".into()))?; + } + + block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); + self.inner.import_block(block, new_cache).await.map_err(Into::into) + } +} + diff --git a/substrate/consensus/src/lib.rs b/substrate/consensus/src/lib.rs index c6c902d9..e81c793f 100644 --- a/substrate/consensus/src/lib.rs +++ b/substrate/consensus/src/lib.rs @@ -9,7 +9,10 @@ use sc_service::TaskManager; use serai_runtime::{self, opaque::Block, RuntimeApi}; mod algorithm; -mod tendermint; + +mod signature_scheme; +mod import; +//mod tendermint; pub struct ExecutorDispatch; impl sc_executor::NativeExecutionDispatch for ExecutorDispatch {