Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V14 Decoding #45

Merged
merged 55 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a3e7d11
Begin scaffolding out crate for v14 decoding
jsdw Oct 5, 2021
e300f9a
Ongoing work to port metadata over
jsdw Oct 5, 2021
9ed8410
Comment out metadata stuff to start fresh
jsdw Oct 6, 2021
d18eead
test binary to play with metadata stuff
jsdw Oct 6, 2021
f113916
Start working out how metadata will be laid out
jsdw Oct 6, 2021
dfec7d1
Basic decoding of metadata to handle calls
jsdw Oct 7, 2021
5cfbc7d
Probably don't need extrinsic info in metadata, actually
jsdw Oct 7, 2021
6e6b90d
allow type resolution via metadata; we'll need that for decoding
jsdw Oct 7, 2021
7133c32
re-write SubstrateType to be derived from scale_info::Type
jsdw Oct 8, 2021
1ace2ec
Tidy up Metadata bits now that we have new SubstrateType
jsdw Oct 8, 2021
154947c
Rewrite SubstrateValue and get things compiling again
jsdw Oct 8, 2021
6241c72
Remove now-unused code
jsdw Oct 8, 2021
0f98fd4
Cargo fmt and a little tidy up
jsdw Oct 8, 2021
46400f2
Wee tidy up
jsdw Oct 8, 2021
a4cc70f
Update Cargo.lock
jsdw Oct 11, 2021
e38451e
Scaffold out some of the decode logic
jsdw Oct 11, 2021
07b7959
cargo fmt
jsdw Oct 11, 2021
aa097b5
Decode signature (untested) and scaffold out arg decoding methods
jsdw Oct 11, 2021
a9f1f20
Add a comment about mut ptr to slice
jsdw Oct 11, 2021
2c8dd13
Remove substrate_type; we _can_ construct scale_info Types ourselves.…
jsdw Oct 11, 2021
bca25c2
First pass over decoding logic done
jsdw Oct 12, 2021
caea294
Tidy up, and make v14_test bin do something a little more useful
jsdw Oct 12, 2021
1f41c16
Some tidy up and error on decoding if not all bytes consumed
jsdw Oct 12, 2021
51a3aa4
Comment lowercasing
jsdw Oct 12, 2021
4b80064
Add copyright notice on v14 code
jsdw Oct 12, 2021
0f6a566
Add missing copyright header
jsdw Oct 12, 2021
d1c8968
Add a couple of comments in the decode logic
jsdw Oct 12, 2021
1d1daeb
remove some commented-out code
jsdw Oct 12, 2021
a2cb527
Use signed extension metadata for dynamic signed extension decoding, …
jsdw Oct 13, 2021
14d882c
Unit test decoding, and some simplifications and removing a couple of…
jsdw Oct 13, 2021
76ab0a0
Remove lifetime from DecodeError and make sure LateEof is correct w.r…
jsdw Oct 13, 2021
e96deab
Cargo fmt
jsdw Oct 13, 2021
2098c40
differentiate between decoding extrinsics prefixed with length and no…
jsdw Oct 14, 2021
5a0abd0
Tweak a comment
jsdw Oct 14, 2021
8daeb91
Rename SubstrateValue to Value and expose from crate. Add first integ…
jsdw Oct 14, 2021
f989aa1
remove slightly-redundant 'Value' suffix from inner enum types
jsdw Oct 14, 2021
02f7318
Pallet/call indexes don't necessarily align with their positions; nee…
jsdw Oct 14, 2021
2b4e55d
Support compact-encoded wrapper-structs and add a couple more integra…
jsdw Oct 14, 2021
d487d27
More integration tests
jsdw Oct 14, 2021
6a72b06
cargo fmt
jsdw Oct 14, 2021
72d49e3
Add a couple more integration tests
jsdw Oct 14, 2021
9bd6564
A little renaming, and remove unwanted DS_Store
jsdw Oct 15, 2021
b2b87cf
Add docs and fix/test ExtrinsicBytes
jsdw Oct 15, 2021
cd4deb0
More docs
jsdw Oct 15, 2021
5831844
cargo clippy
jsdw Oct 15, 2021
3f10df4
More documenting
jsdw Oct 15, 2021
5f2f917
Remove Sequence value type: It's identical to Composite, and potentia…
jsdw Oct 15, 2021
79b282d
cargo fmt
jsdw Oct 15, 2021
deafb08
more doc tweaks
jsdw Oct 15, 2021
fe608de
Cargo clippy (tabs in doc comments keep coming back..)
jsdw Oct 15, 2021
859145c
add sqlx-data for offline mode
jsdw Oct 15, 2021
4111d6c
Update core_v14/src/decoder/extrinsic_bytes.rs
jsdw Oct 15, 2021
827a56d
newline in gitignore
jsdw Oct 15, 2021
4da194c
Doc comment fixes
jsdw Oct 18, 2021
b0374da
More doc comment fixes
jsdw Oct 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"core",
"core_v14",
"extras",
"integration_tests",
"substrate-metadata-versions/metadatav8",
Expand Down
18 changes: 18 additions & 0 deletions core_v14/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "core_v14"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might not be the best name if this crate could one day be used on its own? desub-metadata-v14?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely; the plan is to merge it with a crap name, and then do a PR to rename soem crates.. probably promoting this in place of the <=V13 logic, and moving that logic to be under something with legacy in the name (we'll need to work out the naming though when we get to that!)

version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
log = "0.4"
thiserror = "1.0.22"
primitives = { package = "sp-core", git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9" }
runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9" }
frame_metadata = { package = "frame-metadata", git = "https://github.com/paritytech/frame-metadata", features = ["v14"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
codec = { version = "2", package = "parity-scale-codec" }
hex = "0.4.3"
pallet_democracy = { package = "pallet-democracy", git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.9" }
43 changes: 43 additions & 0 deletions core_v14/src/decoder/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::metadata::Metadata;
use crate::generic_extrinsic::GenericExtrinsic;
use super::utils::{ scaled_extrinsic_bytes, ExtrinsicBytesError };

pub struct Decoder {
metadata: Metadata
}

#[derive(Clone,Debug,thiserror::Error)]
pub enum DecodeError {
#[error("Failed to parse the provided vector of extrinsics: {0}")]
UnexpectedExtrinsicsShape(#[from] ExtrinsicBytesError)
}

impl Decoder {

/// Create a new decoder using the provided metadata.
pub fn with_metadata<M: Into<Metadata>>(metadata: M) -> Decoder {
Decoder {
metadata: metadata.into()
}
}

/// Decode a SCALE encoded vector of extrinsics against the metadata provided
pub fn decode_extrinsics(&self, data: &[u8]) -> Result<Vec<GenericExtrinsic>, DecodeError> {
let extrinsic_bytes = scaled_extrinsic_bytes(data)?;
log::trace!("Decoding {} Total Extrinsics.", extrinsic_bytes.len());

let mut out = Vec::with_capacity(extrinsic_bytes.len());
for (idx, res) in extrinsic_bytes.iter().enumerate() {
let bytes = res?;
log::trace!("Extrinsic {}:{:?}", idx, bytes);
out.push(self.decode_extrinsic(bytes)?);
}
Ok(out)
}

/// Decode a SCALE encoded extrinsic against the metadata provided
pub fn decode_extrinsic(&self, data: &[u8]) -> Result<GenericExtrinsic, DecodeError> {
todo!()
}

}
4 changes: 4 additions & 0 deletions core_v14/src/decoder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod decoder;
mod utils;

pub use decoder::Decoder;
85 changes: 85 additions & 0 deletions core_v14/src/decoder/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/// Iterate over a SCALE encoded vector of extrinsics and return the bytes associated with each one.
/// Conceptually, the vector of extrinsics is encoded in the form:
///
/// `Vec<(Compact<u32>, Extrinsic)>`
///
/// Where Each extrinsic is prefixed with a compact encoding of its length in bytes. This is because
/// extrinsics are themselves just treated as opaque vectors of btyes when they are encoded.
///
/// On each iteration, we return the extrinsic bytes, or a `usize` denoting the position at
pub fn scaled_extrinsic_bytes(data: &[u8]) -> Result<ExtrinsicBytes, ExtrinsicBytesError> {
let (vec_len, vec_len_bytes) = match decode_compact_u32(data) {
Some(res) => res,
None => return Err(ExtrinsicBytesError { index: 0 })
};

Ok(ExtrinsicBytes {
len: vec_len,
data: &data[vec_len_bytes..]
})
}

/// A structure representing a set of extrinsics in terms of their raw SCALE encoded bytes.
#[derive(Clone, Copy)]
pub struct ExtrinsicBytes<'a> {
len: usize,
data: &'a [u8]
}

impl <'a> ExtrinsicBytes<'a> {
/// How many extrinsics are there?
pub fn len(&self) -> usize {
self.len
}

/// Iterate over the bytes, returning each extrinsic found in the form of its bytes,
/// or an error if we cannot decode the bytes as expected.
pub fn iter(&self) -> ExtrinsicBytesIter<'a> {
ExtrinsicBytesIter { data: &self.data, cursor: 0 }
}
}

/// An iterator that returns the set of bytes representing each extrinsic found.
pub struct ExtrinsicBytesIter<'a> {
data: &'a [u8],
cursor: usize
}

impl <'a> Iterator for ExtrinsicBytesIter<'a> {
type Item = Result<&'a [u8], ExtrinsicBytesError>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None
}

let (vec_len, vec_len_bytes) = match decode_compact_u32(&self.data) {
Some(res) => res,
None => return Some(Err(ExtrinsicBytesError { index: self.cursor }))
};
log::trace!("Length {}, Prefix: {}", vec_len, vec_len_bytes);

let res = &self.data[(self.cursor + vec_len_bytes) .. (self.cursor + vec_len + vec_len_bytes)];
self.cursor += vec_len + vec_len_bytes;
Some(Ok(res))
}
}

#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("Expected a compact encoded u32 at byte index {index}, but did not find one")]
pub struct ExtrinsicBytesError {
pub index: usize
}

/// Given a SCALE encoded `Compact<u32>` (which prefixes a SCALE encoded vector, for instance),
/// return a tuple of the length of the vector, and the number of input bytes used to represent
/// this length.
fn decode_compact_u32(mut data: &[u8]) -> Option<(usize, usize)> {
use codec::{Compact, CompactLen, Decode};
use std::convert::TryFrom;

// alternative to `DecodeLength` trait, to avoid casting from a trait
let length = u32::from(Compact::<u32>::decode(&mut data).ok()?);
let prefix = Compact::<u32>::compact_len(&length);
let length = usize::try_from(length).ok()?;
Some((length, prefix))
}
207 changes: 207 additions & 0 deletions core_v14/src/generic_extrinsic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
jsdw marked this conversation as resolved.
Show resolved Hide resolved
// This file is part of substrate-desub.
//
// substrate-desub is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. //
// substrate-desub is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with substrate-desub. If not, see <http://www.gnu.org/licenses/>.

//! Generic Extrinsic Type and Functions

use crate::substrate_value::SubstrateValue;
use serde::Serialize;
use std::fmt;

#[derive(Debug, Serialize)]
pub struct ExtrinsicArgument {
pub name: String,
pub arg: SubstrateValue,
}

impl fmt::Display for ExtrinsicArgument {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, " arg: {}, Ty: {} ", self.name, self.arg)
}
}

