domain_test_service/
lib.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3
4// Cumulus is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Cumulus is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Cumulus.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Crate used for testing with Domain.
18
19pub mod chain_spec;
20pub mod domain;
21pub mod keyring;
22
23pub use domain::*;
24use domain_runtime_primitives::opaque::Block;
25pub use evm_domain_test_runtime;
26use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
27use frame_system::pallet_prelude::{BlockNumberFor, RuntimeCallFor};
28pub use keyring::Keyring as EcdsaKeyring;
29use sc_network::config::{NonReservedPeerMode, TransportConfig};
30use sc_network::multiaddr;
31use sc_service::config::{
32    DatabaseSource, ExecutorConfiguration, KeystoreConfig, MultiaddrWithPeerId,
33    NetworkConfiguration, OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig,
34    RpcConfiguration, WasmExecutionMethod, WasmtimeInstantiationStrategy,
35};
36use sc_service::{
37    BasePath, BlocksPruning, ChainSpec, Configuration as ServiceConfiguration,
38    Error as ServiceError, Role,
39};
40use serde::de::DeserializeOwned;
41use sp_arithmetic::traits::SaturatedConversion;
42use sp_blockchain::HeaderBackend;
43use sp_core::{Get, H256};
44use sp_domains::DomainId;
45pub use sp_keyring::Sr25519Keyring;
46use sp_runtime::codec::{Decode, Encode};
47use sp_runtime::generic;
48use sp_runtime::generic::SignedPayload;
49use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable};
50use std::fmt::{Debug, Display};
51use std::str::FromStr;
52
53/// The domain id of the evm domain
54pub const EVM_DOMAIN_ID: DomainId = DomainId::new(0u32);
55
56/// The domain id of the auto-id domain
57pub const AUTO_ID_DOMAIN_ID: DomainId = DomainId::new(1u32);
58
59/// Create a domain node `Configuration`.
60///
61/// By default an in-memory socket will be used, therefore you need to provide nodes if you want the
62/// node to be connected to other nodes. If `nodes_exclusive` is `true`, the node will only connect
63/// to the given `nodes` and not to any other node.
64#[allow(clippy::too_many_arguments)]
65#[expect(clippy::result_large_err, reason = "Comes from Substrate")]
66pub fn node_config(
67    domain_id: DomainId,
68    tokio_handle: tokio::runtime::Handle,
69    key_seed: String,
70    nodes: Vec<MultiaddrWithPeerId>,
71    nodes_exclusive: bool,
72    role: Role,
73    base_path: BasePath,
74    chain_spec: Box<dyn ChainSpec>,
75) -> Result<ServiceConfiguration, ServiceError> {
76    let root = base_path.path().to_path_buf();
77
78    let domain_name = format!("{domain_id:?}");
79
80    let mut network_config = NetworkConfiguration::new(
81        format!("{key_seed} ({domain_name})"),
82        "network/test/0.1",
83        Default::default(),
84        None,
85    );
86
87    if nodes_exclusive {
88        network_config.default_peers_set.reserved_nodes = nodes;
89        network_config.default_peers_set.non_reserved_mode = NonReservedPeerMode::Deny;
90    } else {
91        network_config.boot_nodes = nodes;
92    }
93
94    network_config.allow_non_globals_in_dht = true;
95
96    network_config
97        .listen_addresses
98        .push(multiaddr::Protocol::Memory(rand::random()).into());
99
100    // NOTE: Block sync is disabled for the domain subnet thus the major sync state may not be accurate,
101    // which will cause transaction not propagate through network properly, setting the `force_synced`
102    // flag can workaround this issue.
103    network_config.force_synced = true;
104
105    network_config.transport = TransportConfig::MemoryOnly;
106
107    Ok(ServiceConfiguration {
108        impl_name: "domain-test-node".to_string(),
109        impl_version: "0.1".to_string(),
110        role,
111        tokio_handle,
112        transaction_pool: Default::default(),
113        network: network_config,
114        keystore: KeystoreConfig::InMemory,
115        database: DatabaseSource::ParityDb {
116            path: root.join("paritydb"),
117        },
118        trie_cache_maximum_size: Some(16 * 1024 * 1024),
119        state_pruning: Some(PruningMode::ArchiveAll),
120        blocks_pruning: BlocksPruning::KeepAll,
121        chain_spec,
122        executor: ExecutorConfiguration {
123            wasm_method: WasmExecutionMethod::Compiled {
124                instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
125            },
126            max_runtime_instances: 8,
127            default_heap_pages: None,
128            runtime_cache_size: 2,
129        },
130        rpc: RpcConfiguration {
131            addr: None,
132            max_request_size: 0,
133            max_response_size: 0,
134            id_provider: None,
135            max_subs_per_conn: 0,
136            port: 0,
137            message_buffer_capacity: 0,
138            batch_config: RpcBatchRequestConfig::Disabled,
139            max_connections: 0,
140            cors: None,
141            methods: Default::default(),
142            rate_limit: None,
143            rate_limit_whitelisted_ips: vec![],
144            rate_limit_trust_proxy_headers: false,
145        },
146        prometheus_config: None,
147        telemetry_endpoints: None,
148        offchain_worker: OffchainWorkerConfig {
149            enabled: true,
150            indexing_enabled: false,
151        },
152        force_authoring: false,
153        disable_grandpa: false,
154        dev_key_seed: Some(key_seed),
155        tracing_targets: None,
156        tracing_receiver: Default::default(),
157        announce_block: true,
158        data_path: base_path.path().into(),
159        base_path,
160        wasm_runtime_overrides: None,
161    })
162}
163
164type SignedExtraFor<Runtime> = (
165    frame_system::CheckNonZeroSender<Runtime>,
166    frame_system::CheckSpecVersion<Runtime>,
167    frame_system::CheckTxVersion<Runtime>,
168    frame_system::CheckGenesis<Runtime>,
169    frame_system::CheckMortality<Runtime>,
170    frame_system::CheckNonce<Runtime>,
171    domain_check_weight::CheckWeight<Runtime>,
172    pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
173);
174
175type UncheckedExtrinsicFor<Runtime> = generic::UncheckedExtrinsic<
176    <Runtime as DomainRuntime>::Address,
177    <Runtime as frame_system::Config>::RuntimeCall,
178    <Runtime as DomainRuntime>::Signature,
179    SignedExtraFor<Runtime>,
180>;
181
182type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;
183
184pub fn construct_extrinsic_raw_payload<Runtime, Client>(
185    client: impl AsRef<Client>,
186    function: RuntimeCallFor<Runtime>,
187    immortal: bool,
188    nonce: u32,
189    tip: BalanceOf<Runtime>,
190) -> (
191    SignedPayload<RuntimeCallFor<Runtime>, SignedExtraFor<Runtime>>,
192    SignedExtraFor<Runtime>,
193)
194where
195    Runtime: frame_system::Config<Hash = H256> + pallet_transaction_payment::Config + Send + Sync,
196    RuntimeCallFor<Runtime>:
197        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
198    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
199    u64: From<BlockNumberFor<Runtime>>,
200    Client: HeaderBackend<Block>,
201    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
202        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + Clone,
203{
204    let current_block_hash = client.as_ref().info().best_hash;
205    let current_block = client.as_ref().info().best_number.saturated_into();
206    let genesis_block = client.as_ref().hash(0).unwrap().unwrap();
207    let period = u64::from(<Runtime as frame_system::Config>::BlockHashCount::get())
208        .checked_next_power_of_two()
209        .map(|c| c / 2)
210        .unwrap_or(2);
211    let extra: SignedExtraFor<Runtime> = (
212        frame_system::CheckNonZeroSender::<Runtime>::new(),
213        frame_system::CheckSpecVersion::<Runtime>::new(),
214        frame_system::CheckTxVersion::<Runtime>::new(),
215        frame_system::CheckGenesis::<Runtime>::new(),
216        frame_system::CheckMortality::<Runtime>::from(if immortal {
217            generic::Era::Immortal
218        } else {
219            generic::Era::mortal(period, current_block)
220        }),
221        frame_system::CheckNonce::<Runtime>::from(nonce.into()),
222        domain_check_weight::CheckWeight::<Runtime>::new(),
223        pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
224    );
225    (
226        generic::SignedPayload::<RuntimeCallFor<Runtime>, SignedExtraFor<Runtime>>::from_raw(
227            function,
228            extra.clone(),
229            ((), 1, 0, genesis_block, current_block_hash, (), (), ()),
230        ),
231        extra,
232    )
233}
234
235pub trait DomainRuntime {
236    type Keyring: Copy;
237    type AccountId: DeserializeOwned
238        + Encode
239        + Decode
240        + Clone
241        + Debug
242        + Display
243        + FromStr
244        + Sync
245        + Send
246        + 'static;
247    type Address: Encode + Decode;
248    type Signature: Encode + Decode;
249    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature;
250    fn account_id(key: Self::Keyring) -> Self::AccountId;
251    fn address(key: Self::Keyring) -> Self::Address;
252    fn to_seed(key: Self::Keyring) -> String;
253}
254
255impl DomainRuntime for evm_domain_test_runtime::Runtime {
256    type Keyring = EcdsaKeyring;
257    type AccountId = evm_domain_test_runtime::AccountId;
258    type Address = evm_domain_test_runtime::Address;
259    type Signature = evm_domain_test_runtime::Signature;
260
261    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature {
262        evm_domain_test_runtime::Signature::new(key.sign(payload))
263    }
264
265    fn account_id(key: Self::Keyring) -> Self::AccountId {
266        key.to_account_id()
267    }
268
269    fn address(key: Self::Keyring) -> Self::Address {
270        key.to_account_id()
271    }
272
273    fn to_seed(key: Self::Keyring) -> String {
274        key.to_seed()
275    }
276}
277
278impl DomainRuntime for auto_id_domain_test_runtime::Runtime {
279    type Keyring = Sr25519Keyring;
280    type AccountId = auto_id_domain_test_runtime::AccountId;
281    type Address = auto_id_domain_test_runtime::Address;
282    type Signature = auto_id_domain_test_runtime::Signature;
283
284    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature {
285        key.sign(payload).into()
286    }
287
288    fn account_id(key: Self::Keyring) -> Self::AccountId {
289        key.to_account_id()
290    }
291
292    fn address(key: Self::Keyring) -> Self::Address {
293        sp_runtime::MultiAddress::Id(key.to_account_id())
294    }
295
296    fn to_seed(key: Self::Keyring) -> String {
297        key.to_seed()
298    }
299}
300
301/// Construct an extrinsic that can be applied to the test runtime.
302pub fn construct_extrinsic_generic<Runtime, Client>(
303    client: impl AsRef<Client>,
304    function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
305    caller: Runtime::Keyring,
306    immortal: bool,
307    nonce: u32,
308    tip: BalanceOf<Runtime>,
309) -> UncheckedExtrinsicFor<Runtime>
310where
311    Runtime: frame_system::Config<Hash = H256>
312        + pallet_transaction_payment::Config
313        + DomainRuntime
314        + Send
315        + Sync,
316    Runtime::RuntimeCall:
317        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
318    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
319    u64: From<BlockNumberFor<Runtime>>,
320    Client: HeaderBackend<Block>,
321    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
322        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + Clone,
323{
324    let function = function.into();
325    let (raw_payload, extra) =
326        construct_extrinsic_raw_payload(client, function.clone(), immortal, nonce, tip);
327    let signature = raw_payload.using_encoded(|e| Runtime::sign(caller, e));
328    let address = Runtime::address(caller);
329    UncheckedExtrinsicFor::<Runtime>::new_signed(function, address, signature, extra)
330}
331
332/// Construct an unsigned extrinsic that can be applied to the test runtime.
333pub fn construct_unsigned_extrinsic<Runtime>(
334    function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
335) -> UncheckedExtrinsicFor<Runtime>
336where
337    Runtime: frame_system::Config<Hash = H256>
338        + pallet_transaction_payment::Config
339        + DomainRuntime
340        + Send
341        + Sync,
342    Runtime::RuntimeCall:
343        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
344    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
345{
346    let function = function.into();
347    UncheckedExtrinsicFor::<Runtime>::new_bare(function)
348}