1
use std::any::Any;
use std::io;
use subxt::*;
use subxt::{ClientBuilder,DefaultConfig, DefaultExtra};
use subxt::codec::Decode;
use crate::sp_runtime::OpaqueExtrinsic;
use sp_runtime::{
    generic::{
        UncheckedExtrinsic
    },
    MultiSignature,
    AnySignature
};

#[subxt::subxt(runtime_metadata_path = "src/polkadot_metadata.scale")]
pub mod polkadot {}

type Address = u64;
type Call = Vec<u8>;
type Signature = AnySignature;
type Extra = DefaultExtra<DefaultConfig>;
type UnEx = UncheckedExtrinsic<Address, Call, Signature, Extra>;

fn main(){
        let mut input1 = "0x280402000be1da78d37e01".as_bytes(); //get from json-rpc (polkadot node!)
        let mut input2 = "280402000be1da78d37e01".as_bytes();
        println!("input1  = {:?}", input1);
        println!("input2  = {:?}", input2);
        let uncheck_extrinsic = UnEx::decode(&mut input2);
        println!("block extrinsics = {:?}", uncheck_extrinsic);  // Got  `Invalid transaction version` error
}

The reason I get input manually is because I use a separate polkadot node.

Now, if input1 or input2 is put as an input value, the following error occurs.

Err(Error { cause: None, desc: "Invalid transaction version" })

What else should I do?

Is this the right way to approach it?

I'm not going to use polkadot-js :)

2
  • 0x280402000be1da78d37e01 seems a legit extrinsic of setting the timestamp. Maybe try getting the latest polkadot_metadata.scale and trying again?
    – Squirrel
    Commented Feb 21, 2022 at 12:47
  • @Squireel There was no point in doing it
    – PaperFrog
    Commented Feb 22, 2022 at 1:33

1 Answer 1

3

For the sake of answering this question (though I have replied to you elsewhere already on this topic); The transaction format is not just a simple SCALE encoding of some details. In rust pseudocode, a transaction will have roughly this format

// Length in bytes of the extrinsic data to follow: 
let byte_len = <Compact<u32>>::decode(input)?;

// decode a byte consisting of the bits 0bABBBBBBB.
// - if A is 1, a signature follows, and if it is 0 there is no signature
// - the bits B decode to the transaction protocol version, currently 4.
let version_and_sig = u8::decode(input)?;
let is_signature = (version_and_sig & b10000000) > 0;

if (is_signature) {
  let from_address = <MultiAddress<AccountId32,u32>>::decode(input)?;
  let signature = MultiSignature::decode(input)?; // Prob an SR25519 signature
  // Extra information is variable and actually depends on the chain;
  // the below example is for polkadot, last I checked (a while ago).
  // - era (ie is transaction mortal or immortal, and how long will it last?
  // - nonce (how many prior transactions from the from_address?
  // - tip (a tip to the block author, which can be 0)
  let (era, nonce, tip): (Era, Compact<u32>, Compact<u128>)::decode(input)?;
}

// Now, we're decoding the Call enum, which looks something like
// `Pallet::Foo(Call::Something(1, true))`. Enum indexes are currently scale
// encoded to u8's. Often we want to get at least the pallet index, so that we
// can look up in the Metadata how to decode the rest.
let pallet_index = u8::decode(input)?;
let call_index = u8::decode(input)?;
let call_params = decode_call_params(metadata, pallet_index, call_index, input)?;

In other words, a transaction begins with its compact encoded length in bytes, and then has a byte which is not SCALE encoded and has 1 bit for "will a signature follow" and 7 bits for "what is the transaction protocol version we're using here".

After manually decoding that byte you can write logic to SCALE decode the signature (if present) or skip straight to SCALE decoding the call data (if signature isn't present).

The call data cannot be decoded into a vector of bytes; conceptually it is more like a SCALE encoded enum whose outer variant is the pallet you're making a call into, and inner variant is the call you're making and any parameters that you need to pass to it. One way to decode the call data is to decode a singel u8 (corresponding to the outer enum variant tag) to see what the pallet index the call is for, and then digging up information about the call enum from the metadata and using that to decode the rest of the call data.

Have a look at the decode_extrinsic implementation for an example of how one might decode an extrinsic using runtime metadata.

Ultimately though, depending on your use case you may prefer to use a library like Subxt, which allows you to build and submit extrinsics, access node storage and subscribe/decode events. There is a bunch of stuff to work out if you want to manually figure out how to encode and decode these things (transactions, events, storage keys and results) yourself, and much of it isn't well documented, so using a library like Subxt will probably get you there much quicker.

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.