diff --git a/Cargo.lock b/Cargo.lock index a7369f0a7..78cd1fdd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -467,6 +467,7 @@ name = "bioauth-consensus" version = "0.1.0" dependencies = [ "async-trait", + "futures 0.3.16", "mockall", "node-primitives", "pallet-bioauth", @@ -479,6 +480,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-aura", + "sp-keystore", "sp-runtime", "thiserror", "tokio 1.9.0", diff --git a/crates/bioauth-consensus/Cargo.toml b/crates/bioauth-consensus/Cargo.toml index 46cf349c2..0bc19a05c 100644 --- a/crates/bioauth-consensus/Cargo.toml +++ b/crates/bioauth-consensus/Cargo.toml @@ -10,6 +10,7 @@ pallet-bioauth = { version = "0.1", path = "../pallet-bioauth", optional = true async-trait = "0.1.42" codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] } +futures = "0.3" sc-client-api = { git = "https://github.com/humanode-network/substrate", branch = "master" } sc-consensus = { git = "https://github.com/humanode-network/substrate", branch = "master" } sp-api = { git = "https://github.com/humanode-network/substrate", branch = "master" } @@ -17,8 +18,10 @@ sp-application-crypto = { git = "https://github.com/humanode-network/substrate", sp-blockchain = { git = "https://github.com/humanode-network/substrate", branch = "master" } sp-consensus = { git = "https://github.com/humanode-network/substrate", branch = "master" } sp-consensus-aura = { git = "https://github.com/humanode-network/substrate", branch = "master", optional = true } +sp-keystore = { git = "https://github.com/humanode-network/substrate", branch = "master" } sp-runtime = { git = "https://github.com/humanode-network/substrate", branch = "master" } thiserror = "1" +tokio = { version = "1", features = ["rt-multi-thread"] } [dev-dependencies] mockall = "0.10" diff --git a/crates/bioauth-consensus/src/lib.rs b/crates/bioauth-consensus/src/lib.rs index efbadc388..f509eb228 100644 --- a/crates/bioauth-consensus/src/lib.rs +++ b/crates/bioauth-consensus/src/lib.rs @@ -6,11 +6,14 @@ clippy::clone_on_ref_ptr )] +use futures::future; +use futures::future::FutureExt; use sc_client_api::{backend::Backend, Finalizer}; use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; -use sp_api::{ProvideRuntimeApi, TransactionFor}; +use sp_api::{HeaderT, ProvideRuntimeApi, TransactionFor}; use sp_blockchain::{well_known_cache_keys, HeaderBackend}; -use sp_consensus::Error as ConsensusError; +use sp_consensus::{Environment, Error as ConsensusError}; +use sp_keystore::SyncCryptoStorePtr; use sp_runtime::traits::Block as BlockT; use std::{collections::HashMap, marker::PhantomData, sync::Arc}; use thiserror::Error; @@ -151,3 +154,104 @@ where self.inner.import_block(block, cache).await } } + +/// A Proposer handler for Bioauth. +pub struct BioauthProposer { + /// A basic authorship proposer. + base_proposer: BAP, + /// Keystore to extract validator public key. + keystore: SyncCryptoStorePtr, + /// The bioauth auhtrization verifier. + authorization_verifier: AV, + /// A phantom data for Block. + _phantom_block: PhantomData, +} + +/// BioauthProposer Error Type. +#[derive(Error, Debug, Eq, PartialEq)] +pub enum BioauthProposerError +where + AV: std::error::Error, +{ + /// The block author isn't Bioauth authorized. + #[error("the block author isn't bioauth-authorized")] + NotBioauthAuthorized, + /// Authorization verification error. + #[error("unable verify the authorization: {0}")] + AuthorizationVerifier(AV), +} + +impl BioauthProposer { + /// Simple constructor. + pub fn new( + base_proposer: BAP, + keystore: SyncCryptoStorePtr, + authorization_verifier: AV, + ) -> Self { + BioauthProposer { + base_proposer, + keystore, + authorization_verifier, + _phantom_block: PhantomData, + } + } +} + +impl Environment for BioauthProposer +where + AV: AuthorizationVerifier< + Block = Block, + PublicKeyType = sp_consensus_aura::sr25519::AuthorityId, + > + Send, + ::Error: std::error::Error + Send + Sync + 'static, + BAP: Environment + Send + Sync + 'static, + BAP::Error: Send, + BAP::Proposer: Send, +{ + type Proposer = BAP::Proposer; + + type CreateProposer = future::BoxFuture<'static, Result>; + + type Error = BAP::Error; + + fn init(&mut self, parent_header: &Block::Header) -> Self::CreateProposer { + let mkerr = |err: BioauthProposerError| -> Self::CreateProposer { + Box::pin(future::err(Self::Error::from(sp_consensus::Error::Other( + Box::new(err), + )))) + }; + + let keystore_ref = self.keystore.as_ref(); + + let aura_public_keys = tokio::task::block_in_place(move || { + sp_keystore::SyncCryptoStore::sr25519_public_keys( + keystore_ref, + sp_application_crypto::key_types::AURA, + ) + }); + + assert!( + aura_public_keys.len() == 1, + "The list of aura public keys should contain only 1 key, please report this" + ); + + let aura_public_key = aura_public_keys[0]; + + let parent_hash = parent_header.hash(); + let at = sp_api::BlockId::hash(parent_hash); + + let is_authorized = match self + .authorization_verifier + .is_authorized(&at, &aura_public_key.into()) + { + Ok(v) => v, + Err(err) => return mkerr(BioauthProposerError::AuthorizationVerifier(err)), + }; + + if !is_authorized { + return mkerr(BioauthProposerError::NotBioauthAuthorized); + } + + self.base_proposer.init(parent_header).boxed() + } +} diff --git a/crates/humanode-peer/src/service.rs b/crates/humanode-peer/src/service.rs index 2daa2b47b..a560822a3 100644 --- a/crates/humanode-peer/src/service.rs +++ b/crates/humanode-peer/src/service.rs @@ -170,6 +170,16 @@ pub async fn new_full(config: Configuration) -> Result, + _, + > = bioauth_consensus::BioauthProposer::new( + proposer_factory, + keystore_container.sync_keystore(), + bioauth_consensus::bioauth::AuthorizationVerifier::new(Arc::clone(&client)), + ); + let (network, system_rpc_tx, network_starter) = sc_service::build_network(sc_service::BuildNetworkParams { config: &config,