Closed jsdw closed 2 years ago
@jsdw many thanks for this example. It really helped me to better understand how extrinsics work and rewrite my code which initially was based on this guide. I got some errors using subxt::PairSigner;
and used sp_runtime::MultiSignature
to sign call data. Unfortunately, I still get a "Transaction has a bad signature" error. Any thoughts on what I'm doing wrong? Thanks!
let from = AccountKeyring::Alice.to_account_id();
let alice_nonce = get_nonce(&from).await;
let runtime_version = get_runtime_version().await.unwrap();
let genesis_hash = get_genesis_hash().await;
let pallet_index: u8 = 8;
let method_index: u8 = 0;
let call_index: [u8; 2] = [pallet_index, method_index];
// extrinsic parameter
let address: Vec<u8> = "4d4c14c40d1b7ecb942455794693fa68".as_bytes().to_vec().encode();
// extrinsic parameter
let owner: [u8; 1] = [0];
let call: Vec<u8> = [call_index.to_vec(), address, owner.to_vec()].concat();
let extra = (
Era::Immortal,
Compact(alice_nonce),
Compact(0u128)
);
let additional = (
runtime_version.spec_version,
runtime_version.transaction_version,
genesis_hash,
genesis_hash,
);
let payload_to_be_signed: Vec<u8> = {
let mut encoded = call.clone();
extra.encode_to(&mut encoded);
additional.encode_to(&mut encoded);
encoded
};
let signature= {
if payload_to_be_signed.len() > 256 {
AccountKeyring::Alice.sign(&sp_core::blake2_256(&payload_to_be_signed)[..])
} else {
AccountKeyring::Alice.sign(&payload_to_be_signed)
}
};
let extrinsic = {
let mut encoded_inner = Vec::new();
(0b1000_0000 + 4u8).encode_to(&mut encoded_inner);
MultiAddress::Id::<_, u32>(from).encode_to(&mut encoded_inner);
MultiSignature::Sr25519(signature).encode_to(&mut encoded_inner);
extra.encode_to(&mut encoded_inner);
encoded_inner.extend(&call);
let len = Compact(encoded_inner.len() as u32);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(&encoded_inner);
encoded
};
let extrinsic_hex = format!("0x{}", hex::encode(&extrinsic));
println!("RESULT HEX {:?}", extrinsic_hex);
@amrbz My example code above was very quickly scrabbled together and was probably a little off; have a look at https://github.com/paritytech/subxt/blob/6012d2a75e3ea50209f9f5ab9638e6ee9e91a900/subxt/src/client.rs#L283 for the "final" version which has been tested to work.
Offhand what you've done looks good for a current Polkadot node though, so you may need to dig a little deeper. But, for a substrate node (ie substrate --dev --tmp
) the tip payment (the Compact(0u128)
in your extra
) needs replacing with (Compact(0u128), None as Option<u32>)
where the option can represent the ID of the asset that you'll be giving as a tip (None
meaning.. pay using the native node currency).
@jsdw Thanks. May I ask on the error with subxt::PairSigner
. I use the code from the example
use subxt::PairSigner;
let signer = PairSigner::new(AccountKeyring::Alice.pair());
But I got this error:
❯ cargo run rpc-substrate -> main ?
Compiling rpc-substrate v0.1.0 (/Users/amrbz/Dev/rust/rpc-substrate)
error[E0284]: type annotations needed for `PairSigner<T, E, sp_core::sr25519::Pair>`
--> src/main.rs:363:18
|
363 | let signer = PairSigner::new(AccountKeyring::Alice.pair());
| ------ ^^^^^^^^^^^^^^^ cannot infer type for type parameter `T`
| |
| consider giving `signer` the explicit type `PairSigner<T, E, _>`, where the type parameter `T` is specified
|
= note: cannot satisfy `<_ as Config>::Signature == _`
For more information about this error, try `rustc --explain E0284`.
error: could not compile `rpc-substrate` due to previous error
So I can't try out subxt
lib for signing data. Maybe that would help.
T
is the config type that's used. If you actually pass that signer
into a subxt function like sign_and_submit_default()
(eg like here) Rust will be able to infer the correct type for you.
In isolation, you'll need to tell it manually what typw T
is; the default is subxt::DefaultConfig
(which works fine with Substrate/Polkadot-like chains), so something like this probably will suffice:
let signer = PairSigner::<subxt::DefaultConfig, _, _>::new(AccountKeyring::Alice.pair());
@jsdw I've figured out how to generate an extrinsic offline. The working code is available here. Thank you very much for your help!
Closed by #490
Some goals I have in mind here:
substrate
types.How:
client.create_signed
or perhapsextrinsic::create_signed
), so it's easy to see what's going on. Right now you have to dig through various traits and such to piece together how an extrinsic is encoded.create_signed
at the mo. Perhaps allow thenonce()
to be optionally pulled from these too (in the future, it might be that we can opt to pull other params from here to override the defaults).Signer
trait if useful/needed (we just want to be able to obtain the account ID and sign the relevant bytes, I think).SignedExtension
andUncheckedExtrinsic
; I don't think we need them if we handle encoding ourselves.I thought about codegen to create the extra/additional types, and it may help a little going forwards, but for now I couldn't see a compelling enough win by doing this (I like the current approach of passing useful details from the client like nonce etc in when constructing them, and not sure how to reconcile this with codegen). Instead, I want to make it as easy as possible to create and use custom extra/additional params for your chain.
These thoughts expressed in code:
As much as anything, the code above serves as a reference to myself or anybody else who'd like to implement this as a starting point to work from!
Anyway, Any thoughts and such would be appreciated! :)
Supercedes #390 and #187.
Closed by #490.