4

I am coding a pallet in which I need to know who I am inside a offchain worker to check if I should send a transaction or not. Right now I have access to the account in the send_signed_transaction callback but it doesn`t seem like an elegant way.

This is what I would need:

    #[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
    fn offchain_worker(now: BlockNumberFor<T>) {
        let signer = Signer::<T, T::AuthorityId>::all_accounts();

        if !signer.can_sign() {
            log::warn!(
                target: LOG_TARGET,
                "Skipping offchain worker because no local account is available."
            );
            return;
        }

        if !sp_io::offchain::is_validator() {
            return;
        }

        // What i need
        let who = signer.iam();

        let validators = Validators::<T>::get();
        let approvedValidators = ApprovedValidators::<T>::get();

        if (!approvedValidators.contains(&who)) {
            return;
        }

        if (validators.contains(&who)) {
            return;
        }

        signer.send_signed_transaction(|_account| Call::add_validator_again {
            validator_id: _account.id.clone(),
        });

        return;
    }
}

2 Answers 2

1

It seems that your rationale is starting from the assumption that only OCWs will be calling add_validator_again. That is not safe. Malicious actors could also call them while "impersonating" an OCW.

Regardless of the checks you are making inside your OCW logic, you also need to make sure that the execution of add_validator_again is only granted to keys belonging to ApprovedValidators.

You could implement a SignedExtension with some logic similar to this:

impl<T: Config + Send + Sync> SignedExtension for AuthorizeOcw<T>
where
    T::Call: Dispatchable<Info = DispatchInfo> + GetCallMetadata,
{
    type AccountId = T::AccountId;
    type Call = T::Call;
    type AdditionalSigned = ();
    type Pre = ();
    const IDENTIFIER: &'static str = "AuthorizeOcw";

    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
        Ok(())
    }

    fn validate(
        &self,
        who: &Self::AccountId,
        call: &Self::Call,
        _info: &DispatchInfoOf<Self::Call>,
        _len: usize,
    ) -> TransactionValidity {
        let md = call.get_call_metadata();

        if md.palet_name == "my_pallet" && 
           md.function_name == "add_validator_again" &&
           <ApprovedValidators<T>>::contains_key(who) &&
           !<Validators<T>>::contains_key(who)
        {
            Ok(Default::default())
        } else {
            Err(InvalidTransaction::Call.into())
        }
    }
}

Transactions with calls to add_validator_again that are coming from accounts that don't belong to ApprovedValidators (or that do belong to Validators) will never make it into the Tx Pool.


In regards to the signer.iam() that you alluded to, unfortunately the public APIs exposed by frame_system::offchain::Signer don't really offer anything of that nature.

But with the solution described above, you don't really need it.

Sure, most OCWs will "waste" transactions that never get executed. But that is actually the right security model, since unauthorized actors could also attempt this and they must not be successful.

0

Yes, there is a way. Take a look at how the keys are being retrieved from the keystore in the substrate under the hood in Signer implementation.

fn keystore_accounts(&self) -> impl Iterator<Item = Account<T>> {
    C::RuntimeAppPublic::all().into_iter().enumerate().map(|(index, key)| {
        let generic_public = C::GenericPublic::from(key);
        let public: T::Public = generic_public.into();
        let account_id = public.clone().into_account();
        Account::new(index, account_id, public)
    })
}

In your case it will be something like:

        let collected_account_ids = <T::AuthorityId as AppCrypto<
            T::Public,
            T::Signature,
        >>::RuntimeAppPublic::all()
        .into_iter()
        .filter_map(|key| {
            let generic_public = <T::AuthorityId as AppCrypto<
                T::Public,
                T::Signature,
            >>::GenericPublic::from(key);
            let public_key: T::Public = generic_public.into();
            let account_id = public_key.clone().into_account();
            if <ApprovedValidators<T>>::get().contains(&account_id) {
                Option::Some(account_id)
            } else {
                Option::None
            }
        })
        .collect::<Vec<_>>();

Then, do your logic with collected keys, or just edit the code above to conform your needs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.