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} ;
9797use frame_system:: {
98- offchain:: { CreateBare , SubmitTransaction } ,
98+ offchain:: { CreateAuthorizedTransaction , SubmitTransaction } ,
9999 pallet_prelude:: * ,
100100} ;
101101pub use pallet:: * ;
@@ -104,6 +104,7 @@ use sp_application_crypto::RuntimeAppPublic;
104104use 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} ;
109110use 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
0 commit comments