10

Substrate introduces some unique concepts such as unsigned transactions and signed extensions. What are the steps for encoding an extrinsic in Substrate? What are the specific details that relate to encoding signed transactions for chains that may use one or more signed extensions?

0

2 Answers 2

9

More generally, if you were to SCALE encode a signed transaction, you'd follow these steps:

  1. Combine the call, extra and additional data and SCALE encode it:
    • call data: the pallet index, call index and other fields relevant to this call (e.g. if this is a transfer call from the Balances pallet, this would be address and balance).
    • extra data (signed extensions): e.g. era, nonce and some tip value.
    • additional data: the spec version, transaction version and genesis hash of the target chain.
  2. If the resulting payload is more than 256 bytes, hash it then sign the result (e.g. using Alice). If not, just sign the SCALE encoded bytes from (1).
  3. Format the signature by combining the sender's account ID + the signature from (2) + the extra data.
  4. Encode the formatted signature.
  5. Encode the call itself.
  6. Construct the extrinsic by creating an array of bytes whereby:
    • The first byte contains the first bit as 1 (denoting that the transaction is signed) and 7 bits containing the transaction version (currently 4 for all Substrate based chains).
    • The remaining bytes contain the result from (4) and (5) (in that order).
  7. Prefix the encoded data from (6) with its compact encoded length.

Have a look at this example to see these steps in action. If you were to encode an unsigned transaction, you'd follow the same procedure except:

  1. The first bit of the first byte set in (6) is 0 (the bit following the compact length added in (7)), denoting that the transaction is unsigned.
  2. There are no signature bytes following this first byte encoded in (6); we append the call data from (5) next.

For encoding an extrinsic with different signed extensions, you'll need to know what they are and add them to the extra data you SCALE encode.


A couple clarifications on key terms:

  • An unsigned transaction in Substrate is just a type of transaction which instead of requiring a signature, requires a set of conditions to be met in order for the transaction to be validated. This implies that developers must carefully implement their own protection mechanisms and economic deterrents against bad actors.

  • A signed extension is a way to extend the information packed into a transaction (signed or unsigned) so that the transaction pool can perform certain validation checks on it. Examples of these checks include: what's the weight for this transaction and will it fit in the block? Has this transaction already expired? Is the nonce for the same account unique? Is there a tip attached to this transaction? See the list of signed extensions in FRAME here.

2

As most Substrate builders are Rust natives, the best examples of extrinsic formation and submission can be found in subxt.

For your specific example, sp_runtime::traits::SignedExtension can be implemented in a manner such as in subxt/src/extrinsic/extra.rs where SignedExtra trait is implimented for various types like DefaultExtraWithTxPayment.

The encoding is implemented in subxt/src/lib.rs quite simply using the codec Encode/Decode traits implemented on things like Call set here:

/// Call trait.
pub trait Call: Encode {
    /// Pallet name.
    const PALLET: &'static str;
    /// Function name.
    const FUNCTION: &'static str;

    /// Returns true if the given pallet and function names match this call.
    fn is_call(pallet: &str, function: &str) -> bool {
        Self::PALLET == pallet && Self::FUNCTION == function
    }
}

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.