Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/bioauth-consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ sp-blockchain = { git = "https://github.com/humanode-network/substrate", branch
sp-consensus = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sp-consensus-aura = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sp-consensus-slots = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sp-keystore = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sp-runtime = { git = "https://github.com/humanode-network/substrate", branch = "master" }
thiserror = "1"

[dev-dependencies]
mockall = "0.10"
node-primitives = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sc-keystore = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sc-service = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sp-core = { git = "https://github.com/humanode-network/substrate", branch = "master" }
tokio = { version = "1", features = ["full"] }
Expand Down
30 changes: 30 additions & 0 deletions crates/bioauth-consensus/src/author_validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Block author validation logic.

/// BlockAuthorExtractor provides a functionality to extract
/// block author public key for a particular block.
pub trait BlockAuthorExtractor {
/// BlockAuthorExtractor error.
type Error;
/// BlockHeader type.
type BlockHeader;
/// Block author Public key type.
type PublicKeyType;

/// Extract block author public key for a provided block header.
fn extract_block_author(
&self,
block_header: &Self::BlockHeader,
) -> Result<Self::PublicKeyType, Self::Error>;
}

/// BlockAuthorExtractor provides a functionality to verify
/// whether aparticular author is authorized to be a validator.
pub trait AuthorizationVerifier {
/// AuthorizationVerifier
type Error;
/// Public key type.
type PublicKeyType: ?Sized;

/// Verify that a provided author is authorized by it's public key.
fn is_authorized(&self, author_public_key: &Self::PublicKeyType) -> Result<bool, Self::Error>;
}
154 changes: 115 additions & 39 deletions crates/bioauth-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,32 @@
clippy::clone_on_ref_ptr
)]

use author_validation::{AuthorizationVerifier, BlockAuthorExtractor};
use pallet_bioauth::BioauthApi;
use sc_client_api::{backend::Backend, Finalizer};
use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult};
use sp_api::{Decode, ProvideRuntimeApi, TransactionFor};
use sp_application_crypto::Public;
use sp_blockchain::{well_known_cache_keys, HeaderBackend};
use sp_consensus::Error as ConsensusError;
use sp_consensus::{CanAuthorWith, Error as ConsensusError};
use sp_consensus_aura::{AuraApi, Slot};
use sp_keystore::SyncCryptoStorePtr;
use sp_runtime::generic::OpaqueDigestItemId;
use sp_runtime::traits::{Block as BlockT, Header};
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
use thiserror::Error;

mod author_validation;

#[cfg(test)]
mod tests;