#[derive(Debug, Serialize)]
pub struct GenericCall {
name: String,
module: String,
args: Vec<ExtrinsicArgument>,
}

impl fmt::Display for GenericCall {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::from("");
s.push_str(&self.name);
s.push_str(": ");
for val in self.args.iter() {
s.push_str(&format!("{}", val));
}
write!(f, "{}", s)
}
}

/// Generic Extrinsic Type
#[derive(Debug, Serialize)]
pub struct GenericExtrinsic {
signature: Option<GenericSignature>,
call: GenericCall,
}

impl fmt::Display for GenericExtrinsic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::from("");
if let Some(v) = &self.signature {
s.push_str(&format!("{}", v));
} else {
s.push_str(&"None".to_string());
}
s.push('\n');
s.push_str("CALL");
s.push('\n');
s.push_str(&format!("{}", self.call));
write!(f, "{}", s)
}
}

impl GenericExtrinsic {
/// create a new generic extrinsic type
pub fn new(sig: Option<SubstrateValue>, call: Vec<(String, SubstrateValue)>, name: String, module: String) -> Self {
let call =
call.into_iter().map(|c| ExtrinsicArgument { name: c.0, arg: c.1 }).collect::<Vec<ExtrinsicArgument>>();
let call = GenericCall { name, module, args: call };
Self { signature: sig.map(GenericSignature::new), call }
}

pub fn is_signed(&self) -> bool {
self.signature.is_some()
}

pub fn signature(&self) -> Option<&GenericSignature> {
self.signature.as_ref()
}

pub fn call(&self) -> &GenericCall {
&self.call
}

pub fn ext_module(&self) -> &str {
&self.call.module
}

pub fn ext_call(&self) -> &str {
&self.call.name
}

pub fn args(&self) -> &[ExtrinsicArgument] {
&self.call.args
}
}

