-
Notifications
You must be signed in to change notification settings - Fork 258
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
runtime API: Substitute UncheckedExtrinsic
with custom encoding
#1076
Changes from 4 commits
2ad017f
2468751
8a81d75
7dad649
13151c7
f5724e4
dec4577
06b2d53
c87b85a
3287d9a
3447a26
2d9a374
9345d0a
d33104a
9c07895
55a3e99
ac30a50
c39f90b
ff88a85
43c1ff3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// Copyright 2019-2023 Parity Technologies (UK) Ltd. | ||
// This file is dual-licensed as Apache-2.0 or GPL-3.0. | ||
// see LICENSE for license details. | ||
|
||
//! The "default" Substrate/Polkadot UncheckedExtrinsic. | ||
//! This is used in codegen for runtime API calls. | ||
//! | ||
//! The inner bytes represent the encoded extrinsic expected by the | ||
//! runtime APIs. Deriving `EncodeAsType` would lead to the inner | ||
//! bytes to be re-encoded (length prefixed). | ||
|
||
use std::marker::PhantomData; | ||
|
||
use codec::Decode; | ||
use scale_decode::{visitor::DecodeAsTypeResult, IntoVisitor, Visitor}; | ||
|
||
/// The unchecked extrinsic from substrate. | ||
#[derive(Clone, Debug, Eq, PartialEq, Decode)] | ||
pub struct UncheckedExtrinsic<Address, Call, Signature, Extra>( | ||
pub Vec<u8>, | ||
#[codec(skip)] pub PhantomData<(Address, Call, Signature, Extra)>, | ||
); | ||
|
||
impl<Address, Call, Signature, Extra> codec::Encode | ||
for UncheckedExtrinsic<Address, Call, Signature, Extra> | ||
{ | ||
fn encode(&self) -> Vec<u8> { | ||
self.0.to_owned() | ||
} | ||
lexnv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
impl<Address, Call, Signature, Extra> scale_encode::EncodeAsType | ||
for UncheckedExtrinsic<Address, Call, Signature, Extra> | ||
{ | ||
fn encode_as_type_to( | ||
&self, | ||
_type_id: u32, | ||
_types: &scale_info::PortableRegistry, | ||
out: &mut Vec<u8>, | ||
) -> Result<(), scale_encode::Error> { | ||
out.extend_from_slice(&self.0); | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<Address, Call, Signature, Extra> scale_encode::EncodeAsFields | ||
for UncheckedExtrinsic<Address, Call, Signature, Extra> | ||
{ | ||
fn encode_as_fields_to( | ||
&self, | ||
_fields: &mut dyn scale_encode::FieldIter<'_>, | ||
_types: &scale_info::PortableRegistry, | ||
out: &mut Vec<u8>, | ||
) -> Result<(), scale_encode::Error> { | ||
out.extend_from_slice(&self.0); | ||
Ok(()) | ||
} | ||
} | ||
lexnv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
pub struct UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra>( | ||
PhantomData<(Address, Call, Signature, Extra)>, | ||
); | ||
|
||
impl<Address, Call, Signature, Extra> Visitor | ||
for UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra> | ||
{ | ||
type Value<'scale, 'info> = UncheckedExtrinsic<Address, Call, Signature, Extra>; | ||
type Error = scale_decode::Error; | ||
|
||
fn unchecked_decode_as_type<'scale, 'info>( | ||
self, | ||
input: &mut &'scale [u8], | ||
_type_id: scale_decode::visitor::TypeId, | ||
_types: &'info scale_info::PortableRegistry, | ||
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> { | ||
use scale_decode::{visitor::DecodeError, Error}; | ||
let decoded = UncheckedExtrinsic::decode(input) | ||
.map_err(|e| Error::new(DecodeError::CodecError(e).into())); | ||
DecodeAsTypeResult::Decoded(decoded) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aah, I was hoping that you could get rid of more of this by using I wonder whether something like this would work at least, to lean on the inner impl better (while not making any assumptions about it): impl<Address, Call, Signature, Extra> Visitor
for UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra>
{
type Value<'scale, 'info> = UncheckedExtrinsic<Address, Call, Signature, Extra>;
type Error = scale_decode::Error;
fn unchecked_decode_as_type<'scale, 'info>(
self,
input: &mut &'scale [u8],
type_id: scale_decode::visitor::TypeId,
types: &'info scale_info::PortableRegistry,
) -> DecodeAsTypeResult<Self, Result<Self::Value<'scale, 'info>, Self::Error>> {
DecodeAsTypeResult::Decoded(self.0.decode_as_type(input, type_id.0, types))
}
} Maybe this is a sign that we can do something better with the |
||
|
||
impl<Address, Call, Signature, Extra> IntoVisitor | ||
for UncheckedExtrinsic<Address, Call, Signature, Extra> | ||
{ | ||
type Visitor = UncheckedExtrinsicDecodeAsTypeVisitor<Address, Call, Signature, Extra>; | ||
|
||
fn into_visitor() -> Self::Visitor { | ||
UncheckedExtrinsicDecodeAsTypeVisitor(PhantomData) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ | |
// see LICENSE for license details. | ||
|
||
use crate::{node_runtime, test_context}; | ||
use codec::Encode; | ||
use core::marker::PhantomData; | ||
use subxt::utils::AccountId32; | ||
use subxt_signer::sr25519::dev; | ||
|
||
|
@@ -47,3 +49,57 @@ async fn account_nonce() -> Result<(), subxt::Error> { | |
|
||
Ok(()) | ||
} | ||
|
||
#[tokio::test] | ||
async fn unchecked_extrinsic_encoding() -> Result<(), subxt::Error> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the test; great to check that this works :) |
||
let ctx = test_context().await; | ||
let api = ctx.client(); | ||
|
||
let alice = dev::alice(); | ||
let bob = dev::bob(); | ||
let bob_address = bob.public_key().to_address(); | ||
|
||
// Construct a tx from Alice to Bob. | ||
let tx = node_runtime::tx().balances().transfer(bob_address, 10_000); | ||
|
||
let signed_extrinsic = api | ||
.tx() | ||
.create_signed(&tx, &alice, Default::default()) | ||
.await | ||
.unwrap(); | ||
|
||
let tx_bytes = signed_extrinsic.into_encoded(); | ||
let len = tx_bytes.len() as u32; | ||
|
||
// Manually encode the runtime API call arguments to make a raw call. | ||
let mut encoded = tx_bytes.clone(); | ||
encoded.extend(len.encode()); | ||
|
||
let expected_result: node_runtime::runtime_types::pallet_transaction_payment::types::FeeDetails< | ||
::core::primitive::u128, | ||
> = api | ||
.runtime_api() | ||
.at_latest() | ||
.await? | ||
.call_raw( | ||
"TransactionPaymentApi_query_fee_details", | ||
Some(encoded.as_ref()), | ||
) | ||
.await?; | ||
|
||
// Use the generated API to confirm the result with the raw call. | ||
let runtime_api_call = node_runtime::apis() | ||
.transaction_payment_api() | ||
.query_fee_details(subxt::utils::UncheckedExtrinsic(tx_bytes, PhantomData), len); | ||
|
||
let result = api | ||
.runtime_api() | ||
.at_latest() | ||
.await? | ||
.call(runtime_api_call) | ||
.await?; | ||
|
||
assert_eq!(expected_result, result); | ||
|
||
Ok(()) | ||
} |
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.
Potentially we could reuse some other code and avoid some duplicated logic by having this be:
Then, all of the impls could delegate to the
Static<Encoded>
type, which I think already does what we want it to.In any case though, let's give this a nicer interface since users need to call it and shouldn't care about PhantomData etc, something like:
Hopefully type inference will sort out the generic params when it's used, ie
Will hopefully Just Work, and infer all of the types properly!