/// A block-import handler for Bioauth.
pub struct BioauthBlockImport<Backend, Block: BlockT, Client> {
/// The client to interact with the chain.
inner: Arc<Client>,
/// Keystore to extract validator public key.
keystore: SyncCryptoStorePtr,
/// A phantom data for Backend.
_phantom_back_end: PhantomData<Backend>,
/// A phantom data for Block.
Expand All @@ -49,60 +55,46 @@ pub enum BioauthBlockImportError {
ErrorExtractAuthorities,
}

impl<Backend, Block: BlockT, Client> Clone for BioauthBlockImport<Backend, Block, Client> {
fn clone(&self) -> Self {
impl<BE, Block: BlockT, Client> BioauthBlockImport<BE, Block, Client> {
/// Simple constructor.
pub fn new(inner: Arc<Client>, keystore: SyncCryptoStorePtr) -> Self
where
BE: Backend<Block> + 'static,
{
BioauthBlockImport {
inner: Arc::clone(&self.inner),
inner,
keystore,
_phantom_back_end: PhantomData,
_phantom_block: PhantomData,
}
}
}

impl<BE, Block: BlockT, Client> BioauthBlockImport<BE, Block, Client> {
/// Simple constructor.
pub fn new(inner: Arc<Client>) -> Self
where
BE: Backend<Block> + 'static,
{
impl<Backend, Block: BlockT, Client> Clone for BioauthBlockImport<Backend, Block, Client> {
fn clone(&self) -> Self {
BioauthBlockImport {
inner,
inner: Arc::clone(&self.inner),
keystore: Arc::clone(&self.keystore),
_phantom_back_end: PhantomData,
_phantom_block: PhantomData,
}
}
}

#[async_trait::async_trait]
impl<BE, Block: BlockT, Client> BlockImport<Block> for BioauthBlockImport<BE, Block, Client>
impl<Backend, Block: BlockT, Client> BlockAuthorExtractor
for BioauthBlockImport<Backend, Block, Client>
where
Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + Send + Sync + Finalizer<Block, BE>,
for<'a> &'a Client:
BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<Client, Block>>,
TransactionFor<Client, Block>: 'static,
Client: HeaderBackend<Block> + ProvideRuntimeApi<Block>,
Client::Api: AuraApi<Block, sp_consensus_aura::sr25519::AuthorityId>,
Client::Api: BioauthApi<Block>,
BE: Backend<Block>,
{
type Error = ConsensusError;
type BlockHeader = Block::Header;
type PublicKeyType = Vec<u8>;

type Transaction = TransactionFor<Client, Block>;

/// Check block preconditions. Only entire structure of a block.
async fn check_block(
&mut self,
block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
self.inner.check_block(block).await
}

/// Import a block.
/// Cached data can be accessed through the blockchain cache.
async fn import_block(
&mut self,
block: BlockImportParams<Block, Self::Transaction>,
cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
fn extract_block_author(
&self,
block_header: &Self::BlockHeader,
) -> Result<Vec<u8>, Self::Error> {
// Extract a number of the last imported block.
let at = &sp_api::BlockId::Hash(self.inner.info().best_hash);

Expand All @@ -112,8 +104,7 @@ where
})?;

// Extract current slot of a new produced block.
let mut slot = block
.header
let mut slot = block_header
.digest()
.log(|l| l.try_as_raw(OpaqueDigestItemId::PreRuntime(b"aura")))
.ok_or_else(|| {
Expand All @@ -136,7 +127,23 @@ where
.ok_or_else(|| {
sp_consensus::Error::Other(Box::new(BioauthBlockImportError::InvalidSlotNumber))
})?;
let author_public_key = author_public_key.as_slice();

Ok(author_public_key.to_raw_vec())
}
}

impl<Backend, Block: BlockT, Client> AuthorizationVerifier
for BioauthBlockImport<Backend, Block, Client>
where
Client: HeaderBackend<Block> + ProvideRuntimeApi<Block>,
Client::Api: BioauthApi<Block>,
{
type Error = ConsensusError;
type PublicKeyType = [u8];

fn is_authorized(&self, author_public_key: &Self::PublicKeyType) -> Result<bool, Self::Error> {
// Extract a number of the last imported block.
let at = &sp_api::BlockId::Hash(self.inner.info().best_hash);

// Get current stored tickets.
let stored_tickets = self
Expand All @@ -153,6 +160,47 @@ where
.iter()
.any(|ticket| ticket.public_key == author_public_key);

Ok(is_authorized)
}
}

#[async_trait::async_trait]
impl<BE, Block: BlockT, Client> BlockImport<Block> for BioauthBlockImport<BE, Block, Client>
where
Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + Send + Sync + Finalizer<Block, BE>,
for<'a> &'a Client:
BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<Client, Block>>,
TransactionFor<Client, Block>: 'static,
Client::Api: AuraApi<Block, sp_consensus_aura::sr25519::AuthorityId>,
Client::Api: BioauthApi<Block>,
BE: Backend<Block>,
{
type Error = ConsensusError;

type Transaction = TransactionFor<Client, Block>;

/// Check block preconditions. Only entire structure of a block.
async fn check_block(
&mut self,
block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
self.inner.check_block(block).await
}

/// Import a block.
/// Cached data can be accessed through the blockchain cache.
async fn import_block(
&mut self,
block: BlockImportParams<Block, Self::Transaction>,
cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
// Extract a number of the last imported block.
let at = &sp_api::BlockId::Hash(self.inner.info().best_hash);

let author_public_key = self.extract_block_author(&block.header)?;

let is_authorized = self.is_authorized(author_public_key.as_slice())?;

if !is_authorized {
return Err(sp_consensus::Error::Other(Box::new(
BioauthBlockImportError::NotBioauthAuthorized,
Expand All @@ -168,3 +216,31 @@ where
self.inner.import_block(block, cache).await
}
}

impl<BE, Block: BlockT, Client> CanAuthorWith<Block> for BioauthBlockImport<BE, Block, Client>
where
Client: HeaderBackend<Block> + ProvideRuntimeApi<Block>,
Client::Api: BioauthApi<Block>,
{
fn can_author_with(&self, _at: &sp_api::BlockId<Block>) -> Result<(), String> {
let mut aura_public_keys = sp_keystore::SyncCryptoStore::sr25519_public_keys(
self.keystore.as_ref(),
sp_application_crypto::key_types::AURA,
);
assert_eq!(aura_public_keys.len(), 1);
let aura_public_key = match aura_public_keys.drain(..).next() {
Some(v) => v,
_ => return Err("You aren't Aura validator.".to_string()),
};

let is_authorized = self
.is_authorized(aura_public_key.as_slice())
.map_err(|e| e.to_string())?;

if !is_authorized {
return Err("You aren't bioauth-authorized.".to_string());
}

Ok(())
}
}
17 changes: 12 additions & 5 deletions crates/bioauth-consensus/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use mockall::predicate::*;
use mockall::*;
use node_primitives::{Block, BlockNumber, Hash, Header};
use pallet_bioauth::StoredAuthTicket;
use sc_service::config::KeystoreConfig;
use sc_service::KeystoreContainer;
use sp_api::{ApiError, ApiRef, NativeOrEncoded};
use sp_consensus::BlockOrigin;
use sp_consensus_aura::digests::CompatibleDigestItem;
Expand Down Expand Up @@ -202,12 +204,13 @@ async fn it_denies_block_import_with_error_extract_authorities() {
.returning(move || runtime_api.clone().into());

let client = Arc::new(mock_client);
let keystore_container = KeystoreContainer::new(&KeystoreConfig::InMemory).unwrap();

let mut bioauth_block_import: BioauthBlockImport<
sc_service::TFullBackend<Block>,
_,
MockClient,
> = BioauthBlockImport::new(Arc::clone(&client));
> = BioauthBlockImport::new(Arc::clone(&client), keystore_container.sync_keystore());

let res = bioauth_block_import
.import_block(
Expand Down Expand Up @@ -243,12 +246,13 @@ async fn it_denies_block_import_with_invalid_slot_number() {
.returning(move || runtime_api.clone().into());

let client = Arc::new(mock_client);
let keystore_container = KeystoreContainer::new(&KeystoreConfig::InMemory).unwrap();

let mut bioauth_block_import: BioauthBlockImport<
sc_service::TFullBackend<Block>,
_,
MockClient,
> = BioauthBlockImport::new(Arc::clone(&client));
> = BioauthBlockImport::new(Arc::clone(&client), keystore_container.sync_keystore());

let res = bioauth_block_import
.import_block(
Expand Down Expand Up @@ -287,12 +291,13 @@ async fn it_denies_block_import_with_error_extract_stored_auth_ticket() {
.returning(move || runtime_api.clone().into());

let client = Arc::new(mock_client);
let keystore_container = KeystoreContainer::new(&KeystoreConfig::InMemory).unwrap();

let mut bioauth_block_import: BioauthBlockImport<
sc_service::TFullBackend<Block>,
_,
MockClient,
> = BioauthBlockImport::new(Arc::clone(&client));
> = BioauthBlockImport::new(Arc::clone(&client), keystore_container.sync_keystore());

let res = bioauth_block_import
.import_block(
Expand Down Expand Up @@ -339,12 +344,13 @@ async fn it_denies_block_import_with_not_bioauth_authorized() {
.returning(move || runtime_api.clone().into());

let client = Arc::new(mock_client);
let keystore_container = KeystoreContainer::new(&KeystoreConfig::InMemory).unwrap();

let mut bioauth_block_import: BioauthBlockImport<
sc_service::TFullBackend<Block>,
_,
MockClient,
> = BioauthBlockImport::new(Arc::clone(&client));
> = BioauthBlockImport::new(Arc::clone(&client), keystore_container.sync_keystore());

let res = bioauth_block_import
.import_block(
Expand Down Expand Up @@ -405,12 +411,13 @@ async fn it_permits_block_import_with_valid_data() {
.returning(move || runtime_api.clone().into());

let client = Arc::new(mock_client);
let keystore_container = KeystoreContainer::new(&KeystoreConfig::InMemory).unwrap();

let mut bioauth_block_import: BioauthBlockImport<
sc_service::TFullBackend<Block>,
_,
MockClient,
> = BioauthBlockImport::new(Arc::clone(&client));
> = BioauthBlockImport::new(Arc::clone(&client), keystore_container.sync_keystore());

let res = bioauth_block_import
.import_block(
Expand Down
1 change: 0 additions & 1 deletion crates/humanode-peer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ qr2term = "0.2"
reqwest = "0.11"
sc-basic-authorship = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sc-cli = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sc-client-api = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sc-consensus = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sc-consensus-aura = { git = "https://github.com/humanode-network/substrate", branch = "master" }
sc-executor = { git = "https://github.com/humanode-network/substrate", branch = "master" }
Expand Down
Loading