Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Deprecate ValidateUnsigned and prevent duplicate heartbeats #3975

Merged
merged 6 commits into from
Nov 3, 2019
Merged
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
12 changes: 9 additions & 3 deletions core/sr-primitives/src/generic/checked_extrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
//! stage.

use crate::traits::{
self, Member, MaybeDisplay, SignedExtension, Dispatchable, ValidateUnsigned,
self, Member, MaybeDisplay, SignedExtension, Dispatchable,
};
#[allow(deprecated)]
use crate::traits::ValidateUnsigned;
use crate::weights::{GetDispatchInfo, DispatchInfo};
use crate::transaction_validity::TransactionValidity;

Expand Down Expand Up @@ -51,6 +53,7 @@ where
self.signed.as_ref().map(|x| &x.0)
}

#[allow(deprecated)] // Allow ValidateUnsigned
fn validate<U: ValidateUnsigned<Call = Self::Call>>(
&self,
info: DispatchInfo,
Expand All @@ -60,11 +63,13 @@ where
Extra::validate(extra, id, &self.function, info, len)
} else {
let valid = Extra::validate_unsigned(&self.function, info, len)?;
Ok(valid.combine_with(U::validate_unsigned(&self.function)?))
let unsigned_validation = U::validate_unsigned(&self.function)?;
Ok(valid.combine_with(unsigned_validation))
}
}

fn apply(
#[allow(deprecated)] // Allow ValidateUnsigned
fn apply<U: ValidateUnsigned<Call=Self::Call>>(
self,
info: DispatchInfo,
len: usize,
Expand All @@ -74,6 +79,7 @@ where
(Some(id), pre)
} else {
let pre = Extra::pre_dispatch_unsigned(&self.function, info, len)?;
U::pre_dispatch(&self.function)?;
(None, pre)
};
let res = self.function.dispatch(Origin::from(maybe_who));
Expand Down
8 changes: 6 additions & 2 deletions core/sr-primitives/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ use serde::{Serialize, Serializer, Deserialize, de::Error as DeError, Deserializ
use std::{fmt::Debug, ops::Deref, fmt, cell::RefCell};
use crate::codec::{Codec, Encode, Decode};
use crate::traits::{
self, Checkable, Applyable, BlakeTwo256, OpaqueKeys, ValidateUnsigned,
self, Checkable, Applyable, BlakeTwo256, OpaqueKeys,
SignedExtension, Dispatchable,
};
#[allow(deprecated)]
use crate::traits::ValidateUnsigned;
use crate::{generic, KeyTypeId, ApplyResult};
use crate::weights::{GetDispatchInfo, DispatchInfo};
pub use primitives::{H256, sr25519};
Expand Down Expand Up @@ -323,6 +325,7 @@ impl<Origin, Call, Extra> Applyable for TestXt<Call, Extra> where
fn sender(&self) -> Option<&Self::AccountId> { self.0.as_ref().map(|x| &x.0) }

/// Checks to see if this is a valid *transaction*. It returns information on it if so.
#[allow(deprecated)] // Allow ValidateUnsigned
fn validate<U: ValidateUnsigned<Call=Self::Call>>(
&self,
_info: DispatchInfo,
Expand All @@ -333,7 +336,8 @@ impl<Origin, Call, Extra> Applyable for TestXt<Call, Extra> where

/// Executes all necessary logic needed prior to dispatch and deconstructs into function call,
/// index and sender.
fn apply(
#[allow(deprecated)] // Allow ValidateUnsigned
fn apply<U: ValidateUnsigned<Call=Self::Call>>(
self,
info: DispatchInfo,
len: usize,
Expand Down
24 changes: 20 additions & 4 deletions core/sr-primitives/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,9 +755,6 @@ pub trait SignedExtension: Codec + Debug + Sync + Send + Clone + Eq + PartialEq

/// Validate an unsigned transaction for the transaction queue.
///
/// Normally the default implementation is fine since `ValidateUnsigned`
/// is a better way of recognising and validating unsigned transactions.
///
/// This function can be called frequently by the transaction queue,
/// to obtain transaction validity against current state.
/// It should perform all checks that determine a valid unsigned transaction,
Expand Down Expand Up @@ -889,6 +886,7 @@ pub trait Applyable: Sized + Send + Sync {
fn sender(&self) -> Option<&Self::AccountId>;

/// Checks to see if this is a valid *transaction*. It returns information on it if so.
#[allow(deprecated)] // Allow ValidateUnsigned
fn validate<V: ValidateUnsigned<Call=Self::Call>>(
&self,
info: DispatchInfo,
Expand All @@ -897,7 +895,8 @@ pub trait Applyable: Sized + Send + Sync {

/// Executes all necessary logic needed prior to dispatch and deconstructs into function call,
/// index and sender.
fn apply(
#[allow(deprecated)] // Allow ValidateUnsigned
fn apply<V: ValidateUnsigned<Call=Self::Call>>(
self,
info: DispatchInfo,
len: usize,
Expand Down Expand Up @@ -966,10 +965,27 @@ pub trait RuntimeApiInfo {
/// the transaction for the transaction pool.
/// During block execution phase one need to perform the same checks anyway,
/// since this function is not being called.
#[deprecated(note = "Use SignedExtensions instead.")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should we use SignedExtensions for this? You mean we should create an extra signed extension for each unsigned transaction and add this to the signed extensions used by the UncheckedExtrinsic?

This would mean that we accept any unsigned transaction, when we forget to add the appropriate signed extension?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's a fair point that we should only allow unsigned transactions that are coming from one of the whitelisted modules (currently the validate_unsigned function of SignedExtension will just allow everything). But it should be easy to do a wrapper type that simply disallows a transaction if it's unsigned and none of the modules returns non-default TransactionValidity.

Actually it might be best to just return InvalidTransaction by default from validate_unsigned when we get rid of UnsignedValidator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good ideas :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe one of our meta-modules (system etc.) can register a unified CheckAndHandleUnsigned signed extension. What this does is it rejects everything except for a handful ones that are explicitly whitelisted. This can be injected into system via type UnsignedValidatorModules which would be a tuple of modules that implement some trait (maybe even the current ValidateUnsigned)

This basically reverses the problem Basti said

This would mean that we accept any unsigned transaction, when we forget to add the appropriate signed extension?

everything is rejected unless a module explicitly ping system module about it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay what I said ^^ is in the same line of:

But it should be easy to do a wrapper type that simply disallows a transaction if it's unsigned and none of the modules returns non-default TransactionValidity.

i just understood it :D

pub trait ValidateUnsigned {
/// The call to validate
type Call;

/// Validate the call right before dispatch.
///
/// This method should be used to prevent transactions already in the pool
/// (i.e. passing `validate_unsigned`) from being included in blocks
/// in case we know they now became invalid.
///
/// By default it's a good idea to call `validate_unsigned` from within
/// this function again to make sure we never include an invalid transaction.
///
/// Changes made to storage WILL be persisted if the call returns `Ok`.
fn pre_dispatch(call: &Self::Call) -> Result<(), crate::ApplyError> {
Self::validate_unsigned(call)
.map(|_| ())
.map_err(Into::into)
}

/// Return the validity of the call
///
/// This doesn't execute any side-effects; it merely checks
Expand Down
16 changes: 13 additions & 3 deletions srml/executive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
//! # pub type AllModules = u64;
//! # pub enum Runtime {};
//! # use sr_primitives::transaction_validity::{TransactionValidity, UnknownTransaction};
//! # #[allow(deprecated)]
//! # use sr_primitives::traits::ValidateUnsigned;
//! # #[allow(deprecated)]
//! # impl ValidateUnsigned for Runtime {
//! # type Call = ();
//! #
Expand All @@ -79,10 +81,12 @@ use sr_primitives::{
generic::Digest, ApplyResult, weights::GetDispatchInfo,
traits::{
self, Header, Zero, One, Checkable, Applyable, CheckEqual, OnFinalize, OnInitialize,
NumberFor, Block as BlockT, OffchainWorker, ValidateUnsigned, Dispatchable
NumberFor, Block as BlockT, OffchainWorker, Dispatchable,
},
transaction_validity::TransactionValidity,
};
#[allow(deprecated)]
use sr_primitives::traits::ValidateUnsigned;
use codec::{Codec, Encode};
use system::{extrinsics_root, DigestOf};

Expand All @@ -100,6 +104,7 @@ pub struct Executive<System, Block, Context, UnsignedValidator, AllModules>(
PhantomData<(System, Block, Context, UnsignedValidator, AllModules)>
);

#[allow(deprecated)] // Allow ValidateUnsigned, remove the attribute when the trait is removed.
impl<
System: system::Trait,
Block: traits::Block<Header=System::Header, Hash=System::Hash>,
Expand All @@ -119,6 +124,7 @@ where
}
}

#[allow(deprecated)] // Allow ValidateUnsigned, remove the attribute when the trait is removed.
impl<
System: system::Trait,
Block: traits::Block<Header=System::Header, Hash=System::Hash>,
Expand Down Expand Up @@ -242,7 +248,7 @@ where

// Decode parameters and dispatch
let dispatch_info = xt.get_dispatch_info();
let r = Applyable::apply(xt, dispatch_info, encoded_len)?;
let r = Applyable::apply::<UnsignedValidator>(xt, dispatch_info, encoded_len)?;

<system::Module<System>>::note_applied_extrinsic(&r, encoded_len as u32);

Expand Down Expand Up @@ -326,7 +332,6 @@ mod tests {
}
}

// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, Eq, PartialEq)]
pub struct Runtime;
parameter_types! {
Expand Down Expand Up @@ -382,9 +387,14 @@ mod tests {
type FeeMultiplierUpdate = ();
}

#[allow(deprecated)]
impl ValidateUnsigned for Runtime {
type Call = Call;

fn pre_dispatch(_call: &Self::Call) -> Result<(), ApplyError> {
Ok(())
}

fn validate_unsigned(call: &Self::Call) -> TransactionValidity {
match call {
Call::Balances(BalancesCall::set_balance(_, _, _)) => Ok(Default::default()),
Expand Down
17 changes: 7 additions & 10 deletions srml/im-online/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ use sr_staking_primitives::{
offence::{ReportOffence, Offence, Kind},
};
use support::{
decl_module, decl_event, decl_storage, print, ensure, Parameter, debug
decl_module, decl_event, decl_storage, print, Parameter, debug
};
use system::ensure_none;
use system::offchain::SubmitUnsignedTransaction;
Expand Down Expand Up @@ -243,24 +243,20 @@ decl_module! {
fn heartbeat(
origin,
heartbeat: Heartbeat<T::BlockNumber>,
signature: <T::AuthorityId as RuntimeAppPublic>::Signature
// since signature verification is done in `validate_unsigned`
// we can skip doing it here again.
_signature: <T::AuthorityId as RuntimeAppPublic>::Signature
) {
ensure_none(origin)?;

let current_session = <session::Module<T>>::current_index();
ensure!(current_session == heartbeat.session_index, "Outdated heartbeat received.");
let exists = <ReceivedHeartbeats>::exists(
&current_session,
&heartbeat.authority_index
);
let keys = Keys::<T>::get();
let maybe_public = keys.get(heartbeat.authority_index as usize);
if let (false, Some(public)) = (exists, maybe_public) {
let signature_valid = heartbeat.using_encoded(|encoded_heartbeat| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this gone?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's done in validate_unsigned as well before dispatching the transaction. @tomusdrw are pre_dispatch checks run as part of block execution? Otherwise someone could include an invalid transaction into a block if the signature is only checked there.

Copy link
Contributor Author

@tomusdrw tomusdrw Oct 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's the main difference between validate_transaction(run only by the pool, to determine if transaction should enter it or not) and pre_dispatch (run during block processing to make sure the transaction is valid). A transaction with failing pre_dispatch should cause a block import to panic. We are only able to recover from it during block authorship (we just skip transaction, mark it as invalid in the pool and try others).

The reason why we have pre-dispatch failing transactions in the pool is asynchrony between validate_transaction and the actual block creation. Some transactions might become invalid because some other transactions were imported first, and such relationship is not possible to express via provides.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation! :)

public.verify(&encoded_heartbeat, &signature)
});
ensure!(signature_valid, "Invalid heartbeat signature.");

let public = keys.get(heartbeat.authority_index as usize);
if let (false, Some(public)) = (exists, public) {
Self::deposit_event(Event::<T>::HeartbeatReceived(public.clone()));

let network_state = heartbeat.network_state.encode();
Expand Down Expand Up @@ -545,6 +541,7 @@ impl<T: Trait> session::OneSessionHandler<T::AccountId> for Module<T> {
}
}

#[allow(deprecated)]
impl<T: Trait> support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;

Expand Down
9 changes: 7 additions & 2 deletions srml/im-online/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ fn heartbeat(
authority_index: u32,
id: UintAuthorityId,
) -> dispatch::Result {
#[allow(deprecated)]
use support::unsigned::ValidateUnsigned;

let heartbeat = Heartbeat {
block_number,
network_state: OpaqueNetworkState {
Expand All @@ -114,6 +117,8 @@ fn heartbeat(
};
let signature = id.sign(&heartbeat.encode()).unwrap();

#[allow(deprecated)] // Allow ValidateUnsigned
ImOnline::pre_dispatch(&crate::Call::heartbeat(heartbeat.clone(), signature.clone()))?;
ImOnline::heartbeat(
Origin::system(system::RawOrigin::None),
heartbeat,
Expand Down Expand Up @@ -170,8 +175,8 @@ fn late_heartbeat_should_fail() {
assert_eq!(Session::validators(), vec![1, 2, 3]);

// when
assert_noop!(heartbeat(1, 3, 0, 1.into()), "Outdated heartbeat received.");
assert_noop!(heartbeat(1, 1, 0, 1.into()), "Outdated heartbeat received.");
assert_noop!(heartbeat(1, 3, 0, 1.into()), "Transaction is outdated");
assert_noop!(heartbeat(1, 1, 0, 1.into()), "Transaction is outdated");
});
}

Expand Down
13 changes: 13 additions & 0 deletions srml/support/src/unsigned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.

#[doc(hidden)]
#[allow(deprecated)]
pub use crate::sr_primitives::traits::ValidateUnsigned;
#[doc(hidden)]
pub use crate::sr_primitives::transaction_validity::{TransactionValidity, UnknownTransaction};
Expand Down Expand Up @@ -65,9 +66,20 @@ macro_rules! impl_outer_validate_unsigned {
$( $module:ident )*
}
) => {
#[allow(deprecated)] // Allow ValidateUnsigned
impl $crate::unsigned::ValidateUnsigned for $runtime {
type Call = Call;

fn pre_dispatch(call: &Self::Call) -> Result<(), $crate::unsigned::ApplyError> {
#[allow(unreachable_patterns)]
match call {
$( Call::$module(inner_call) => $module::pre_dispatch(inner_call), )*
// pre-dispatch should not stop inherent extrinsics, validation should prevent
// including arbitrary (non-inherent) extrinsics to blocks.
_ => Ok(()),
}
}

fn validate_unsigned(call: &Self::Call) -> $crate::unsigned::TransactionValidity {
#[allow(unreachable_patterns)]
match call {
Expand Down Expand Up @@ -97,6 +109,7 @@ mod test_partial_and_full_call {
pub mod timestamp {
pub struct Module;

#[allow(deprecated)] // Allow ValidateUnsigned
impl super::super::ValidateUnsigned for Module {
type Call = Call;

Expand Down