diff --git a/client/network/src/protocol/notifications/behaviour.rs b/client/network/src/protocol/notifications/behaviour.rs index 26a246f57690f..f8d591bdc9021 100644 --- a/client/network/src/protocol/notifications/behaviour.rs +++ b/client/network/src/protocol/notifications/behaviour.rs @@ -716,7 +716,7 @@ impl Notifications { timer: delay_id, timer_deadline: *backoff, }; - }, + } // Disabled => Enabled PeerState::Disabled { mut connections, backoff_until } => { @@ -2098,7 +2098,7 @@ impl NetworkBehaviour for Notifications { .boxed(), ); } - }, + } // We intentionally never remove elements from `delays`, and it may // thus contain obsolete entries. This is a normal situation. diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index 02cb2b22b3886..8b8e566ce384d 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -36,7 +36,9 @@ sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } [features] default = ["std"] -with-tracing = ["sp-tracing/with-tracing"] + +# Enable signature verification in background task +background-sig-check = [] std = [ "codec/std", "scale-info/std", @@ -48,3 +50,4 @@ std = [ "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] +with-tracing = ["sp-tracing/with-tracing"] diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 7ff5584879cea..c34dc9397bea8 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -125,10 +125,14 @@ use frame_support::{ }, weights::{DispatchClass, DispatchInfo, GetDispatchInfo}, }; +#[cfg(not(feature = "background-sig-check"))] +use sp_runtime::traits::Checkable; +#[cfg(feature = "background-sig-check")] +use sp_runtime::traits::{BackgroundCheckable as Checkable, Checkable as _}; use sp_runtime::{ generic::Digest, traits::{ - self, Applyable, CheckEqual, Checkable, Dispatchable, Header, NumberFor, One, Saturating, + self, Applyable, CheckEqual, Dispatchable, Header, NumberFor, One, Saturating, ValidateUnsigned, Zero, }, transaction_validity::{TransactionSource, TransactionValidity}, @@ -136,7 +140,7 @@ use sp_runtime::{ }; use sp_std::{marker::PhantomData, prelude::*}; -pub type CheckedOf = >::Checked; +pub type CheckedOf = >::Checked; pub type CallOf = as Applyable>::Call; pub type OriginOf = as Dispatchable>::Origin; @@ -351,6 +355,7 @@ where } } + #[cfg(feature = "background-sig-check")] /// Actually execute all transitions for `block`. pub fn execute_block(block: Block) { sp_io::init_tracing(); @@ -362,12 +367,28 @@ where // any initial checks Self::initial_checks(&block); + + // check extrinsics in background + let (header, extrinsics) = block.deconstruct(); let signature_batching = sp_runtime::SignatureBatching::start(); + let checked_extrinsics = extrinsics + .into_iter() + .map(|extrinsic| { + let encoded = extrinsic.encode(); + match extrinsic.background_check(&Default::default()) { + Ok(checked_extrinsic) => (checked_extrinsic, encoded), + Err(e) => { + let err: &'static str = e.into(); + panic!("{}", err) + } + } + }) + .collect(); // execute extrinsics - let (header, extrinsics) = block.deconstruct(); - Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); + Self::execute_checked_extrinsics_with_book_keeping(checked_extrinsics, *header.number()); + // ensure that all background checks have been completed successfully. if !signature_batching.verify() { panic!("Signature verification failed."); } @@ -377,6 +398,27 @@ where } } + #[cfg(not(feature = "background-sig-check"))] + /// Actually execute all transitions for `block`. + pub fn execute_block(block: Block) { + sp_io::init_tracing(); + sp_tracing::within_span! { + sp_tracing::info_span!("execute_block", ?block); + + Self::initialize_block(block.header()); + + // any initial checks + Self::initial_checks(&block); + + // execute extrinsics + let (header, extrinsics) = block.deconstruct(); + Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); + + // any final checks + Self::final_checks(&header); + } + } + /// Execute given extrinsics and take care of post-extrinsics book-keeping. fn execute_extrinsics_with_book_keeping( extrinsics: Vec, @@ -395,6 +437,29 @@ where Self::idle_and_finalize_hook(block_number); } + #[cfg(feature = "background-sig-check")] + /// Execute given checked extrinsics and take care of post-extrinsics book-keeping. + fn execute_checked_extrinsics_with_book_keeping( + checked_extrinsics: Vec<(CheckedOf, Vec)>, + block_number: NumberFor, + ) { + checked_extrinsics.into_iter().for_each( + |(checked_extrinsic, unchecked_extrinsic_encoded)| { + if let Err(e) = + Self::apply_checked_extrinsic(checked_extrinsic, unchecked_extrinsic_encoded) + { + let err: &'static str = e.into(); + panic!("{}", err) + } + }, + ); + + // post-extrinsics book-keeping + >::note_finished_extrinsics(); + + Self::idle_and_finalize_hook(block_number); + } + /// Finalize the block - it is up the caller to ensure that all header fields are valid /// except state-root. pub fn finalize_block() -> System::Header { @@ -465,6 +530,38 @@ where Ok(r.map(|_| ()).map_err(|e| e.error)) } + #[cfg(feature = "background-sig-check")] + /// Apply checked extrinsic outside of the block execution function. + /// + /// This doesn't attempt to validate anything regarding the block, but it builds a list of uxt + /// hashes. + pub fn apply_checked_extrinsic( + checked_extrinsic: CheckedOf, + unchecked_extrinsic_encoded: Vec, + ) -> ApplyExtrinsicResult { + sp_io::init_tracing(); + sp_tracing::enter_span!(sp_tracing::info_span!("apply_checked_extrinsic", + ext=?sp_core::hexdisplay::HexDisplay::from(&unchecked_extrinsic_encoded))); + + let encoded_len = unchecked_extrinsic_encoded.len(); + + // We don't need to make sure to `note_extrinsic` only after we know it's going to be + // executed to prevent it from leaking in storage since at this point, it will either + // execute or panic (and revert storage changes). + >::note_extrinsic(unchecked_extrinsic_encoded); + + // AUDIT: Under no circumstances may this function panic from here onwards. + + // Decode parameters and dispatch + let dispatch_info = checked_extrinsic.get_dispatch_info(); + let r = + Applyable::apply::(checked_extrinsic, &dispatch_info, encoded_len)?; + + >::note_applied_extrinsic(&r, dispatch_info); + + Ok(r.map(|_| ()).map_err(|e| e.error)) + } + fn final_checks(header: &System::Header) { sp_tracing::enter_span!(sp_tracing::Level::TRACE, "final_checks"); // remove temporaries diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index 53e7c349f2a76..f0f33d1bb7921 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -20,8 +20,8 @@ use crate::{ generic::CheckedExtrinsic, traits::{ - self, Checkable, Extrinsic, ExtrinsicMetadata, IdentifyAccount, MaybeDisplay, Member, - SignedExtension, + self, BackgroundCheckable, Checkable, Extrinsic, ExtrinsicMetadata, IdentifyAccount, + MaybeDisplay, Member, SignedExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, @@ -159,6 +159,36 @@ where } } +impl BackgroundCheckable + for UncheckedExtrinsic +where + Address: Member + MaybeDisplay, + Call: Encode + Member, + Signature: Member + traits::BackgroundVerify, + ::Signer: IdentifyAccount, + Extra: SignedExtension, + AccountId: Member + MaybeDisplay, + Lookup: traits::Lookup, +{ + fn background_check(self, lookup: &Lookup) -> Result { + Ok(match self.signature { + Some((signed, signature, extra)) => { + let signed = lookup.lookup(signed)?; + let raw_payload = SignedPayload::new(self.function, extra)?; + if !raw_payload + .using_encoded(|payload| signature.background_verify(payload, &signed)) + { + return Err(InvalidTransaction::BadProof.into()) + } + + let (function, extra, _) = raw_payload.deconstruct(); + CheckedExtrinsic { signed: Some((signed, extra)), function } + }, + None => CheckedExtrinsic { signed: None, function: self.function }, + }) + } +} + impl ExtrinsicMetadata for UncheckedExtrinsic where diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 26db5ef081a8c..02d88e675c400 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -412,6 +412,36 @@ impl Verify for MultiSignature { } } +impl crate::traits::BackgroundVerify for MultiSignature { + fn background_verify>(&self, mut msg: L, signer: &AccountId32) -> bool { + match (self, signer) { + (Self::Ed25519(ref sig), who) => + if let Ok(pubkey) = ed25519::Public::from_slice(who.as_ref()) { + sig.background_verify(msg, &pubkey) + } else { + false + }, + (Self::Sr25519(ref sig), who) => + if let Ok(pubkey) = sr25519::Public::from_slice(who.as_ref()) { + sig.background_verify(msg, &pubkey) + } else { + false + }, + (Self::Ecdsa(ref sig), who) => { + // Unfortunatly, ecdsa signature can't be verified in a background task + // because we don't known the public key. + let m = sp_io::hashing::blake2_256(msg.get()); + match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) { + Ok(pubkey) => + &sp_io::hashing::blake2_256(pubkey.as_ref()) == + >::as_ref(who), + _ => false, + } + }, + } + } +} + /// Signature verify that can work with any known signature types.. #[derive(Eq, PartialEq, Clone, Default, Encode, Decode, RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 0ddd8cae93ea7..b8ab9f603c75f 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -170,6 +170,57 @@ where } } +/// A signature that supports background verification. +pub trait BackgroundVerify: Verify { + /// Register a signature for background verification. + /// + /// This requires that background verification is enabled by doing XYZ. + /// + /// Returns `true` when the signature was successfully registered for background verification + /// or if background verification is not enabled the signature could be verified successfully + /// immediately. + /// + /// # Warning + /// + /// This requires that the background verification is finished by calling finalize_verify to + /// check the result of all submitted signature verifications. + fn background_verify>( + &self, + msg: L, + signer: &::AccountId, + ) -> bool; +} + +impl BackgroundVerify for sp_core::ed25519::Signature { + fn background_verify>( + &self, + mut msg: L, + signer: &::AccountId, + ) -> bool { + sp_io::crypto::ed25519_batch_verify(self, msg.get(), signer) + } +} + +impl BackgroundVerify for sp_core::sr25519::Signature { + fn background_verify>( + &self, + mut msg: L, + signer: &::AccountId, + ) -> bool { + sp_io::crypto::sr25519_batch_verify(self, msg.get(), signer) + } +} + +impl BackgroundVerify for sp_core::ecdsa::Signature { + fn background_verify>( + &self, + mut msg: L, + signer: &::AccountId, + ) -> bool { + sp_io::crypto::ecdsa_batch_verify(self, msg.get(), signer) + } +} + /// An error type that indicates that the origin is invalid. #[derive(Encode, Decode, RuntimeDebug)] pub struct BadOrigin; @@ -773,6 +824,18 @@ pub trait Checkable: Sized { fn check(self, c: &Context) -> Result; } +/// A piece of information "checkable" in a background task, used by the standard Substrate +/// Executive in order to check the validity of a piece of extrinsic information, usually by +/// verifying the signature. Implement for pieces of information that require some additional +/// context `Context` in order to be checked. +pub trait BackgroundCheckable: Checkable { + /// Check self in a background tas, given an instance of Context. + fn background_check( + self, + c: &Context, + ) -> Result<>::Checked, TransactionValidityError>; +} + /// A "checkable" piece of information, used by the standard Substrate Executive in order to /// check the validity of a piece of extrinsic information, usually by verifying the signature. /// Implement for pieces of information that don't require additional context in order to be