referrerpolicy=no-referrer-when-downgrade

sc_statement_store/
subxt_client.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Custom subxt configuration for runtimes interacting with statement-store, used only for tests
20//!
21//! The runtime uses `VerifyMultiSignature` instead of the standard
22//! `VerifySignature` transaction extension, and includes a `RestrictOrigins`
23//! extension that encodes as a bool (`false` = 0x00 to disable origin
24//! restrictions). These non-standard extensions cannot be auto-defaulted by
25//! frame-decode, so this module provides a `CustomConfig` that handles them
26//! explicitly.
27
28use scale_info::PortableRegistry;
29use sp_core::{sr25519, Pair};
30use subxt::{
31	config::{
32		substrate::SubstrateConfig,
33		transaction_extensions::{
34			ChargeAssetTxPayment, ChargeTransactionPayment, CheckGenesis, CheckMetadataHash,
35			CheckMortality, CheckNonce, CheckSpecVersion, CheckTxVersion, VerifySignature,
36		},
37		ClientState, Config, DefaultExtrinsicParamsBuilder, TransactionExtension,
38		TransactionExtensions,
39	},
40	dynamic::Value,
41	ext::{frame_decode, scale_value::value},
42	transactions::Signer,
43	utils::H256,
44	OnlineClient,
45};
46
47/// Wrapper around `VerifySignature` that matches the runtime's `VerifyMultiSignature` name
48pub struct VerifyMultiSignature<T: Config>(VerifySignature<T>);
49
50impl<T: Config> frame_decode::extrinsics::TransactionExtension<PortableRegistry>
51	for VerifyMultiSignature<T>
52{
53	const NAME: &str = "VerifyMultiSignature";
54
55	fn encode_value_to(
56		&self,
57		type_id: u32,
58		type_resolver: &PortableRegistry,
59		v: &mut Vec<u8>,
60	) -> Result<(), frame_decode::extrinsics::TransactionExtensionError> {
61		self.0.encode_value_to(type_id, type_resolver, v)
62	}
63
64	fn encode_value_for_signer_payload_to(
65		&self,
66		type_id: u32,
67		type_resolver: &PortableRegistry,
68		v: &mut Vec<u8>,
69	) -> Result<(), frame_decode::extrinsics::TransactionExtensionError> {
70		self.0.encode_value_for_signer_payload_to(type_id, type_resolver, v)
71	}
72
73	fn encode_implicit_to(
74		&self,
75		type_id: u32,
76		type_resolver: &PortableRegistry,
77		v: &mut Vec<u8>,
78	) -> Result<(), frame_decode::extrinsics::TransactionExtensionError> {
79		self.0.encode_implicit_to(type_id, type_resolver, v)
80	}
81}
82
83impl<T: Config> TransactionExtension<T> for VerifyMultiSignature<T> {
84	type Decoded = <VerifySignature<T> as TransactionExtension<T>>::Decoded;
85	type Params = ();
86
87	fn new(
88		client: &ClientState<T>,
89		params: Self::Params,
90	) -> Result<Self, subxt::error::TransactionExtensionError> {
91		Ok(VerifyMultiSignature(VerifySignature::new(client, params)?))
92	}
93
94	fn inject_signature(&mut self, account_id: &T::AccountId, signature: &T::Signature) {
95		self.0.inject_signature(account_id, signature);
96	}
97}
98
99/// Custom transaction extension for `RestrictOrigins`
100///
101/// This extension encodes as `false` (0x00) to disable origin restrictions
102/// It is a `bool` in the runtime (not `Option<T>`), so frame-decode cannot
103/// auto-default it and it must be handled explicitly
104pub struct RestrictOrigins;
105
106impl frame_decode::extrinsics::TransactionExtension<PortableRegistry> for RestrictOrigins {
107	const NAME: &str = "RestrictOrigins";
108
109	fn encode_value_to(
110		&self,
111		_type_id: u32,
112		_type_resolver: &PortableRegistry,
113		v: &mut Vec<u8>,
114	) -> Result<(), frame_decode::extrinsics::TransactionExtensionError> {
115		// Encode `false` disables origin restriction
116		v.push(0x00);
117		Ok(())
118	}
119
120	fn encode_implicit_to(
121		&self,
122		_type_id: u32,
123		_type_resolver: &PortableRegistry,
124		_v: &mut Vec<u8>,
125	) -> Result<(), frame_decode::extrinsics::TransactionExtensionError> {
126		Ok(())
127	}
128}
129
130impl<T: Config> TransactionExtension<T> for RestrictOrigins {
131	type Decoded = u8;
132	type Params = ();
133
134	fn new(
135		_client: &ClientState<T>,
136		_params: Self::Params,
137	) -> Result<Self, subxt::error::TransactionExtensionError> {
138		Ok(RestrictOrigins)
139	}
140}
141
142/// Custom subxt `Config`
143///
144/// Registers the non-standard `VerifyMultiSignature` and `RestrictOrigins`
145/// transaction extensions so that subxt can correctly encode extrinsics
146#[derive(Debug, Clone)]
147pub struct CustomConfig(SubstrateConfig);
148
149impl Default for CustomConfig {
150	fn default() -> Self {
151		CustomConfig(SubstrateConfig::new())
152	}
153}
154
155impl Config for CustomConfig {
156	type AccountId = <SubstrateConfig as Config>::AccountId;
157	type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
158	type Signature = <SubstrateConfig as Config>::Signature;
159	type Hasher = <SubstrateConfig as Config>::Hasher;
160	type Header = <SubstrateConfig as Config>::Header;
161	type AssetId = <SubstrateConfig as Config>::AssetId;
162	type TransactionExtensions = (
163		VerifyMultiSignature<Self>,
164		CheckSpecVersion,
165		CheckTxVersion,
166		CheckNonce,
167		CheckGenesis<Self>,
168		CheckMortality<Self>,
169		ChargeAssetTxPayment<Self>,
170		ChargeTransactionPayment,
171		CheckMetadataHash,
172		RestrictOrigins,
173	);
174
175	fn genesis_hash(&self) -> Option<subxt::config::HashFor<Self>> {
176		self.0.genesis_hash()
177	}
178
179	fn spec_and_transaction_version_for_block_number(
180		&self,
181		block_number: u64,
182	) -> Option<(u32, u32)> {
183		self.0.spec_and_transaction_version_for_block_number(block_number)
184	}
185
186	fn metadata_for_spec_version(&self, spec_version: u32) -> Option<subxt::metadata::ArcMetadata> {
187		self.0.metadata_for_spec_version(spec_version)
188	}
189
190	fn set_metadata_for_spec_version(
191		&self,
192		spec_version: u32,
193		metadata: subxt::metadata::ArcMetadata,
194	) {
195		self.0.set_metadata_for_spec_version(spec_version, metadata)
196	}
197}
198
199/// Builds params for CustomConfig's transaction extensions (9 defaults + RestrictOrigins)
200fn build_params(
201	nonce: u64,
202) -> <<CustomConfig as Config>::TransactionExtensions as TransactionExtensions<CustomConfig>>::Params
203{
204	let (a, b, c, d, e, f, g, h, i) = DefaultExtrinsicParamsBuilder::<CustomConfig>::new()
205		.immortal()
206		.nonce(nonce)
207		.build();
208	(a, b, c, d, e, f, g, h, i, ())
209}
210
211/// Submits an extrinsic with an explicit nonce and waits for it to be finalized
212pub async fn submit_extrinsic<S: Signer<CustomConfig>>(
213	client: &OnlineClient<CustomConfig>,
214	call: &subxt::transactions::DynamicPayload<Vec<Value>>,
215	signer: &S,
216	nonce: u64,
217) -> Result<H256, anyhow::Error> {
218	let tx_in_block = client
219		.tx()
220		.await?
221		.sign_and_submit_then_watch(call, signer, build_params(nonce))
222		.await?
223		.wait_for_finalized()
224		.await?;
225
226	tx_in_block.wait_for_success().await?;
227	Ok(tx_in_block.block_hash())
228}
229
230/// Gets the current nonce for an account
231pub async fn get_account_nonce(
232	client: &OnlineClient<CustomConfig>,
233	account_id: &<CustomConfig as Config>::AccountId,
234) -> Result<u64, anyhow::Error> {
235	let nonce = client.tx().await?.account_nonce(account_id).await?;
236	Ok(nonce)
237}
238
239/// Matches `indiv_pallet_people_lite::MSG_PREFIX`
240pub const MSG_PREFIX: &[u8; 30] = b"pop:people-lite:register using";
241
242/// Builds a sudo call wrapping `PeopleLite::increase_attestation_allowance`
243pub fn create_increase_allowance_call(
244	who: Vec<u8>,
245	count: u32,
246) -> subxt::transactions::DynamicPayload<Vec<Value>> {
247	subxt::tx::dynamic(
248		"Sudo",
249		"sudo",
250		vec![value! {
251			PeopleLite(increase_attestation_allowance {
252				account: Value::from_bytes(who),
253				count: Value::u128(count as u128),
254			})
255		}],
256	)
257}
258
259/// Builds a `PeopleLite::attest` call
260pub fn create_attest_call(
261	candidate: Vec<u8>,
262	candidate_signature: Vec<u8>,
263	ring_vrf_key: Vec<u8>,
264	proof_of_ownership: Vec<u8>,
265	consumer_registration: Option<Value>,
266) -> subxt::transactions::DynamicPayload<Vec<Value>> {
267	let reg = match consumer_registration {
268		Some(v) => Value::unnamed_variant("Some", [v]),
269		None => Value::unnamed_variant("None", []),
270	};
271	subxt::tx::dynamic(
272		"PeopleLite",
273		"attest",
274		vec![
275			Value::from_bytes(candidate),
276			Value::unnamed_variant("Sr25519", [Value::from_bytes(candidate_signature)]),
277			Value::from_bytes(ring_vrf_key),
278			Value::from_bytes(proof_of_ownership),
279			reg,
280		],
281	)
282}
283
284/// Builds consumer registration parameters for `create_attest_call`
285///
286/// The `LiteConsumerRegistrationParams` struct has fields:
287///   signature: `MultiSignature`, account: `AccountId`,
288///   identifier_key: `CommunicationIdentifier` (`[u8; 65]`),
289///   username: `Username` (`BoundedVec<u8, 32>`), reserved_username: `Option<Username>`
290///
291/// The signing payload is `(account, verifier, identifier_key, username_prefix,
292/// reserved_username).encode()` where `verifier` is the attester (origin of the attest call)
293pub fn create_consumer_registration_params(
294	consumer_pair: &sr25519::Pair,
295	consumer_account: &[u8; 32],
296	verifier_account: &[u8; 32],
297) -> Value {
298	use sp_core::Encode;
299
300	let identifier_key = [0u8; 65];
301	let username = b"testuser.00";
302	let reserved_username: Option<Vec<u8>> = None;
303
304	// Build SCALE-encoded signing payload matching LiteConsumerRegistrationParams::signing_payload:
305	// (account, verifier, identifier_key, username[..separator], reserved_username).encode()
306	// The username prefix is the part before the '.' separator
307	let separator_idx = username.iter().position(|b| *b == b'.').unwrap_or(username.len());
308	let username_prefix = &username[..separator_idx];
309	let payload = (
310		consumer_account,
311		verifier_account,
312		&identifier_key,
313		&username_prefix.to_vec(),
314		&reserved_username,
315	)
316		.encode();
317
318	let sig = consumer_pair.sign(&payload);
319	value! {
320		{
321			signature: Value::unnamed_variant("Sr25519", [Value::from_bytes(sig.0.to_vec())]),
322			account: Value::from_bytes(consumer_account.to_vec()),
323			identifier_key: Value::from_bytes(identifier_key.to_vec()),
324			username: Value::from_bytes(username.to_vec()),
325			reserved_username: Value::unnamed_variant("None", []),
326		}
327	}
328}
329
330/// Sets statement allowances at runtime via a sudo extrinsic signed by Alice
331pub async fn set_allowances_via_sudo(
332	ws_uri: &str,
333	items: Vec<(Vec<u8>, Vec<u8>)>,
334) -> Result<(), anyhow::Error> {
335	log::info!("Setting {} statement allowances via sudo...", items.len());
336
337	let client = OnlineClient::<CustomConfig>::from_insecure_url_with_config(
338		CustomConfig::default(),
339		ws_uri,
340	)
341	.await?;
342	let alice = subxt_signer::sr25519::dev::alice();
343
344	let items_value: Vec<Value> = items
345		.into_iter()
346		.map(|(key, value)| value!((Value::from_bytes(key), Value::from_bytes(value))))
347		.collect();
348	let call = subxt::tx::dynamic(
349		"Sudo",
350		"sudo",
351		vec![value! {
352			System(set_storage { items: items_value })
353		}],
354	);
355
356	client
357		.tx()
358		.await?
359		.sign_and_submit_then_watch_default(&call, &alice)
360		.await?
361		.wait_for_finalized_success()
362		.await?;
363
364	Ok(())
365}