subxt_core/blocks/
extrinsics.rs

1// Copyright 2019-2024 Parity Technologies (UK) Ltd.
2// This file is dual-licensed as Apache-2.0 or GPL-3.0.
3// see LICENSE for license details.
4
5use super::BlockError;
6use crate::blocks::extrinsic_transaction_extensions::ExtrinsicTransactionExtensions;
7use crate::{
8    Metadata,
9    config::{Config, HashFor, Hasher},
10    error::{Error, MetadataError},
11};
12use alloc::sync::Arc;
13use alloc::vec::Vec;
14use core::ops::Deref;
15use frame_decode::extrinsics::Extrinsic;
16use scale_decode::DecodeAsType;
17use subxt_metadata::PalletMetadata;
18
19pub use crate::blocks::StaticExtrinsic;
20
21/// The body of a block.
22pub struct Extrinsics<T: Config> {
23    extrinsics: Vec<Arc<(Extrinsic<'static, u32>, Vec<u8>)>>,
24    metadata: Metadata,
25    hasher: T::Hasher,
26    _marker: core::marker::PhantomData<T>,
27}
28
29impl<T: Config> Extrinsics<T> {
30    /// Instantiate a new [`Extrinsics`] object, given a vector containing
31    /// each extrinsic hash (in the form of bytes) and some metadata that
32    /// we'll use to decode them.
33    pub fn decode_from(extrinsics: Vec<Vec<u8>>, metadata: Metadata) -> Result<Self, Error> {
34        let hasher = T::Hasher::new(&metadata);
35        let extrinsics = extrinsics
36            .into_iter()
37            .enumerate()
38            .map(|(extrinsic_index, bytes)| {
39                let cursor = &mut &*bytes;
40
41                // Try to decode the extrinsic.
42                let decoded_info = frame_decode::extrinsics::decode_extrinsic(
43                    cursor,
44                    metadata.deref(),
45                    metadata.types(),
46                )
47                .map_err(|error| BlockError::ExtrinsicDecodeError {
48                    extrinsic_index,
49                    error,
50                })?
51                .into_owned();
52
53                // We didn't consume all bytes, so decoding probably failed.
54                if !cursor.is_empty() {
55                    return Err(BlockError::LeftoverBytes {
56                        extrinsic_index,
57                        num_leftover_bytes: cursor.len(),
58                    }
59                    .into());
60                }
61
62                Ok(Arc::new((decoded_info, bytes)))
63            })
64            .collect::<Result<_, Error>>()?;
65
66        Ok(Self {
67            extrinsics,
68            hasher,
69            metadata,
70            _marker: core::marker::PhantomData,
71        })
72    }
73
74    /// The number of extrinsics.
75    pub fn len(&self) -> usize {
76        self.extrinsics.len()
77    }
78
79    /// Are there no extrinsics in this block?
80    // Note: mainly here to satisfy clippy.
81    pub fn is_empty(&self) -> bool {
82        self.extrinsics.is_empty()
83    }
84
85    /// Returns an iterator over the extrinsics in the block body.
86    // Dev note: The returned iterator is 'static + Send so that we can box it up and make
87    // use of it with our `FilterExtrinsic` stuff.
88    pub fn iter(&self) -> impl Iterator<Item = ExtrinsicDetails<T>> + Send + Sync + 'static {
89        let extrinsics = self.extrinsics.clone();
90        let num_extrinsics = self.extrinsics.len();
91        let hasher = self.hasher;
92        let metadata = self.metadata.clone();
93
94        (0..num_extrinsics).map(move |index| {
95            ExtrinsicDetails::new(
96                index as u32,
97                extrinsics[index].clone(),
98                hasher,
99                metadata.clone(),
100            )
101        })
102    }
103
104    /// Iterate through the extrinsics using metadata to dynamically decode and skip
105    /// them, and return only those which should decode to the provided `E` type.
106    /// If an error occurs, all subsequent iterations return `None`.
107    pub fn find<E: StaticExtrinsic>(
108        &self,
109    ) -> impl Iterator<Item = Result<FoundExtrinsic<T, E>, Error>> {
110        self.iter().filter_map(|details| {
111            match details.as_extrinsic::<E>() {
112                // Failed to decode extrinsic:
113                Err(err) => Some(Err(err)),
114                // Extrinsic for a different pallet / different call (skip):
115                Ok(None) => None,
116                Ok(Some(value)) => Some(Ok(FoundExtrinsic { details, value })),
117            }
118        })
119    }
120
121    /// Iterate through the extrinsics using metadata to dynamically decode and skip
122    /// them, and return the first extrinsic found which decodes to the provided `E` type.
123    pub fn find_first<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
124        self.find::<E>().next().transpose()
125    }
126
127    /// Iterate through the extrinsics using metadata to dynamically decode and skip
128    /// them, and return the last extrinsic found which decodes to the provided `Ev` type.
129    pub fn find_last<E: StaticExtrinsic>(&self) -> Result<Option<FoundExtrinsic<T, E>>, Error> {
130        self.find::<E>().last().transpose()
131    }
132
133    /// Find an extrinsics that decodes to the type provided. Returns true if it was found.
134    pub fn has<E: StaticExtrinsic>(&self) -> Result<bool, Error> {
135        Ok(self.find::<E>().next().transpose()?.is_some())
136    }
137}
138
139/// A single extrinsic in a block.
140pub struct ExtrinsicDetails<T: Config> {
141    /// The index of the extrinsic in the block.
142    index: u32,
143    /// Extrinsic bytes and decode info.
144    ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
145    /// Hash the extrinsic if we want.
146    hasher: T::Hasher,
147    /// Subxt metadata to fetch the extrinsic metadata.
148    metadata: Metadata,
149    _marker: core::marker::PhantomData<T>,
150}
151
152impl<T> ExtrinsicDetails<T>
153where
154    T: Config,
155{
156    // Attempt to dynamically decode a single extrinsic from the given input.
157    #[doc(hidden)]
158    pub fn new(
159        index: u32,
160        ext: Arc<(Extrinsic<'static, u32>, Vec<u8>)>,
161        hasher: T::Hasher,
162        metadata: Metadata,
163    ) -> ExtrinsicDetails<T> {
164        ExtrinsicDetails {
165            index,
166            ext,
167            hasher,
168            metadata,
169            _marker: core::marker::PhantomData,
170        }
171    }
172
173    /// Calculate and return the hash of the extrinsic, based on the configured hasher.
174    pub fn hash(&self) -> HashFor<T> {
175        // Use hash(), not hash_of(), because we don't want to double encode the bytes.
176        self.hasher.hash(self.bytes())
177    }
178
179    /// Is the extrinsic signed?
180    pub fn is_signed(&self) -> bool {
181        self.decoded_info().is_signed()
182    }
183
184    /// The index of the extrinsic in the block.
185    pub fn index(&self) -> u32 {
186        self.index
187    }
188
189    /// Return _all_ of the bytes representing this extrinsic, which include, in order:
190    /// - First byte: abbbbbbb (a = 0 for unsigned, 1 for signed, b = version)
191    /// - SignatureType (if the payload is signed)
192    ///   - Address
193    ///   - Signature
194    ///   - Extra fields
195    /// - Extrinsic call bytes
196    pub fn bytes(&self) -> &[u8] {
197        &self.ext.1
198    }
199
200    /// Return only the bytes representing this extrinsic call:
201    /// - First byte is the pallet index
202    /// - Second byte is the variant (call) index
203    /// - Followed by field bytes.
204    ///
205    /// # Note
206    ///
207    /// Please use [`Self::bytes`] if you want to get all extrinsic bytes.
208    pub fn call_bytes(&self) -> &[u8] {
209        &self.bytes()[self.decoded_info().call_data_range()]
210    }
211
212    /// Return the bytes representing the fields stored in this extrinsic.
213    ///
214    /// # Note
215    ///
216    /// This is a subset of [`Self::call_bytes`] that does not include the
217    /// first two bytes that denote the pallet index and the variant index.
218    pub fn field_bytes(&self) -> &[u8] {
219        // Note: this cannot panic because we checked the extrinsic bytes
220        // to contain at least two bytes.
221        &self.bytes()[self.decoded_info().call_data_args_range()]
222    }
223
224    /// Return only the bytes of the address that signed this extrinsic.
225    ///
226    /// # Note
227    ///
228    /// Returns `None` if the extrinsic is not signed.
229    pub fn address_bytes(&self) -> Option<&[u8]> {
230        self.decoded_info()
231            .signature_payload()
232            .map(|s| &self.bytes()[s.address_range()])
233    }
234
235    /// Returns Some(signature_bytes) if the extrinsic was signed otherwise None is returned.
236    pub fn signature_bytes(&self) -> Option<&[u8]> {
237        self.decoded_info()
238            .signature_payload()
239            .map(|s| &self.bytes()[s.signature_range()])
240    }
241
242    /// Returns the signed extension `extra` bytes of the extrinsic.
243    /// Each signed extension has an `extra` type (May be zero-sized).
244    /// These bytes are the scale encoded `extra` fields of each signed extension in order of the signed extensions.
245    /// They do *not* include the `additional` signed bytes that are used as part of the payload that is signed.
246    ///
247    /// Note: Returns `None` if the extrinsic is not signed.
248    pub fn transaction_extensions_bytes(&self) -> Option<&[u8]> {
249        self.decoded_info()
250            .transaction_extension_payload()
251            .map(|t| &self.bytes()[t.range()])
252    }
253
254    /// Returns `None` if the extrinsic is not signed.
255    pub fn transaction_extensions(&self) -> Option<ExtrinsicTransactionExtensions<'_, T>> {
256        self.decoded_info()
257            .transaction_extension_payload()
258            .map(|t| ExtrinsicTransactionExtensions::new(self.bytes(), &self.metadata, t))
259    }
260
261    /// The index of the pallet that the extrinsic originated from.
262    pub fn pallet_index(&self) -> u8 {
263        self.decoded_info().pallet_index()
264    }
265
266    /// The index of the extrinsic variant that the extrinsic originated from.
267    pub fn variant_index(&self) -> u8 {
268        self.decoded_info().call_index()
269    }
270
271    /// The name of the pallet from whence the extrinsic originated.
272    pub fn pallet_name(&self) -> Result<&str, Error> {
273        Ok(self.extrinsic_metadata()?.pallet.name())
274    }
275
276    /// The name of the call (ie the name of the variant that it corresponds to).
277    pub fn variant_name(&self) -> Result<&str, Error> {
278        Ok(&self.extrinsic_metadata()?.variant.name)
279    }
280
281    /// Fetch the metadata for this extrinsic.
282    pub fn extrinsic_metadata(&self) -> Result<ExtrinsicMetadataDetails<'_>, Error> {
283        let pallet = self.metadata.pallet_by_index_err(self.pallet_index())?;
284        let variant = pallet
285            .call_variant_by_index(self.variant_index())
286            .ok_or_else(|| MetadataError::VariantIndexNotFound(self.variant_index()))?;
287
288        Ok(ExtrinsicMetadataDetails { pallet, variant })
289    }
290
291    /// Decode and provide the extrinsic fields back in the form of a [`scale_value::Composite`]
292    /// type which represents the named or unnamed fields that were present in the extrinsic.
293    pub fn field_values(&self) -> Result<scale_value::Composite<u32>, Error> {
294        let bytes = &mut self.field_bytes();
295        let extrinsic_metadata = self.extrinsic_metadata()?;
296
297        let mut fields = extrinsic_metadata
298            .variant
299            .fields
300            .iter()
301            .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
302        let decoded =
303            scale_value::scale::decode_as_fields(bytes, &mut fields, self.metadata.types())?;
304
305        Ok(decoded)
306    }
307
308    /// Attempt to decode these [`ExtrinsicDetails`] into a type representing the extrinsic fields.
309    /// Such types are exposed in the codegen as `pallet_name::calls::types::CallName` types.
310    pub fn as_extrinsic<E: StaticExtrinsic>(&self) -> Result<Option<E>, Error> {
311        let extrinsic_metadata = self.extrinsic_metadata()?;
312        if extrinsic_metadata.pallet.name() == E::PALLET
313            && extrinsic_metadata.variant.name == E::CALL
314        {
315            let mut fields = extrinsic_metadata
316                .variant
317                .fields
318                .iter()
319                .map(|f| scale_decode::Field::new(f.ty.id, f.name.as_deref()));
320            let decoded =
321                E::decode_as_fields(&mut self.field_bytes(), &mut fields, self.metadata.types())?;
322            Ok(Some(decoded))
323        } else {
324            Ok(None)
325        }
326    }
327
328    /// Attempt to decode these [`ExtrinsicDetails`] into an outer call enum type (which includes
329    /// the pallet and extrinsic enum variants as well as the extrinsic fields). A compatible
330    /// type for this is exposed via static codegen as a root level `Call` type.
331    pub fn as_root_extrinsic<E: DecodeAsType>(&self) -> Result<E, Error> {
332        let decoded = E::decode_as_type(
333            &mut &self.call_bytes()[..],
334            self.metadata.outer_enums().call_enum_ty(),
335            self.metadata.types(),
336        )?;
337
338        Ok(decoded)
339    }
340
341    fn decoded_info(&self) -> &Extrinsic<'static, u32> {
342        &self.ext.0
343    }
344}
345
346/// A Static Extrinsic found in a block coupled with it's details.
347pub struct FoundExtrinsic<T: Config, E> {
348    /// Details for the extrinsic.
349    pub details: ExtrinsicDetails<T>,
350    /// The decoded extrinsic value.
351    pub value: E,
352}
353
354/// Details for the given extrinsic plucked from the metadata.
355pub struct ExtrinsicMetadataDetails<'a> {
356    /// Metadata for the pallet that the extrinsic belongs to.
357    pub pallet: PalletMetadata<'a>,
358    /// Metadata for the variant which describes the pallet extrinsics.
359    pub variant: &'a scale_info::Variant<scale_info::form::PortableForm>,
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365    use crate::config::SubstrateConfig;
366    use assert_matches::assert_matches;
367    use codec::{Decode, Encode};
368    use frame_metadata::v15::{CustomMetadata, OuterEnums};
369    use frame_metadata::{
370        RuntimeMetadataPrefixed,
371        v15::{ExtrinsicMetadata, PalletCallMetadata, PalletMetadata, RuntimeMetadataV15},
372    };
373    use scale_info::{TypeInfo, meta_type};
374    use scale_value::Value;
375
376    // Extrinsic needs to contain at least the generic type parameter "Call"
377    // for the metadata to be valid.
378    // The "Call" type from the metadata is used to decode extrinsics.
379    #[allow(unused)]
380    #[derive(TypeInfo)]
381    struct ExtrinsicType<Address, Call, Signature, Extra> {
382        pub signature: Option<(Address, Signature, Extra)>,
383        pub function: Call,
384    }
385
386    // Because this type is used to decode extrinsics, we expect this to be a TypeDefVariant.
387    // Each pallet must contain one single variant.
388    #[allow(unused)]
389    #[derive(
390        Encode,
391        Decode,
392        TypeInfo,
393        Clone,
394        Debug,
395        PartialEq,
396        Eq,
397        scale_encode::EncodeAsType,
398        scale_decode::DecodeAsType,
399    )]
400    enum RuntimeCall {
401        Test(Pallet),
402    }
403
404    // The calls of the pallet.
405    #[allow(unused)]
406    #[derive(
407        Encode,
408        Decode,
409        TypeInfo,
410        Clone,
411        Debug,
412        PartialEq,
413        Eq,
414        scale_encode::EncodeAsType,
415        scale_decode::DecodeAsType,
416    )]
417    enum Pallet {
418        #[allow(unused)]
419        #[codec(index = 2)]
420        TestCall {
421            value: u128,
422            signed: bool,
423            name: String,
424        },
425    }
426
427    #[allow(unused)]
428    #[derive(
429        Encode,
430        Decode,
431        TypeInfo,
432        Clone,
433        Debug,
434        PartialEq,
435        Eq,
436        scale_encode::EncodeAsType,
437        scale_decode::DecodeAsType,
438    )]
439    struct TestCallExtrinsic {
440        value: u128,
441        signed: bool,
442        name: String,
443    }
444
445    impl StaticExtrinsic for TestCallExtrinsic {
446        const PALLET: &'static str = "Test";
447        const CALL: &'static str = "TestCall";
448    }
449
450    /// Build fake metadata consisting the types needed to represent an extrinsic.
451    fn metadata() -> Metadata {
452        let pallets = vec![PalletMetadata {
453            name: "Test",
454            storage: None,
455            calls: Some(PalletCallMetadata {
456                ty: meta_type::<Pallet>(),
457            }),
458            event: None,
459            constants: vec![],
460            error: None,
461            index: 0,
462            docs: vec![],
463        }];
464
465        let extrinsic = ExtrinsicMetadata {
466            version: 4,
467            signed_extensions: vec![],
468            address_ty: meta_type::<()>(),
469            call_ty: meta_type::<RuntimeCall>(),
470            signature_ty: meta_type::<()>(),
471            extra_ty: meta_type::<()>(),
472        };
473
474        let meta = RuntimeMetadataV15::new(
475            pallets,
476            extrinsic,
477            meta_type::<()>(),
478            vec![],
479            OuterEnums {
480                call_enum_ty: meta_type::<RuntimeCall>(),
481                event_enum_ty: meta_type::<()>(),
482                error_enum_ty: meta_type::<()>(),
483            },
484            CustomMetadata {
485                map: Default::default(),
486            },
487        );
488        let runtime_metadata: RuntimeMetadataPrefixed = meta.into();
489        let metadata: subxt_metadata::Metadata = runtime_metadata.try_into().unwrap();
490
491        Metadata::from(metadata)
492    }
493
494    #[test]
495    fn extrinsic_metadata_consistency() {
496        let metadata = metadata();
497
498        // Except our metadata to contain the registered types.
499        let pallet = metadata.pallet_by_index(0).expect("pallet exists");
500        let extrinsic = pallet
501            .call_variant_by_index(2)
502            .expect("metadata contains the RuntimeCall enum with this pallet");
503
504        assert_eq!(pallet.name(), "Test");
505        assert_eq!(&extrinsic.name, "TestCall");
506    }
507
508    #[test]
509    fn insufficient_extrinsic_bytes() {
510        let metadata = metadata();
511
512        // Decode with empty bytes.
513        let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![]], metadata);
514        assert_matches!(
515            result.err(),
516            Some(crate::Error::Block(
517                crate::error::BlockError::ExtrinsicDecodeError {
518                    extrinsic_index: 0,
519                    error: _
520                }
521            ))
522        );
523    }
524
525    #[test]
526    fn unsupported_version_extrinsic() {
527        use frame_decode::extrinsics::ExtrinsicDecodeError;
528
529        let metadata = metadata();
530
531        // Decode with invalid version.
532        let result = Extrinsics::<SubstrateConfig>::decode_from(vec![vec![3u8].encode()], metadata);
533
534        assert_matches!(
535            result.err(),
536            Some(crate::Error::Block(
537                crate::error::BlockError::ExtrinsicDecodeError {
538                    extrinsic_index: 0,
539                    error: ExtrinsicDecodeError::VersionNotSupported(3),
540                }
541            ))
542        );
543    }
544
545    #[test]
546    fn tx_hashes_line_up() {
547        let metadata = metadata();
548        let hasher = <SubstrateConfig as Config>::Hasher::new(&metadata);
549
550        let tx = crate::dynamic::tx(
551            "Test",
552            "TestCall",
553            vec![
554                Value::u128(10),
555                Value::bool(true),
556                Value::string("SomeValue"),
557            ],
558        );
559
560        // Encoded TX ready to submit.
561        let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
562            .expect("Valid dynamic parameters are provided");
563
564        // Extrinsic details ready to decode.
565        let extrinsics = Extrinsics::<SubstrateConfig>::decode_from(
566            vec![tx_encoded.encoded().to_owned()],
567            metadata,
568        )
569        .expect("Valid extrinsic");
570
571        let extrinsic = extrinsics.iter().next().unwrap();
572
573        // Both of these types should produce the same bytes.
574        assert_eq!(tx_encoded.encoded(), extrinsic.bytes(), "bytes should eq");
575        // Both of these types should produce the same hash.
576        assert_eq!(
577            tx_encoded.hash_with(hasher),
578            extrinsic.hash(),
579            "hashes should eq"
580        );
581    }
582
583    #[test]
584    fn statically_decode_extrinsic() {
585        let metadata = metadata();
586
587        let tx = crate::dynamic::tx(
588            "Test",
589            "TestCall",
590            vec![
591                Value::u128(10),
592                Value::bool(true),
593                Value::string("SomeValue"),
594            ],
595        );
596        let tx_encoded = crate::tx::create_v4_unsigned::<SubstrateConfig, _>(&tx, &metadata)
597            .expect("Valid dynamic parameters are provided");
598
599        // Note: `create_unsigned` produces the extrinsic bytes by prefixing the extrinsic length.
600        // The length is handled deserializing `ChainBlockExtrinsic`, therefore the first byte is not needed.
601        let extrinsics = Extrinsics::<SubstrateConfig>::decode_from(
602            vec![tx_encoded.encoded().to_owned()],
603            metadata,
604        )
605        .expect("Valid extrinsic");
606
607        let extrinsic = extrinsics.iter().next().unwrap();
608
609        assert!(!extrinsic.is_signed());
610
611        assert_eq!(extrinsic.index(), 0);
612
613        assert_eq!(extrinsic.pallet_index(), 0);
614        assert_eq!(
615            extrinsic
616                .pallet_name()
617                .expect("Valid metadata contains pallet name"),
618            "Test"
619        );
620
621        assert_eq!(extrinsic.variant_index(), 2);
622        assert_eq!(
623            extrinsic
624                .variant_name()
625                .expect("Valid metadata contains variant name"),
626            "TestCall"
627        );
628
629        // Decode the extrinsic to the root enum.
630        let decoded_extrinsic = extrinsic
631            .as_root_extrinsic::<RuntimeCall>()
632            .expect("can decode extrinsic to root enum");
633
634        assert_eq!(
635            decoded_extrinsic,
636            RuntimeCall::Test(Pallet::TestCall {
637                value: 10,
638                signed: true,
639                name: "SomeValue".into(),
640            })
641        );
642
643        // Decode the extrinsic to the extrinsic variant.
644        let decoded_extrinsic = extrinsic
645            .as_extrinsic::<TestCallExtrinsic>()
646            .expect("can decode extrinsic to extrinsic variant")
647            .expect("value cannot be None");
648
649        assert_eq!(
650            decoded_extrinsic,
651            TestCallExtrinsic {
652                value: 10,
653                signed: true,
654                name: "SomeValue".into(),
655            }
656        );
657    }
658}