Search code examples
substrate

Substrate: How to validate the originator of unsigned extrinsics?


I need to be able to identify the source of an unsigned extrinsic for spam prevention purposes. Assume there is a set of known authorities from whom I am willing to accept an unsigned extrinsic. I want to check that the sender of the unsigned extrinsic is a member of this authority set (and that they are who they say they are).

From what I can tell there are a couple of different approaches and I would like to better understand the differences between each and the trade-offs involved.

  1. Add a signature as a call parameter and define ValidateUnsigned.

    This approach is used in the ImOnline pallet. Roughly speaking:

    decl_module!(
        // ...
    
        fn my_unsigned_call(_origin, 
            args: MyArgs<T>, 
            authority: T::Authority, 
            signature: T::Signature) {
            // Handle the call
            todo!()
        }
    )
    
    impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
        // ...
    
        fn validate_unsigned(
            _source: TransactionSource,
            call: &Self::Call,
        ) -> TransactionValidity {
            if let Call::my_unsigned_call(args, authority, signature) = call {
                // Check the sender is in the approved authority set and verify sig
                todo!();
            }
        }
    }
    
  2. Implement SignedExtension for some metadata associated with the pallet trait. This is touched upon in the docs and seems to be implemented in the TransactionPayment pallet. Implementation would be something like this:

    struct SenderInfo<T> {
        authority: T::Authority,
        signature: T::Signature,
    }
    
    impl<T: Config + Send + Sync> SignedExtension for SenderInfo<T>
    where
        <T as frame_system::Config>::Call: IsSubType<Call<T>>,
    {
        // ... 
    
        fn validate_unsigned(
            call: &Self::Call,
            info: &DispatchInfoOf<Self::Call>,
            len: usize
        ) -> TransactionValidity {
            // validate self.authority and self.signature
    
        }
    }
    

    This SignedExtension then needs to be aggregated into the SignedExtra of the runtime. (Right?)

I am tending towards using the second option since it seems cleaner: it doesn't require me to pollute my method signatures with extra information that is not even used in the method call. But would this mean that any transaction submitted to the runtime, signed or unsigned, would need to add this customised SignedExtra?

Are there any other considerations I should be aware of?


Solution

  • I'm working on a very similar thing.

    I was able to do so with your approach 1. Basically I do check two things there:

    1. If payload was signed properly - When you think about it, this will tell you only about the user, but it doesn't check if a user is your authority user.
    2. I do check if this account is on my authorities list

    My working example is available here https://github.com/korzewski/jackblock/blob/master/pallets/jackblock/src/lib.rs#L401

    Although, this is not perfect because I need to keep a second list of Authorities and add them manually.

    Currently I'm trying to refactor it, so my authorities accounts are the same as my validators (Aura pallet). Still looking for a solution so maybe you know how to solve it? Basically how to reuse Aura pallet in my own pallet