Skip to content

Commit

Permalink
Support multiple extension versions in V5 extrinsics (#4)
Browse files Browse the repository at this point in the history
* Support multiple extension versions in V5 extrinsics

* No need to check version too when General

* Disallow non-zero extension versions for pre-V16 metadatas

* Bump to 0.4.0

* Address PR comments
  • Loading branch information
jsdw authored Oct 22, 2024
1 parent ec4e175 commit 01bc0f6
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 88 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ The format is based on [Keep a Changelog].

[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/

## 0.4.0 (2024-10-21)

- Split `ExtrinsicTypeInfo` trait to get signature and extensions info separately, and support being given an extension version in the latter.
- Remove support for V5 signed extrinsics, which are no longer a thing (see [#3685](https://github.com/paritytech/polkadot-sdk/pull/3685) for context).

## 0.3.0 (2024-09-30)

- Fix `extrinsic.call_range()` and `extensions.range()` functions, and clarify descriptions.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "frame-decode"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
description = "Decode extrinsics and storage from Substrate based chains"
license = "Apache-2.0"
Expand Down
75 changes: 45 additions & 30 deletions src/decoding/extrinsic_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ pub enum ExtrinsicDecodeError {
},
NotEnoughBytes,
VersionNotSupported(u8),
VersionTypeNotSupported(u8),
ExtrinsicTypeNotSupported {
version: u8,
extrinsic_type: u8,
},
CannotGetInfo(ExtrinsicInfoError<'static>),
CannotDecodeSignature(DecodeErrorTrace),
CannotDecodePalletIndex(parity_scale_codec::Error),
Expand Down Expand Up @@ -72,10 +75,13 @@ impl core::fmt::Display for ExtrinsicDecodeError {
"This extrinsic version ({extrinsic_version}) is not supported."
)
}
ExtrinsicDecodeError::VersionTypeNotSupported(version_ty) => {
ExtrinsicDecodeError::ExtrinsicTypeNotSupported {
version,
extrinsic_type,
} => {
write!(
f,
"This extrinsic version type ({version_ty}) is not supported; only Bare, Signed and General types are supported."
"The extrinsic type 0b{extrinsic_type:02b} is not supported (given extrinsic version {version})."
)
}
ExtrinsicDecodeError::CannotGetInfo(extrinsic_info_error) => {
Expand Down Expand Up @@ -554,23 +560,31 @@ where
return Err(ExtrinsicDecodeError::VersionNotSupported(version));
}

// We know about the following types of extrinsic.
// We know about the following types of extrinsics:
// - "Bare": no signature or extensions. V4 inherents encode the same as V5 bare.
// - "Signed": an address, signature and extensions. Only exists in V4.
// - "General": no signature, just extensions (one of which can include sig). Only exists in V5.
let version_ty = match version_type {
0b00 => ExtrinsicType::Bare,
0b10 => ExtrinsicType::Signed,
0b01 => ExtrinsicType::General,
_ => return Err(ExtrinsicDecodeError::VersionTypeNotSupported(version_type)),
0b10 if version == 4 => ExtrinsicType::Signed,
0b01 if version == 5 => ExtrinsicType::General,
_ => {
return Err(ExtrinsicDecodeError::ExtrinsicTypeNotSupported {
version,
extrinsic_type: version_type,
})
}
};

let curr_idx = |cursor: &mut &[u8]| (bytes.len() - cursor.len()) as u32;

let signature_info = info
.get_signature_info()
.map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;

// Signature part. Present for V4 or V5 signed extrinsics
// Signature part. Present for V4 signed extrinsics
let signature = (version_ty == ExtrinsicType::Signed)
.then(|| {
let signature_info = info
.get_signature_info()
.map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;

let address_start_idx = curr_idx(cursor);
decode_with_error_tracing(
cursor,
Expand Down Expand Up @@ -600,19 +614,20 @@ where
})
.transpose()?;

// Transaction extensions part. Present for Signed or General extrinsics.
// "General" extensions now have a single byte representing the extension version.
let extension_version = (version_ty == ExtrinsicType::General)
.then(|| u8::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodeExtensionsVersion))
.transpose()?;

// Signed and General extrinsics both now have a set of transaction extensions.
let extensions = (version_ty == ExtrinsicType::General || version_ty == ExtrinsicType::Signed)
.then(|| {
let transaction_extensions_version = if version_ty == ExtrinsicType::General
|| version == 5
{
u8::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodeExtensionsVersion)?
} else {
0
};
let extension_info = info
.get_extension_info(extension_version)
.map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;

let mut transaction_extensions = vec![];
for ext in signature_info.transaction_extension_ids {
for ext in extension_info.extension_ids {
let start_idx = curr_idx(cursor);
decode_with_error_tracing(
cursor,
Expand All @@ -634,24 +649,24 @@ where
}

Ok::<_, ExtrinsicDecodeError>(ExtrinsicExtensions {
transaction_extensions_version,
transaction_extensions_version: extension_version.unwrap_or(0),
transaction_extensions,
})
})
.transpose()?;

// Call data part
// All extrinsics now have the encoded call data.
let pallet_index_idx = curr_idx(cursor);
let pallet_index: u8 =
Decode::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodePalletIndex)?;
let call_index: u8 =
Decode::decode(cursor).map_err(ExtrinsicDecodeError::CannotDecodeCallIndex)?;
let extrinsic_info = info
.get_extrinsic_info(pallet_index, call_index)
let call_info = info
.get_call_info(pallet_index, call_index)
.map_err(|e| ExtrinsicDecodeError::CannotGetInfo(e.into_owned()))?;

let mut call_data = vec![];
for arg in extrinsic_info.args {
for arg in call_info.args {
let start_idx = curr_idx(cursor);
decode_with_error_tracing(
cursor,
Expand All @@ -660,8 +675,8 @@ where
scale_decode::visitor::IgnoreVisitor::new(),
)
.map_err(|e| ExtrinsicDecodeError::CannotDecodeCallData {
pallet_name: extrinsic_info.pallet_name.to_string(),
call_name: extrinsic_info.call_name.to_string(),
pallet_name: call_info.pallet_name.to_string(),
call_name: call_info.call_name.to_string(),
argument_name: arg.name.to_string(),
reason: e,
})?;
Expand All @@ -684,10 +699,10 @@ where
byte_len: bytes.len() as u32,
signature,
extensions,
pallet_name: extrinsic_info.pallet_name,
pallet_name: call_info.pallet_name,
pallet_index,
pallet_index_idx,
call_name: extrinsic_info.call_name,
call_name: call_info.call_name,
call_index,
call_data,
};
Expand Down
Loading

0 comments on commit 01bc0f6

Please sign in to comment.