-
Notifications
You must be signed in to change notification settings - Fork 14
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
Parse signed payload and expose call data type information #66
Conversation
@Noch3 I'd be keen on gathering your feedback on this one, since it builds on your previous PRs and hopefully exposes the pieces of information you wanted :) |
Wow thank you so much! I'll take a look today! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether this PR providing a separate tree of type information that can be aligned to the |
Yes the design space there is large so it makes sense to solve that separately. |
pub fn args(&self) -> &[TypeId] { | ||
&self.args | ||
} | ||
struct MetadataCalls { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
unnamed_value(vec![]) | ||
} | ||
|
||
fn singleton_value(x: Value) -> Value { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the idea behind storing prim_u<int>_value()
in a singleton for the tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
had the same question!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A singleton here is just a Composite value with only a single member. Some of the metadata types decode into such a thing (eg newtype wrapper structs), and so we need to do the same wrapping when comparing the Value
we get back.
@@ -21,3 +21,6 @@ pub mod value; | |||
pub use decoder::Decoder; | |||
pub use metadata::Metadata; | |||
pub use value::Value; | |||
|
|||
/// A re-export of the [`scale_info`] crate, since we delegate much of the type inspection to it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does re-export
mean, as in where else is this getting exported? Trying to get more familiar with whats going on here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's common for libraries that manipulate data expressed in types stemming from another crate (like here, much of what is going on in desub
relies on scale-info
types) to "adopt" or "forward" that other crate's types as if they were its own.
One instance of this is how the rust standard library re-exports types and code modules from the core::
namespace as std::
. The core::
set of types and modules do not require a std
environment but that's not very important to std
-using applications, so for convenience the core::*
modules are also available through std::*
.
In this case James is re-exporting all of scale-info
, allowing users to access them through the desub
path, e.g. the use desub::TypeInfo
, rather than use scale_info::TypeInfo
. This is convenient and perfectly fine because it is not a feature of desub
to have "swappable" scale type info backends.
Clear as mud? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Thank you! Nit: they'd have access via desub::scale_info::TypeInfo
as it stands. The main benefit just being that you don't need to also add/keep-up-to-date scale_info
to yoru Cargo.toml; you can just access the version used by desub)
calls: HashMap<u8, MetadataCall>, | ||
/// Metadata may not contain call information. If it does, | ||
/// it'll be here. | ||
calls: Option<MetadataCalls>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be a silly dumb question, but do you know any cases off the top of your head where this would unwrap
to None?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just guessing here, but maybe something like the shell
parachain could have zero calls? (Well, hmm, that one has exactly one call right?). It'd be something pretty exotic, but unless it's illegal to have a pallet with zero calls we need something here that can represent such a thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure either, but since the frame-metadata
type allows for None I thought that instead of erroring immediately, I'd allow for the possibility and, if needed, handle errors closer to the calls that would be broken by it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm. I really like the inline docs as it allowed me to follow along pretty seamlessly.
@Noch3 Thanks for bringing this to light!
core_v14/src/decoder/mod.rs
Outdated
@@ -127,17 +129,14 @@ pub enum DecodeError { | |||
DecodeTypeError(#[from] DecodeTypeError), | |||
#[error("Failed to decode: expected more data")] | |||
EarlyEof(&'static str), | |||
#[error("Failed to decode extrinsics: expected {0} more bytes to be consumed from the input")] | |||
DidntConsumeBytes(usize), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UndrainedBytes
maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had trouble with this one! Maybe BytesNotConsumed
or ExcessBytes
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like ExcessBytes
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExcessBytes
it is!
/// of the extrinsic we think we decoded, and the number of bytes left in the slice provided (which we | ||
/// expected to entirely consume, but did not). | ||
#[error("Decoding an extrinsic should consume all bytes, but {0} bytes remain")] | ||
LateEof(usize, Extrinsic), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good riddance! That "late end of file" really messed with my brain. :)
core_v14/src/metadata/version_14.rs
Outdated
for (idx, variant) in calls_variant.variants().iter().enumerate() { | ||
call_variant_indexes.insert(variant.index(), idx); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we use FromIterator
here somehow, dunno, call_variant_indexes = calls_variant.variants().iter().enumerate().collect()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
random thought: perhaps call_variant_indexes
should be an IndexMap
? I guess these collections are small enough that it doesn't matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yup we can now collect straight into a HashMap
; good call!
I looked at IndexMap briefly; it's all about preserving insertion order by the sounds of it. What was your thinking around using it?
(since the index is a u8 I could see that a [usize; 256]
or something could be used if we wanted)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was your thinking around using it?
It was a random thought, something like this: given the purpose of this hashmap is to map from one index to another index, we could sort of use insertion order (idx
here) instead of a hashed key. So IndexMap's IndexSet
type. Or, as you suggest, a fixed sized array.
I don't think it's a good optimization to make at this point though so, "no action required"!
unnamed_value(vec![]) | ||
} | ||
|
||
fn singleton_value(x: Value) -> Value { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
had the same question!
Co-authored-by: David <[email protected]> Co-authored-by: Tarik Gul <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
This PR builds on @Noch3's work to add decoding for the signer payload. It also exposes the Variant type in the call data, so that you can access the name and details about each field, and it exposes scale_info more directly, and a means of getting hold of the type registry from our metadata.
Noch3's PR exposed the cursor approach (using input like
&mut &[u8]
); I felt like this made it easier to decide how to handle remaining bytes, and decode bits from a stream, so I consistified the rest of the decode API on this approach too.I'm also experimenting here with returning references wherever possible when decoding to save some allocations when they may not be needed.
A couple of thoughts/questions: