-
Notifications
You must be signed in to change notification settings - Fork 255
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
Support decoding signed extensions #1209
Support decoding signed extensions #1209
Conversation
subxt/src/blocks/extrinsic_types.rs
Outdated
/// # Note | ||
/// | ||
/// Returns `None` if the extrinsic is not signed. | ||
pub fn extra_bytes(&self) -> Option<&[u8]> { |
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.
dq: does this include just extra
or extra | additional_signed
?
See https://docs.rs/sp-runtime/latest/src/sp_runtime/generic/unchecked_extrinsic.rs.html#197 for context
Regardless, I think it would be good to document that
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.
Also, since we have signed_extensions
below to get the signed extensions, let's call this signed_extensions_bytes
so it's clear it's getting the bytes for the same thing.
subxt/src/blocks/extrinsic_types.rs
Outdated
/// The tip of an extrinsic, extracted from the ChargeTransactionPayment signed extension. | ||
pub fn tip(&self) -> Option<u128> { | ||
let tip = self | ||
.find("ChargeTransactionPayment") |
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.
Would be cool have these in the metadata, I'm not sure whether some chains overrides the transaction_payment pallet and add their own impl probably not.
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.
Yeah; the idea of these "quick" helpers is that they work in the overwhelmingly common case, but may not always work (and should fail gracefully eg returning None
here if the extension isn't available)
} | ||
|
||
/// The nonce of the account that submitted the extrinsic, extracted from the CheckNonce signed extension. | ||
pub fn nonce(&self) -> Option<u64> { |
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.
This may fail on certain runtimes if the CheckNonce
isn't part of the SignedExtra.
Might worth a comment for it and advise to use the dynamic API then.
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.
Should be nice to have for the general usecase...
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.
Yeah, it's just for a common case that will most likely work, and should fail gracefully and return None
if the relevant extension isn't found :)
@@ -227,3 +229,61 @@ async fn fetch_block_and_decode_extrinsic_details() { | |||
assert_eq!(ext.value, 10_000); | |||
assert!(tx.is_signed()); | |||
} | |||
|
|||
#[tokio::test] | |||
async fn decode_signed_extensions_from_blocks() { |
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.
Would be cool to test with different runtimes so both ChargeAssetTxPayment
and ChargeTransactionPayment
works but probably annoying to support here.
Could be done in another PR
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.
Yeah; I pondered this too but not sure how I'd test this?! It'd be a pain introducing the ability to spawn up a different node just for this
subxt/src/blocks/extrinsic_types.rs
Outdated
if index == num_signed_extensions { | ||
None | ||
} else { |
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.
Nit; perhaps avoid the indentation in the else
by doing return None
here instead; an early exit when done :)
subxt/src/blocks/extrinsic_types.rs
Outdated
|
||
impl<'a> ExtrinsicSignedExtensions<'a> { | ||
/// Get a certain signed extension by its name. | ||
pub fn find(&self, signed_extension: impl AsRef<str>) -> Option<ExtrinsicSignedExtension> { |
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.
To be consistent with how Extrinsics/Events are decoded, I think find
should look more like:
fn find<S: SignedExtension>() -> Result<Option<S>, Error>
For "dynamically finding things", one can .iter().find(..)
anyway.
I'd suggest just removing this for now until we have a nice way to find and decode statically based on the subxt::tx::signed_extensions::*
types (which should def be a separate PR).
subxt/src/blocks/extrinsic_types.rs
Outdated
} | ||
|
||
impl<'a> ExtrinsicSignedExtension<'a> { | ||
/// The extra bytes associated with the signed extension. |
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.
/// The extra bytes associated with the signed extension. | |
/// The bytes representing this signed extension. |
subxt/src/blocks/extrinsic_types.rs
Outdated
} | ||
|
||
/// DecodedValueThunk representing the type of the extra of this signed extension. | ||
pub fn decoded(&self) -> Result<DecodedValueThunk, Error> { |
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.
Let's remove this decoded
fn; it doesn't add much on top of value()
below imo :)
The value
fn can do a more "direct" decoding into a Value type them too; something like Value::decode_as_type(...)
assert_eq!(nonce1, 0); | ||
assert_eq!(tip1, 1234); | ||
assert_eq!(nonce2, 1); | ||
assert_eq!(tip2, 5678); |
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'd like us to also test that iterating over signed extensions works; right now we have no way to really know if it's actually decoding them as we expect :)
The set of signed extensions on a node is fairly fixed, so we can just sanity test that each of the expected ones is decoded and that the names are right or something like that (in addition to the above).
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 added code for testing this to the end of the test :)
Looking good; I resolved all of the comments from myself that you've addressed so just worth looking over the others; nothing major from me now! Will be lovely to have this! On testing: one way to unit test this would be crafting some custom metadata with whatever signed extensions we want, and then creating an This way, we could test a few different combinations of signed extensions (eg if Nonce/Tip ones don't exist, or if both the tip ones exist etc). But yes, I don't mind this being a follow-up PR because it'll take a little work to do nicely! |
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.
Great job, looks good to me!
I wondered why missing docs on a function weren't being picked up in CI, and noticed that the new structs weren't exposed in the module, so I did that and extended a few lifetimes while in the area :)
let alice = dev::alice(); | ||
let bob = dev::bob(); | ||
|
||
macro_rules! submit_transfer_extrinsic_and_get_it_back { |
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.
dq: What are the benefits of this macro over a plain function? Looks good otherwise!
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.
There were just some really stupid lifetime issues when using a Closure.
And I cannot use a normal function easily because then I need to specify all types.
And the type of api
here depends on the unstable-light-client
feature flag. And the type of api
would be a generic type of the return value of that function as well. So I would have to provide two implementations of such a function depending on the feature flag. Also I cannot put the api a trait object easily, because OnlineClientT is not object safe. So in the end I just went with this macro.
LGTM! What do you think about extending the blocks_subscribing to also decode the extensions? 🤔 |
I don't think that this is a blocker for merging, but would be nice to show this stuff in an example somewhere! |
subxt/src/blocks/extrinsic_types.rs
Outdated
|
||
/// Return only the bytes of the signature for this signed extrinsic. | ||
/// | ||
/// # Note |
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.
This note can be removed and change the documentation to something like:
Returns Some(signature_bytes) if the extrinsic was signed otherwise
None
is returned.
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.
nice
subxt/examples/blocks_subscribing.rs
Outdated
if let Some(signed_extensions) = ext.signed_extensions() { | ||
println!(" Signed Extensions:"); | ||
for signed_extension in signed_extensions.iter() { | ||
let signed_extension = signed_extension?; | ||
let name = signed_extension.name(); | ||
let value = signed_extension.value()?.to_string(); | ||
println!(" {name}: {value}"); | ||
} | ||
} else { | ||
println!(" No Signed Extensions"); | ||
} |
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 because this is an example, I wonder whether we could make this a bit less ugly, ie removing the else
branch and moving it after the other println
's or something?
Fixes #1124
This PR adds an option to dynamically decode signed extensions from extrinsics.
Until now we just skipped over any signed extensions (Signed Extra bytes).
There are two convenience functions for
tip()
andnonce()
. When you have ExtrinsicDetails you can not do the following: