Skip to main content

sp_runtime/traits/vers_tx_ext/
mod.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! The traits and primitive types for versioned transaction extension pipelines.
19
20use crate::{
21	scale_info::StaticTypeInfo,
22	traits::{
23		DispatchInfoOf, DispatchOriginOf, Dispatchable, PostDispatchInfoOf,
24		TransactionExtensionMetadata,
25	},
26	transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction},
27};
28use alloc::{collections::BTreeMap, vec::Vec};
29use codec::Encode;
30use core::fmt::Debug;
31use sp_weights::Weight;
32
33mod at_vers;
34mod invalid;
35mod multi;
36mod variant;
37pub use at_vers::PipelineAtVers;
38pub use invalid::InvalidVersion;
39pub use multi::MultiVersion;
40pub use variant::ExtensionVariant;
41
42/// The version for an instance of a versioned transaction extension pipeline.
43///
44/// This trait is part of [`Pipeline`]. It is defined independently to allow implementation to
45/// rely only on it without bounding the whole trait [`Pipeline`].
46///
47/// (type name is short for versioned (Vers) Pipeline version (Version)).
48pub trait PipelineVersion {
49	/// Return the version for the given versioned transaction extension pipeline.
50	fn version(&self) -> u8;
51}
52
53/// A versioned transaction extension pipeline.
54///
55/// This defines multiple version of a transaction extensions pipeline.
56///
57/// (type name is short for versioned (Vers) Pipeline).
58pub trait Pipeline<Call: Dispatchable>:
59	Encode
60	+ DecodeWithVersion
61	+ DecodeWithVersionWithMemTracking
62	+ Debug
63	+ StaticTypeInfo
64	+ Send
65	+ Sync
66	+ Clone
67	+ PipelineVersion
68{
69	/// Build the metadata for the versioned transaction extension pipeline.
70	fn build_metadata(builder: &mut PipelineMetadataBuilder);
71
72	/// Validate a transaction.
73	fn validate_only(
74		&self,
75		origin: DispatchOriginOf<Call>,
76		call: &Call,
77		info: &DispatchInfoOf<Call>,
78		len: usize,
79		source: TransactionSource,
80	) -> Result<ValidTransaction, TransactionValidityError>;
81
82	/// Dispatch a transaction.
83	fn dispatch_transaction(
84		self,
85		origin: DispatchOriginOf<Call>,
86		call: Call,
87		info: &DispatchInfoOf<Call>,
88		len: usize,
89	) -> crate::ApplyExtrinsicResultWithInfo<PostDispatchInfoOf<Call>>;
90
91	/// Return the pre dispatch weight for the given versioned transaction extension pipeline and
92	/// call.
93	fn weight(&self, call: &Call) -> Weight;
94}
95
96/// A type that can be decoded from a specific version and a [`codec::Input`].
97pub trait DecodeWithVersion: Sized {
98	/// Decode the type from the given version and input.
99	fn decode_with_version<I: codec::Input>(
100		extension_version: u8,
101		input: &mut I,
102	) -> Result<Self, codec::Error>;
103}
104
105/// A type implements [`DecodeWithVersion`] where inner decoding is implementing
106/// [`codec::DecodeWithMemTracking`].
107pub trait DecodeWithVersionWithMemTracking: DecodeWithVersion {}
108
109/// A type to build the metadata for the versioned transaction extension pipeline.
110pub struct PipelineMetadataBuilder {
111	/// The transaction extension pipeline by version and its list of items as vec of index into
112	/// the other field `in_versions`.
113	pub by_version: BTreeMap<u8, Vec<u32>>,
114	/// The list of all transaction extension item used.
115	pub in_versions: Vec<TransactionExtensionMetadata>,
116}
117
118impl PipelineMetadataBuilder {
119	/// Create a new empty metadata builder.
120	pub fn new() -> Self {
121		Self { by_version: BTreeMap::new(), in_versions: Vec::new() }
122	}
123
124	/// A function to add a versioned transaction extension pipeline to the metadata builder.
125	pub fn push_versioned_extension(
126		&mut self,
127		ext_version: u8,
128		ext_items: Vec<TransactionExtensionMetadata>,
129	) {
130		if self.by_version.contains_key(&ext_version) {
131			log::warn!("Duplicate definition for transaction extension version: {}", ext_version);
132			debug_assert!(
133				false,
134				"Duplicate definition for transaction extension version: {ext_version}",
135			);
136			return;
137		}
138
139		let mut ext_item_indices = Vec::with_capacity(ext_items.len());
140		for ext_item in ext_items {
141			let ext_item_index =
142				match self.in_versions.iter().position(|ext| ext.identifier == ext_item.identifier)
143				{
144					Some(index) => index,
145					None => {
146						self.in_versions.push(ext_item);
147						self.in_versions.len() - 1
148					},
149				};
150			ext_item_indices.push(ext_item_index as u32);
151		}
152		self.by_version.insert(ext_version, ext_item_indices);
153	}
154}
155
156#[cfg(test)]
157mod tests {
158	use super::*;
159	use scale_info::meta_type;
160
161	#[test]
162	fn test_metadata_builder() {
163		let mut builder = PipelineMetadataBuilder::new();
164
165		let ext_item_a = TransactionExtensionMetadata {
166			identifier: "ExtensionA",
167			ty: meta_type::<u64>(),
168			implicit: meta_type::<(u32, u8)>(),
169		};
170		let ext_item_b = TransactionExtensionMetadata {
171			identifier: "ExtensionB",
172			ty: meta_type::<bool>(),
173			implicit: meta_type::<String>(),
174		};
175
176		// Push version 1 with ExtensionA.
177		builder.push_versioned_extension(1, vec![ext_item_a.clone()]);
178		// Push version 2 with ExtensionB, then ExtensionA again.
179		builder.push_versioned_extension(2, vec![ext_item_b.clone(), ext_item_a.clone()]);
180
181		// We now expect:
182		// - `by_version` to have two entries: {1: [<indices>], 2: [<indices>]}.
183		// - `in_versions` to contain ExtensionA and ExtensionB in some order.
184
185		// Check that by_version now has 2 distinct versions defined.
186		assert_eq!(builder.by_version.len(), 2);
187
188		// Verify version 1 entries.
189		{
190			let v1_indices = builder.by_version.get(&1).expect("Version 1 must be present");
191			assert_eq!(v1_indices.len(), 1, "Version 1 should have exactly one extension");
192			// Since we only ever added ExtensionA for version 1, it must match.
193			assert_eq!(builder.in_versions[v1_indices[0] as usize].identifier, "ExtensionA");
194		}
195
196		// Verify version 2 entries.
197		{
198			let v2_indices = builder.by_version.get(&2).expect("Version 2 must be present");
199			assert_eq!(v2_indices.len(), 2, "Version 2 should have exactly two extensions");
200			// For version 2, we pushed B then A, so the index order should reflect that:
201			//   - ExtensionB is new, so it should get appended at the end of `in_versions`.
202			//   - ExtensionA was seen previously, so it should reuse the earlier index.
203
204			// First index for version 2 should point to "ExtensionB".
205			assert_eq!(builder.in_versions[v2_indices[0] as usize].identifier, "ExtensionB");
206			// Second index for version 2 should point back to "ExtensionA".
207			assert_eq!(builder.in_versions[v2_indices[1] as usize].identifier, "ExtensionA");
208		}
209
210		// There should be exactly 2 unique entries in `in_versions`: [ExtensionA, ExtensionB].
211		assert_eq!(builder.in_versions.len(), 2);
212	}
213}