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

Static Decoding of Signed Extensions: Simple Approach #1235

Merged

Conversation

tadeohepperle
Copy link
Contributor

Instead of introducing a new trait, like in #1226, this PR just adds an associated type Decoded: DecodeAsType to the SignedExtension trait.

Results should be the same as in #1226.

@tadeohepperle tadeohepperle requested a review from a team as a code owner October 30, 2023 17:59
/// Returns an iterator over each of the signed extension details of the extrinsic.
/// If the decoding of any signed extension fails, an error item is yielded and the iterator stops.
pub fn iter(&self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<'a>, Error>> {
pub fn iter(&'a self) -> impl Iterator<Item = Result<ExtrinsicSignedExtension<'a, T>, Error>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you added this &'a self lifetime back because metadata is no longer referenced, but you should be able to get rid of it (esp since you self.metadata.clone() below still).

If Metadata needs to be owned in ExtrinsicSignedExtensions (even though other bits are borrowed) then you could:

  • let metadata = self.metadata.clone() at the top
  • then don't reference self in the iter::from_fn below.

But since everything else is borrowed, I'd borrow the Metadata in ExtrinsicSignedExtensions and then all of the data has the 'a lifetime and that lifetime flows into the iterated stuff too. (Basically, we should either fully commit to lifetimes or avoid them completely here I reckon, and fully committing seems like the good way to go unless we run into some case where they are a pain)

}))
})
}

fn find_by_name(&self, name: impl AsRef<str>) -> Option<ExtrinsicSignedExtension<'_, T>> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could this take &str instead of impl AsRef<str>?

Comment on lines 729 to 737
/// DecodedValueThunk representing the type of the extra of this signed extension.
fn decoded(&self) -> Result<DecodedValueThunk, Error> {
let decoded_value_thunk = DecodedValueThunk::decode_with_metadata(
&mut &self.bytes[..],
self.ty_id,
&self.metadata,
)?;
Ok(decoded_value_thunk)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like some things, like this and the lifetime stuff on ExtrinsicSignedExtensions were lost again compared to the base PR that these build off; would be good to merge the underlying branch or rebase :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, some stuff was lost in the merge, sorry. But this decoded function I kept on purpose. It is just private now, but used in the public as_type() and value() functions.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In the other PR I'd updated to directly use Value::decode_as_type(..) which should be better than DecodedValueThunk::decode_with_metadata(..), and also I'd remove the value() function and just return a Value from this one (I'd have a look at the other PR because that was good here :))

Comment on lines 232 to 235
/// Tip
pub tip: Compact<u128>,
/// Asset Id
pub asset_id: Option<u32>,
Copy link
Collaborator

@jsdw jsdw Oct 31, 2023

Choose a reason for hiding this comment

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

here and below I think I wouldn't make thesepub and instead expose tip() and asset_id() getters where tip returns u128 and the Compact thing is hidden away (it's an implementation detail) :)

Comment on lines 274 to 287
(self.tip, self.asset_id).encode_to(v);
self.encode_to(v);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Personally I prefer the prev approach and not making the struct Encode (it feels like an implementation detail that's better to not expose). Same as below

pub struct ChargeTransactionPayment {
tip: Compact<u128>,
/// Tip
pub tip: Compact<u128>,
Copy link
Collaborator

@jsdw jsdw Oct 31, 2023

Choose a reason for hiding this comment

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

As above I'd expose a tip() getter that returns u128 to hide the Compact implementation detail, and wouldn't have this be Encode

assert_eq!(tip2, 5678);

assert_eq!(tip2, tip2_static);
Copy link
Collaborator

Choose a reason for hiding this comment

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

it'd be really good I think to also test getting the mortality (just because that returns an enum and would be good to check that it decodes OK! I'm not sure off the top of my head whether the encoded impl is consistent with this or not))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a check at the end of the test to verify Mortality decodes and is Era::Immortal.

Copy link
Collaborator

@jsdw jsdw Nov 2, 2023

Choose a reason for hiding this comment

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

Nice, thank you! That's reassuring :)

@jsdw
Copy link
Collaborator

jsdw commented Oct 31, 2023

Nice! Yeah, I think I prefer this to the other way of doing things; one less trait, and the changes are confined to the SignedExtension logic and don't effect anybody that is using ExtrinsicParams directly for instance.

Also just generally less code and no weird indirection from the new trait to the ExtrinsicParamsEncoder trait :)

Comment on lines 730 to 747
/// DecodedValueThunk representing the type of the extra of this signed extension.
fn decoded(&self) -> Result<DecodedValueThunk, Error> {
let decoded_value_thunk = DecodedValueThunk::decode_with_metadata(
&mut &self.bytes[..],
self.ty_id,
self.metadata,
)?;
Ok(decoded_value_thunk)
}

/// Signed Extension as a [`scale_value::Value`]
pub fn value(&self) -> Result<DecodedValue, Error> {
let value =
DecodedValue::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
self.decoded()?.to_value()
}

Ok(value)
/// Decodes the `extra` bytes of this Signed Extension into a static type.
pub fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
self.decoded()?.as_type::<E>().map_err(Into::into)
Copy link
Collaborator

