Skip to content

Commit ae74c5d

Browse files
committed
Migrate pallet-im-online to TransactionExtension API
1 parent f9df323 commit ae74c5d

7 files changed

Lines changed: 205 additions & 136 deletions

File tree

substrate/bin/node/cli/tests/submit_transaction.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,21 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
use codec::Decode;
20-
use frame_system::offchain::{SendSignedTransaction, Signer, SubmitTransaction};
20+
use frame_system::offchain::{
21+
CreateAuthorizedTransaction, SendSignedTransaction, Signer, SubmitTransaction,
22+
};
2123
use kitchensink_runtime::{Executive, ExistentialDeposit, Indices, Runtime, UncheckedExtrinsic};
2224
use polkadot_sdk::*;
2325
use sp_application_crypto::AppCrypto;
2426
use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt};
2527
use sp_keyring::sr25519::Keyring::Alice;
2628
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt};
27-
use sp_runtime::generic;
2829

2930
pub mod common;
3031
use self::common::*;
3132

3233
#[test]
33-
fn should_submit_unsigned_transaction() {
34+
fn should_submit_authorized_transaction() {
3435
let mut t = new_test_ext(compact_code_unwrap());
3536
let (pool, state) = TestTransactionPoolExt::new();
3637
t.register_extension(TransactionPoolExt::new(pool));
@@ -46,7 +47,9 @@ fn should_submit_unsigned_transaction() {
4647
};
4748

4849
let call = pallet_im_online::Call::heartbeat { heartbeat: heartbeat_data, signature };
49-
let xt = generic::UncheckedExtrinsic::new_bare(call.into()).into();
50+
let xt = <Runtime as CreateAuthorizedTransaction<
51+
pallet_im_online::Call<Runtime>,
52+
>>::create_authorized_transaction(call.into());
5053
SubmitTransaction::<Runtime, pallet_im_online::Call<Runtime>>::submit_transaction(xt)
5154
.unwrap();
5255

substrate/frame/im-online/src/benchmarking.rs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
#![cfg(feature = "runtime-benchmarks")]
2121

2222
use frame_benchmarking::v2::*;
23-
use frame_support::{traits::UnfilteredDispatchable, WeakBoundedVec};
24-
use frame_system::RawOrigin;
25-
use sp_runtime::{
26-
traits::{ValidateUnsigned, Zero},
27-
transaction_validity::TransactionSource,
23+
use frame_support::{
24+
traits::{Authorize, UnfilteredDispatchable},
25+
WeakBoundedVec,
2826
};
27+
use frame_system::RawOrigin;
28+
use sp_runtime::{traits::Zero, transaction_validity::TransactionSource};
2929

3030
use crate::*;
3131

@@ -60,51 +60,56 @@ pub fn create_heartbeat<T: Config>(
6060
Ok((input_heartbeat, signature))
6161
}
6262

63-
#[benchmarks]
63+
#[benchmarks(where <T as frame_system::Config>::RuntimeCall: From<Call<T>>)]
6464
mod benchmarks {
6565
use super::*;
6666

67-
#[benchmark(extra)]
67+
#[benchmark]
6868
fn heartbeat(k: Linear<1, { <T as Config>::MaxKeys::get() }>) -> Result<(), BenchmarkError> {
6969
let (input_heartbeat, signature) = create_heartbeat::<T>(k)?;
7070

7171
#[extrinsic_call]
72-
_(RawOrigin::None, input_heartbeat, signature);
72+
_(RawOrigin::Authorized, input_heartbeat, signature);
7373

7474
Ok(())
7575
}
7676

77-
#[benchmark(extra)]
78-
fn validate_unsigned(
77+
#[benchmark]
78+
fn authorize_heartbeat(
7979
k: Linear<1, { <T as Config>::MaxKeys::get() }>,
8080
) -> Result<(), BenchmarkError> {
8181
let (input_heartbeat, signature) = create_heartbeat::<T>(k)?;
82-
let call = Call::heartbeat { heartbeat: input_heartbeat, signature };
82+
let call: <T as frame_system::Config>::RuntimeCall =
83+
Call::heartbeat { heartbeat: input_heartbeat, signature }.into();
8384

8485
#[block]
8586
{
86-
Pallet::<T>::validate_unsigned(TransactionSource::InBlock, &call)
87-
.map_err(<&str>::from)?;
87+
call.authorize(TransactionSource::InBlock)
88+
.expect("heartbeat call should have authorize logic")
89+
.map_err(|_| "authorize failed")?;
8890
}
8991

9092
Ok(())
9193
}
9294

93-
#[benchmark]
94-
fn validate_unsigned_and_then_heartbeat(
95+
#[benchmark(extra)]
96+
fn authorize_and_then_heartbeat(
9597
k: Linear<1, { <T as Config>::MaxKeys::get() }>,
9698
) -> Result<(), BenchmarkError> {
9799
let (input_heartbeat, signature) = create_heartbeat::<T>(k)?;
98100
let call = Call::heartbeat { heartbeat: input_heartbeat, signature };
101+
let runtime_call: <T as frame_system::Config>::RuntimeCall = call.clone().into();
99102
let call_enc = call.encode();
100103

101104
#[block]
102105
{
103-
Pallet::<T>::validate_unsigned(TransactionSource::InBlock, &call)
104-
.map_err(<&str>::from)?;
106+
runtime_call
107+
.authorize(TransactionSource::InBlock)
108+
.expect("heartbeat call should have authorize logic")
109+
.map_err(|_| "authorize failed")?;
105110
<Call<T> as Decode>::decode(&mut &*call_enc)
106111
.expect("call is encoded above, encoding must be correct")
107-
.dispatch_bypass_filter(RawOrigin::None.into())?;
112+
.dispatch_bypass_filter(RawOrigin::Authorized.into())?;
108113
}
109114

110115
Ok(())

substrate/frame/im-online/src/lib.rs

Lines changed: 62 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
//!
2828
//! The heartbeat is a signed transaction, which was signed using the session key
2929
//! and includes the recent best block number of the local validators chain.
30-
//! It is submitted as an Unsigned Transaction via off-chain workers.
30+
//! It is submitted as an Authorized Transaction via off-chain workers.
3131
//!
3232
//! - [`Config`]
3333
//! - [`Call`]
@@ -95,7 +95,7 @@ use frame_support::{
9595
BoundedSlice, WeakBoundedVec,
9696
};
9797
use frame_system::{
98-
offchain::{CreateBare, SubmitTransaction},
98+
offchain::{CreateAuthorizedTransaction, SubmitTransaction},
9999
pallet_prelude::*,
100100
};
101101
pub use pallet::*;
@@ -104,6 +104,7 @@ use sp_application_crypto::RuntimeAppPublic;
104104
use sp_runtime::{
105105
offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
106106
traits::{AtLeast32BitUnsigned, Convert, Saturating, TrailingZeroInput},
107+
transaction_validity::TransactionValidityWithRefund,
107108
Debug, PerThing, Perbill, Permill, SaturatedConversion,
108109
};
109110
use sp_staking::{
@@ -261,7 +262,11 @@ pub mod pallet {
261262
pub struct Pallet<T>(_);
262263

263264
#[pallet::config]
264-
pub trait Config: CreateBare<Call<Self>> + frame_system::Config {
265+
/// # Requirements
266+
///
267+
/// This pallet requires `frame_system::AuthorizeCall` to be included in the runtime's
268+
/// transaction extension pipeline.
269+
pub trait Config: CreateAuthorizedTransaction<Call<Self>> + frame_system::Config {
265270
/// The identifier type for an authority.
266271
type AuthorityId: Member
267272
+ Parameter
@@ -384,20 +389,68 @@ pub mod pallet {
384389
/// ## Complexity:
385390
/// - `O(K)` where K is length of `Keys` (heartbeat.validators_len)
386391
/// - `O(K)`: decoding of length `K`
387-
// NOTE: the weight includes the cost of validate_unsigned as it is part of the cost to
388-
// import block with such an extrinsic.
389392
#[pallet::call_index(0)]
390-
#[pallet::weight(<T as Config>::WeightInfo::validate_unsigned_and_then_heartbeat(
393+
#[pallet::weight(<T as Config>::WeightInfo::heartbeat(
394+
heartbeat.validators_len,
395+
))]
396+
#[pallet::weight_of_authorize(<T as Config>::WeightInfo::authorize_heartbeat(
391397
heartbeat.validators_len,
392398
))]
399+
#[pallet::authorize(|_source: TransactionSource,
400+
heartbeat: &Heartbeat<BlockNumberFor<T>>,
401+
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
402+
| -> TransactionValidityWithRefund {
403+
if Pallet::<T>::is_online(heartbeat.authority_index) {
404+
// we already received a heartbeat for this authority
405+
return Err(InvalidTransaction::Stale.into());
406+
}
407+
408+
// check if session index from heartbeat is recent
409+
let current_session = T::ValidatorSet::session_index();
410+
if heartbeat.session_index != current_session {
411+
return Err(InvalidTransaction::Stale.into());
412+
}
413+
414+
// verify that the incoming (unverified) pubkey is actually an authority id
415+
let keys = Keys::<T>::get();
416+
if keys.len() as u32 != heartbeat.validators_len {
417+
return Err(InvalidTransaction::Custom(INVALID_VALIDATORS_LEN).into());
418+
}
419+
let authority_id = match keys.get(heartbeat.authority_index as usize) {
420+
Some(id) => id,
421+
None => return Err(InvalidTransaction::BadProof.into()),
422+
};
423+
424+
// check signature (this is expensive so we do it last).
425+
let signature_valid = heartbeat.using_encoded(|encoded_heartbeat| {
426+
authority_id.verify(&encoded_heartbeat, signature)
427+
});
428+
429+
if !signature_valid {
430+
return Err(InvalidTransaction::BadProof.into());
431+
}
432+
433+
ValidTransaction::with_tag_prefix("ImOnline")
434+
.priority(T::UnsignedPriority::get())
435+
.and_provides((current_session, authority_id))
436+
.longevity(
437+
TryInto::<u64>::try_into(
438+
T::NextSessionRotation::average_session_length() / 2u32.into(),
439+
)
440+
.unwrap_or(64_u64),
441+
)
442+
.propagate(true)
443+
.build()
444+
.map(|v| (v, Weight::zero()))
445+
})]
393446
pub fn heartbeat(
394447
origin: OriginFor<T>,
395448
heartbeat: Heartbeat<BlockNumberFor<T>>,
396-
// since signature verification is done in `validate_unsigned`
449+
// since signature verification is done in `authorize`
397450
// we can skip doing it here again.
398451
_signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
399452
) -> DispatchResult {
400-
ensure_none(origin)?;
453+
ensure_authorized(origin)?;
401454

402455
let current_session = T::ValidatorSet::session_index();
403456
let exists =
@@ -446,59 +499,6 @@ pub mod pallet {
446499
/// Invalid transaction custom error. Returned when validators_len field in heartbeat is
447500
/// incorrect.
448501
pub(crate) const INVALID_VALIDATORS_LEN: u8 = 10;
449-
450-
#[pallet::validate_unsigned]
451-
impl<T: Config> ValidateUnsigned for Pallet<T> {
452-
type Call = Call<T>;
453-
454-
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
455-
if let Call::heartbeat { heartbeat, signature } = call {
456-
if <Pallet<T>>::is_online(heartbeat.authority_index) {
457-
// we already received a heartbeat for this authority
458-
return InvalidTransaction::Stale.into();
459-
}
460-
461-
// check if session index from heartbeat is recent
462-
let current_session = T::ValidatorSet::session_index();
463-
if heartbeat.session_index != current_session {
464-
return InvalidTransaction::Stale.into();
465-
}
466-
467-
// verify that the incoming (unverified) pubkey is actually an authority id
468-
let keys = Keys::<T>::get();
469-
if keys.len() as u32 != heartbeat.validators_len {
470-
return InvalidTransaction::Custom(INVALID_VALIDATORS_LEN).into();
471-
}
472-
let authority_id = match keys.get(heartbeat.authority_index as usize) {
473-
Some(id) => id,
474-
None => return InvalidTransaction::BadProof.into(),
475-
};
476-
477-
// check signature (this is expensive so we do it last).
478-
let signature_valid = heartbeat.using_encoded(|encoded_heartbeat| {
479-
authority_id.verify(&encoded_heartbeat, signature)
480-
});
481-
482-
if !signature_valid {
483-
return InvalidTransaction::BadProof.into();
484-
}
485-
486-
ValidTransaction::with_tag_prefix("ImOnline")
487-
.priority(T::UnsignedPriority::get())
488-
.and_provides((current_session, authority_id))
489-
.longevity(
490-
TryInto::<u64>::try_into(
491-
T::NextSessionRotation::average_session_length() / 2u32.into(),
492-
)
493-
.unwrap_or(64_u64),
494-
)
495-
.propagate(true)
496-
.build()
497-
} else {
498-
InvalidTransaction::Call.into()
499-
}
500-
}
501-
}
502502
}
503503

504504
/// Keep track of number of authored blocks per authority, uncles are counted as
@@ -643,7 +643,7 @@ impl<T: Config> Pallet<T> {
643643
call,
644644
);
645645

646-
let xt = T::create_bare(call.into());
646+
let xt = T::create_authorized_transaction(call.into());
647647
SubmitTransaction::<T, Call<T>>::submit_transaction(xt)
648648
.map_err(|_| OffchainErr::SubmitTransaction)?;
649649

substrate/frame/im-online/src/mock.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ use frame_support::{
2525
weights::Weight,
2626
};
2727
use pallet_session::historical as pallet_session_historical;
28-
use sp_runtime::{testing::UintAuthorityId, traits::ConvertInto, BuildStorage, Permill};
28+
use sp_runtime::{
29+
testing::UintAuthorityId,
30+
traits::{BlakeTwo256, ConvertInto},
31+
BuildStorage, Permill,
32+
};
2933
use sp_staking::{
3034
offence::{OffenceError, ReportOffence},
3135
SessionIndex,
@@ -34,7 +38,10 @@ use sp_staking::{
3438
use crate as imonline;
3539
use crate::Config;
3640

37-
type Block = frame_system::mocking::MockBlock<Runtime>;
41+
type TxExtension = frame_system::AuthorizeCall<Runtime>;
42+
/// An extrinsic type used for tests.
43+
pub type Extrinsic = sp_runtime::testing::TestXt<RuntimeCall, TxExtension>;
44+
type Block = sp_runtime::generic::Block<sp_runtime::generic::Header<u64, BlakeTwo256>, Extrinsic>;
3845

3946
frame_support::construct_runtime!(
4047
pub enum Runtime {
@@ -73,8 +80,6 @@ impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager
7380
fn start_session(_: SessionIndex) {}
7481
}
7582

76-
/// An extrinsic type used for tests.
77-
pub type Extrinsic = sp_runtime::testing::TestXt<RuntimeCall, ()>;
7883
type IdentificationTuple = (u64, u64);
7984
type Offence = crate::UnresponsivenessOffence<IdentificationTuple>;
8085

@@ -206,12 +211,22 @@ where
206211
type Extrinsic = Extrinsic;
207212
}
208213

209-
impl<LocalCall> frame_system::offchain::CreateBare<LocalCall> for Runtime
214+
impl<LocalCall> frame_system::offchain::CreateTransaction<LocalCall> for Runtime
215+
where
216+
RuntimeCall: From<LocalCall>,
217+
{
218+
type Extension = TxExtension;
219+
fn create_transaction(call: RuntimeCall, extension: Self::Extension) -> Extrinsic {
220+
Extrinsic::new_transaction(call, extension)
221+
}
222+
}
223+
224+
impl<LocalCall> frame_system::offchain::CreateAuthorizedTransaction<LocalCall> for Runtime
210225
where
211226
RuntimeCall: From<LocalCall>,
212227
{
213-
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
214-
Extrinsic::new_bare(call)
228+
fn create_extension() -> Self::Extension {
229+
TxExtension::new()
215230
}
216231
}
217232

0 commit comments

Comments
 (0)