4

I've been following the how-to guide to implementing unsigned extrinsic call with a signed payload.

My pallet defines the following KeyTypeId:

pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"ver!");

And the code that uses the local accounts looks like this:

let signer = Signer::<T, T::AuthorityId>::any_account();

if let Some((_, res)) = signer.send_unsigned_transaction(
  // this line is to prepare and return payload
  |acct| {
  // Update payload with status and signature
    ...
  },
  |signed_payload, signature| Call::submit_unsigned {
    signed_payload,
    signature,
  },
) {
  res.map_err(|_| Error::<T>::OffChainVerificationError)
} else {
  // The case of `None`: no account is available for sending
  error!("No local accounts available. Consider adding one via `author_insertKey` RPC.");
  Err(Error::<T>::OffChainVerificationError)
}

At this time, if I run the OCW, I get the following error from the code above:

ERROR      offchain-worker verification::offchain: No local accounts available. Consider adding one via `author_insertKey` RPC.

I read that we can either specify a local account in the service.rs:

  let keystore = keystore_container.sync_keystore();
  if config.offchain_worker.enabled {
    sc_service::build_offchain_workers(&config, task_manager.spawn_handle(), client.clone(), network.clone());
    sp_keystore::SyncCryptoStore::sr25519_generate_new(
      &*keystore,
      sp_runtime::KeyTypeId(*b"ver!"),
      Some("//Alice"),
    ).expect("Creating key with local account Alice should work");
  }

Or either add the account through RPC call:

curl --location --request POST 'http://localhost:9933' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"method": "author_insertKey",
"params": ["ver!","clip organ olive upper oak void inject side suit toilet stick narrow","0xb48004c6e1625282313b07d1c9950935e86894a2e4f21fb1ffee9854d180c781"],
"id": 1
}'

But in both cases the execution of the code inside submit_unsigned extrinsic, that validates the signature:

      // Ensure the payload of the unsigned transaction was signed by a local account and that the
      // signature is valid
      ensure_none(origin)?;

      let signer = Signer::<T, T::AuthorityId>::all_accounts();
      ensure!(
        // panics in the next line
        signer.with_filter(vec![payload.public.clone()]).can_sign(),
        Error::<T>::OffChainVerificationError
      );

      let signature_valid = SignedPayload::<T>::verify::<T::AuthorityId>(&payload, signature);
      ensure!(signature_valid, Error::<T>::OffChainVerificationError);

panics with the following error:

Thread 'tokio-runtime-worker' panicked at 'No `keystore` associated for the current context!', /workspace/.cargo/git/checkouts/substrate-7e08433d4c370a21/e8a7d16/primitives/io/src/lib.rs:832

This is a bug. Please report it at:

So, there is no keystore available at this time. Turns out from the example that there is no need to explicitly ensure that the account that signed the payload is a local account.

Is the SignedPayload::verify() method already validating this fact?

Out of curiosity, what is the reason why the keystore is not available in extrinsic invocation?

2
  • 1
    When do you get this panic? After inserting a key to the keystore? From the offchain worker?
    – bkchr
    Commented Feb 1, 2023 at 13:31
  • @bkchr I actually saw that the code that causes the panic is inside the unsigned extrinsic, where I validate the payload signature Commented Feb 1, 2023 at 14:02

1 Answer 1

1

But in both cases the execution of the code inside submit_unsigned extrinsic, that validates the signature:

     // Ensure the payload of the unsigned transaction was > signed by a local account and that the
     // signature is valid
     ensure_none(origin)?;

     let signer = Signer::<T, T::AuthorityId>::all_accounts();
     ensure!(
       // panics in the next line
       signer.with_filter(vec![payload.public.clone()]).can_sign(),
       Error::<T>::OffChainVerificationError
     );

     let signature_valid = SignedPayload::<T>::verify::> <T::AuthorityId>(&payload, signature);
     ensure!(signature_valid, Error::><T>::OffChainVerificationError);

Your problem is the following line:

let signer = Signer::<T, T::AuthorityId>::all_accounts();

This requires that the keystore is registered for the wasm execution. The keystore is never available on chain as this would otherwise be a source of indeterminism as not all nodes have the same keys in their keystores or none at all. You need to pass the signer as parameter to your unsigned call and then you need to ensure that this signer is part of some on chain set. For example in im-online this on chain list are the current active validators. Depending on your application you probably also want to use the activate validator set.

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.