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, RpcEndpoint, 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::net::SocketAddr;
52use std::str::FromStr;
53
54/// The domain id of the evm domain
55pub const EVM_DOMAIN_ID: DomainId = DomainId::new(0u32);
56
57/// The domain id of the auto-id domain
58pub const AUTO_ID_DOMAIN_ID: DomainId = DomainId::new(1u32);
59
60/// Create a domain node `Configuration`.
61///
62/// By default an in-memory socket will be used, therefore you need to provide nodes if you want the
63/// node to be connected to other nodes. If `nodes_exclusive` is `true`, the node will only connect
64/// to the given `nodes` and not to any other node.
65#[allow(clippy::too_many_arguments)]
66#[expect(clippy::result_large_err, reason = "Comes from Substrate")]
67pub fn node_config(
68    domain_id: DomainId,
69    tokio_handle: tokio::runtime::Handle,
70    key_seed: String,
71    nodes: Vec<MultiaddrWithPeerId>,
72    nodes_exclusive: bool,
73    role: Role,
74    base_path: BasePath,
75    chain_spec: Box<dyn ChainSpec>,
76    rpc_addr: Option<SocketAddr>,
77    rpc_port: Option<u16>,
78) -> Result<ServiceConfiguration, ServiceError> {
79    let root = base_path.path().to_path_buf();
80
81    let domain_name = format!("{domain_id:?}");
82
83    let mut network_config = NetworkConfiguration::new(
84        format!("{key_seed} ({domain_name})"),
85        "network/test/0.1",
86        Default::default(),
87        None,
88    );
89
90    if nodes_exclusive {
91        network_config.default_peers_set.reserved_nodes = nodes;
92        network_config.default_peers_set.non_reserved_mode = NonReservedPeerMode::Deny;
93    } else {
94        network_config.boot_nodes = nodes;
95    }
96
97    network_config.allow_non_globals_in_dht = true;
98
99    network_config
100        .listen_addresses
101        .push(multiaddr::Protocol::Memory(rand::random()).into());
102
103    // NOTE: Block sync is disabled for the domain subnet thus the major sync state may not be accurate,
104    // which will cause transaction not propagate through network properly, setting the `force_synced`
105    // flag can workaround this issue.
106    network_config.force_synced = true;
107
108    network_config.transport = TransportConfig::MemoryOnly;
109
110    let rpc_configuration = match rpc_addr {
111        Some(listen_addr) => {
112            let port = rpc_port.unwrap_or(9945);
113            RpcConfiguration {
114                addr: Some(vec![RpcEndpoint {
115                    batch_config: RpcBatchRequestConfig::Disabled,
116                    max_connections: 100,
117                    listen_addr,
118                    rpc_methods: Default::default(),
119                    rate_limit: None,
120                    rate_limit_trust_proxy_headers: false,
121                    rate_limit_whitelisted_ips: vec![],
122                    max_payload_in_mb: 15,
123                    max_payload_out_mb: 15,
124                    max_subscriptions_per_connection: 100,
125                    max_buffer_capacity_per_connection: 100,
126                    cors: None,
127                    retry_random_port: true,
128                    is_optional: false,
129                }]),
130                max_request_size: 15,
131                max_response_size: 15,
132                id_provider: None,
133                max_subs_per_conn: 1024,
134                port,
135                message_buffer_capacity: 1024,
136                batch_config: RpcBatchRequestConfig::Disabled,
137                max_connections: 1000,
138                cors: None,
139                methods: Default::default(),
140                rate_limit: None,
141                rate_limit_whitelisted_ips: vec![],
142                rate_limit_trust_proxy_headers: false,
143            }
144        }
145        None => RpcConfiguration {
146            addr: None,
147            max_request_size: 0,
148            max_response_size: 0,
149            id_provider: None,
150            max_subs_per_conn: 0,
151            port: 0,
152            message_buffer_capacity: 0,
153            batch_config: RpcBatchRequestConfig::Disabled,
154            max_connections: 0,
155            cors: None,
156            methods: Default::default(),
157            rate_limit: None,
158            rate_limit_whitelisted_ips: vec![],
159            rate_limit_trust_proxy_headers: false,
160        },
161    };
162
163    Ok(ServiceConfiguration {
164        impl_name: "domain-test-node".to_string(),
165        impl_version: "0.1".to_string(),
166        role,
167        tokio_handle,
168        transaction_pool: Default::default(),
169        network: network_config,
170        keystore: KeystoreConfig::InMemory,
171        database: DatabaseSource::ParityDb {
172            path: root.join("paritydb"),
173        },
174        trie_cache_maximum_size: Some(16 * 1024 * 1024),
175        state_pruning: Some(PruningMode::ArchiveAll),
176        blocks_pruning: BlocksPruning::KeepAll,
177        chain_spec,
178        executor: ExecutorConfiguration {
179            wasm_method: WasmExecutionMethod::Compiled {
180                instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
181            },
182            max_runtime_instances: 8,
183            default_heap_pages: None,
184            runtime_cache_size: 2,
185        },
186        rpc: rpc_configuration,
187        prometheus_config: None,
188        telemetry_endpoints: None,
189        offchain_worker: OffchainWorkerConfig {
190            enabled: true,
191            indexing_enabled: false,
192        },
193        force_authoring: false,
194        disable_grandpa: false,
195        dev_key_seed: Some(key_seed),
196        tracing_targets: None,
197        tracing_receiver: Default::default(),
198        announce_block: true,
199        data_path: base_path.path().into(),
200        base_path,
201        wasm_runtime_overrides: None,
202    })
203}
204
205type SignedExtraFor<Runtime> = (
206    frame_system::CheckNonZeroSender<Runtime>,
207    frame_system::CheckSpecVersion<Runtime>,
208    frame_system::CheckTxVersion<Runtime>,
209    frame_system::CheckGenesis<Runtime>,
210    frame_system::CheckMortality<Runtime>,
211    frame_system::CheckNonce<Runtime>,
212    domain_check_weight::CheckWeight<Runtime>,
213    pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
214);
215
216type UncheckedExtrinsicFor<Runtime> = generic::UncheckedExtrinsic<
217    <Runtime as DomainRuntime>::Address,
218    <Runtime as frame_system::Config>::RuntimeCall,
219    <Runtime as DomainRuntime>::Signature,
220    SignedExtraFor<Runtime>,
221>;
222
223type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;
224
225pub fn construct_extrinsic_raw_payload<Runtime, Client>(
226    client: impl AsRef<Client>,
227    function: RuntimeCallFor<Runtime>,
228    immortal: bool,
229    nonce: u32,
230    tip: BalanceOf<Runtime>,
231) -> (
232    SignedPayload<RuntimeCallFor<Runtime>, SignedExtraFor<Runtime>>,
233    SignedExtraFor<Runtime>,
234)
235where
236    Runtime: frame_system::Config<Hash = H256> + pallet_transaction_payment::Config + Send + Sync,
237    RuntimeCallFor<Runtime>:
238        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
239    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
240    u64: From<BlockNumberFor<Runtime>>,
241    Client: HeaderBackend<Block>,
242    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
243        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + Clone,
244{
245    let current_block_hash = client.as_ref().info().best_hash;
246    let current_block = client.as_ref().info().best_number.saturated_into();
247    let genesis_block = client.as_ref().hash(0).unwrap().unwrap();
248    let period = u64::from(<Runtime as frame_system::Config>::BlockHashCount::get())
249        .checked_next_power_of_two()
250        .map(|c| c / 2)
251        .unwrap_or(2);
252    let extra: SignedExtraFor<Runtime> = (
253        frame_system::CheckNonZeroSender::<Runtime>::new(),
254        frame_system::CheckSpecVersion::<Runtime>::new(),
255        frame_system::CheckTxVersion::<Runtime>::new(),
256        frame_system::CheckGenesis::<Runtime>::new(),
257        frame_system::CheckMortality::<Runtime>::from(if immortal {
258            generic::Era::Immortal
259        } else {
260            generic::Era::mortal(period, current_block)
261        }),
262        frame_system::CheckNonce::<Runtime>::from(nonce.into()),
263        domain_check_weight::CheckWeight::<Runtime>::new(),
264        pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
265    );
266    (
267        generic::SignedPayload::<RuntimeCallFor<Runtime>, SignedExtraFor<Runtime>>::from_raw(
268            function,
269            extra.clone(),
270            ((), 1, 0, genesis_block, current_block_hash, (), (), ()),
271        ),
272        extra,
273    )
274}
275
276pub trait DomainRuntime {
277    type Keyring: Copy;
278    type AccountId: DeserializeOwned
279        + Encode
280        + Decode
281        + Clone
282        + Debug
283        + Display
284        + FromStr
285        + Sync
286        + Send
287        + 'static;
288    type Address: Encode + Decode;
289    type Signature: Encode + Decode;
290    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature;
291    fn account_id(key: Self::Keyring) -> Self::AccountId;
292    fn address(key: Self::Keyring) -> Self::Address;
293    fn to_seed(key: Self::Keyring) -> String;
294}
295
296impl DomainRuntime for evm_domain_test_runtime::Runtime {
297    type Keyring = EcdsaKeyring;
298    type AccountId = evm_domain_test_runtime::AccountId;
299    type Address = evm_domain_test_runtime::Address;
300    type Signature = evm_domain_test_runtime::Signature;
301
302    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature {
303        evm_domain_test_runtime::Signature::new(key.sign(payload))
304    }
305
306    fn account_id(key: Self::Keyring) -> Self::AccountId {
307        key.to_account_id()
308    }
309
310    fn address(key: Self::Keyring) -> Self::Address {
311        key.to_account_id()
312    }
313
314    fn to_seed(key: Self::Keyring) -> String {
315        key.to_seed()
316    }
317}
318
319impl DomainRuntime for auto_id_domain_test_runtime::Runtime {
320    type Keyring = Sr25519Keyring;
321    type AccountId = auto_id_domain_test_runtime::AccountId;
322    type Address = auto_id_domain_test_runtime::Address;
323    type Signature = auto_id_domain_test_runtime::Signature;
324
325    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature {
326        key.sign(payload).into()
327    }
328
329    fn account_id(key: Self::Keyring) -> Self::AccountId {
330        key.to_account_id()
331    }
332
333    fn address(key: Self::Keyring) -> Self::Address {
334        sp_runtime::MultiAddress::Id(key.to_account_id())
335    }
336
337    fn to_seed(key: Self::Keyring) -> String {
338        key.to_seed()
339    }
340}
341
342/// Construct an extrinsic that can be applied to the test runtime.
343pub fn construct_extrinsic_generic<Runtime, Client>(
344    client: impl AsRef<Client>,
345    function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
346    caller: Runtime::Keyring,
347    immortal: bool,
348    nonce: u32,
349    tip: BalanceOf<Runtime>,
350) -> UncheckedExtrinsicFor<Runtime>
351where
352    Runtime: frame_system::Config<Hash = H256>
353        + pallet_transaction_payment::Config
354        + DomainRuntime
355        + Send
356        + Sync,
357    Runtime::RuntimeCall:
358        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
359    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
360    u64: From<BlockNumberFor<Runtime>>,
361    Client: HeaderBackend<Block>,
362    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
363        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + Clone,
364{
365    let function = function.into();
366    let (raw_payload, extra) =
367        construct_extrinsic_raw_payload(client, function.clone(), immortal, nonce, tip);
368    let signature = raw_payload.using_encoded(|e| Runtime::sign(caller, e));
369    let address = Runtime::address(caller);
370    UncheckedExtrinsicFor::<Runtime>::new_signed(function, address, signature, extra)
371}
372
373/// Construct an unsigned extrinsic that can be applied to the test runtime.
374pub fn construct_unsigned_extrinsic<Runtime>(
375    function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
376) -> UncheckedExtrinsicFor<Runtime>
377where
378    Runtime: frame_system::Config<Hash = H256>
379        + pallet_transaction_payment::Config
380        + DomainRuntime
381        + Send
382        + Sync,
383    Runtime::RuntimeCall:
384        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
385    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
386{
387    let function = function.into();
388    UncheckedExtrinsicFor::<Runtime>::new_bare(function)
389}