#[derive(Debug, Serialize)]
pub struct GenericSignature {
#[serde(serialize_with = "crate::util::as_substrate_address")]
address: SubstrateValue,
signature: SubstrateValue,
extra: SubstrateValue,
}

impl GenericSignature {
pub fn new(signature: SubstrateValue) -> Self {
Self::split(signature)
}

/// returns address signature and extra as a tuple
pub fn parts(&self) -> (&SubstrateValue, &SubstrateValue, &SubstrateValue) {
(&self.address, &self.signature, &self.extra)
}

fn split(sig: SubstrateValue) -> Self {
match sig {
SubstrateValue::Composite(mut v) => {
v.reverse();
Self {
address: v.pop().expect("Address must must be present in signature"),
signature: v.pop().expect("Signature must be present"),
extra: v.pop().expect("Extra must be present"),
}
}
_ => panic!("Signature should always be a tuple of Address, Signature, Extra"),
}
}
}

impl fmt::Display for GenericSignature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Address {}\n Signature {}\n SignedExtra {}\n", self.address, self.signature, self.extra)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn should_serialize_generic_extrinsic() {
let call = GenericCall {
name: "set".to_string(),
module: "Timestamp".to_string(),
args: vec![ExtrinsicArgument { name: "Some Arg".to_string(), arg: SubstrateValue::U32(32) }],
};
let ext = GenericExtrinsic {
signature: Some(GenericSignature::new(SubstrateValue::Composite(vec![
SubstrateValue::Composite(vec![
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
0u8.into(),
]),
SubstrateValue::U64(64),
SubstrateValue::U128(128),
]))),
call,
};
let serialized = serde_json::to_string(&ext).unwrap();
assert_eq!(
serialized,
r#"{"signature":{"address":"5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM","signature":64,"extra":128},"call":{"name":"set","module":"Timestamp","args":[{"name":"Some Arg","arg":32}]}}"#
);
}
}
Loading