@jsdw jsdw Nov 2, 2023

Choose a reason for hiding this comment

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

Please revert these changes here to master :)

Comment on lines 735 to 738
pub fn as_type<E: DecodeAsType>(&self) -> Result<E, Error> {
let value = E::decode_as_type(&mut &self.bytes[..], self.ty_id, self.metadata.types())?;
Ok(value)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

So for eg events we have pub fn as_event<E: StaticEvent>(&self) -> Result<Option<E>, Error>, and then for extrinsics we have pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error>.

I'd be inclined to add something here like:

pub fn as_signed_extra<S: SignedExtension>(&self) -> Result<Option<S::Decoded>, Error> {
    // Have sanity check that identifier lines up with `SignedExtension` given and then the 
    // usual decode logic, `Ok(None)` if name doesn't match (like the others).
}

This would be consistent with the above, and also be a little stricter than as_type, only allowing something that looks like the correct signed extension type to even be tried in the first place. I have no real objections to keeping as_type around too, but probably would remove it for now in favour of people defining and using more proper SignedExtension impls here :)

@jsdw
Copy link
Collaborator

jsdw commented Nov 2, 2023

All looks great now; just one comment to change as_type into as_signed_extra and add the extra check that the name lines up (see #1235 (comment)); this will be consistent then with the methods on events and extrinsics, and a little safer than allowing any as_type :)

@@ -669,7 +669,7 @@ impl<'a, T: Config> ExtrinsicSignedExtensions<'a, T> {
/// If the Signed Extension is found, but decoding failed, `Some(Err(err))` is returned.
pub fn find<S: SignedExtension<T>>(&self) -> Option<Result<S::Decoded, Error>> {
Copy link
Collaborator

@jsdw jsdw Nov 2, 2023

Choose a reason for hiding this comment

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

Nit: Maybe this should be Result<Option<..> instead, actually? I'm never sure; just looking at some of the other find methods we have around :)

// But both should start with a compact encoded u128, so this decoding is fine.
let tip = Compact::<u128>::decode(&mut tip.bytes()).ok()?.0;
Some(tip)
// Note: the overhead of iterating twice should be negligible.
Copy link
Collaborator

Choose a reason for hiding this comment

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

We could probably modify the find_by_name function to take in a filter instead of the name, but yea we have a few extras anyway

Copy link
Collaborator

@lexnv lexnv left a comment

Choose a reason for hiding this comment

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

LGTM! Nice one 👍

@tadeohepperle
Copy link
Contributor Author

Since #1227 merged, I am having some trouble implementing scale_decode::IntoVisitor for ChargeAssetTxPayment<T: Config> manually. Just deriving DecodeAsType on ChargeAssetTxPayment does not work anymore because of the Generics, even though I add a DecodeAsType trait bound to the AssetId type of the config.

It tells me, that Option<T::AssetId> also does not implement IntoVisitor and DecodeAsType which seems strange to me, because Option<G> should implement DecodeAsType and so should Option<T::AssetId> should do as well. Need to investigate this more next week.

@jsdw
Copy link
Collaborator

jsdw commented Nov 3, 2023

Just deriving DecodeAsType on ChargeAssetTxPayment does not work anymore because of the Generics

Maybe it's not documented (I didn't check) but I think DecodeAsType derive macro supports a top level trait_bounds attribute which you can set to "" or something to manually tell it that T: Config doesn't need to impl IntoVisitor!

(at least it looks like it supports this based on the scale-decode-derive code!)

Edit: ah here we go; see the bottom example:

https://docs.rs/scale-decode/latest/scale_decode/derive.DecodeAsType.html

lexnv and others added 4 commits November 9, 2023 13:17
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
* Update lightclient

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Fix typo

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Update cargo.toml

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Add tracing logs to improve debugging

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Add socket buffers module for `PlatformRef`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Update `SubxtPlatform`

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cargo: Add lightclient dependencies

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update cargo.lock of wasm tests

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Add constant for with-buffer module

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* lightclient: Replace rand crate with getrandom

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* example: Update cargo lock file

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* examples: Update deps

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
Co-authored-by: Tadeo Hepperle <62739623+tadeohepperle@users.noreply.github.com>
…lt impl (#1227)

* Asset Id in Config trait

* add example configuring the config

* fmt

* fix Default trait bound

* merge examples, fix default again

* adjust config in examples

* Update subxt/src/config/mod.rs

Co-authored-by: James Wilson <james@jsdw.me>

---------

Co-authored-by: James Wilson <james@jsdw.me>
// check that era decodes:
for extensions in [&extensions1, &extensions2] {
let era: Era = extensions
.find::<CheckMortality<SubstrateConfig>>()
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's a shame to have to write the Config bound in like this, but unavoidable as it stands!

(We can probably come up with something to avoid this at some point, but all good for now!)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree.

@tadeohepperle tadeohepperle merged commit 56d0cda into master Nov 10, 2023
10 checks passed
@tadeohepperle tadeohepperle deleted the tadeohepperle-static-decoding-of-signed-extensions-simple branch November 10, 2023 17:32
@jsdw jsdw mentioned this pull request Dec 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants