Skip to main content

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                request_logger_limit: 1024,
144            }
145        }
146        None => RpcConfiguration {
147            addr: None,
148            max_request_size: 0,
149            max_response_size: 0,
150            id_provider: None,
151            max_subs_per_conn: 0,
152            port: 0,
153            message_buffer_capacity: 0,
154            batch_config: RpcBatchRequestConfig::Disabled,
155            max_connections: 0,
156            cors: None,
157            methods: Default::default(),
158            rate_limit: None,
159            rate_limit_whitelisted_ips: vec![],
160            rate_limit_trust_proxy_headers: false,
161            request_logger_limit: 1024,
162        },
163    };
164
165    Ok(ServiceConfiguration {
166        impl_name: "domain-test-node".to_string(),
167        impl_version: "0.1".to_string(),
168        role,
169        tokio_handle,
170        transaction_pool: Default::default(),
171        network: network_config,
172        keystore: KeystoreConfig::InMemory,
173        database: DatabaseSource::ParityDb {
174            path: root.join("paritydb"),
175        },
176        trie_cache_maximum_size: Some(16 * 1024 * 1024),
177        state_pruning: Some(PruningMode::ArchiveAll),
178        blocks_pruning: BlocksPruning::KeepAll,
179        chain_spec,
180        executor: ExecutorConfiguration {
181            wasm_method: WasmExecutionMethod::Compiled {
182                instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
183            },
184            max_runtime_instances: 8,
185            default_heap_pages: None,
186            runtime_cache_size: 2,
187        },
188        rpc: rpc_configuration,
189        prometheus_config: None,
190        telemetry_endpoints: None,
191        offchain_worker: OffchainWorkerConfig {
192            enabled: true,
193            indexing_enabled: false,
194        },
195        force_authoring: false,
196        disable_grandpa: false,
197        dev_key_seed: Some(key_seed),
198        tracing_targets: None,
199        tracing_receiver: Default::default(),
200        announce_block: true,
201        data_path: base_path.path().into(),
202        base_path,
203        wasm_runtime_overrides: None,
204        warm_up_trie_cache: None,
205    })
206}
207
208type SignedExtraFor<Runtime> = (
209    frame_system::CheckNonZeroSender<Runtime>,
210    frame_system::CheckSpecVersion<Runtime>,
211    frame_system::CheckTxVersion<Runtime>,
212    frame_system::CheckGenesis<Runtime>,
213    frame_system::CheckMortality<Runtime>,
214    frame_system::CheckNonce<Runtime>,
215    domain_check_weight::CheckWeight<Runtime>,
216    pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
217);
218
219type UncheckedExtrinsicFor<Runtime> = generic::UncheckedExtrinsic<
220    <Runtime as DomainRuntime>::Address,
221    <Runtime as frame_system::Config>::RuntimeCall,
222    <Runtime as DomainRuntime>::Signature,
223    SignedExtraFor<Runtime>,
224>;
225
226type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;
227
228pub fn construct_extrinsic_raw_payload<Runtime, Client>(
229    client: impl AsRef<Client>,
230    function: RuntimeCallFor<Runtime>,
231    immortal: bool,
232    nonce: u32,
233    tip: BalanceOf<Runtime>,
234) -> (
235    SignedPayload<RuntimeCallFor<Runtime>, SignedExtraFor<Runtime>>,
236    SignedExtraFor<Runtime>,
237)
238where
239    Runtime: frame_system::Config<Hash = H256> + pallet_transaction_payment::Config + Send + Sync,
240    RuntimeCallFor<Runtime>:
241        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
242    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
243    u64: From<BlockNumberFor<Runtime>>,
244    Client: HeaderBackend<Block>,
245    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
246        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + Clone,
247{
248    let current_block_hash = client.as_ref().info().best_hash;
249    let current_block = client.as_ref().info().best_number.saturated_into();
250    let genesis_block = client.as_ref().hash(0).unwrap().unwrap();
251    let period = u64::from(<Runtime as frame_system::Config>::BlockHashCount::get())
252        .checked_next_power_of_two()
253        .map(|c| c / 2)
254        .unwrap_or(2);
255    let extra: SignedExtraFor<Runtime> = (
256        frame_system::CheckNonZeroSender::<Runtime>::new(),
257        frame_system::CheckSpecVersion::<Runtime>::new(),
258        frame_system::CheckTxVersion::<Runtime>::new(),
259        frame_system::CheckGenesis::<Runtime>::new(),
260        frame_system::CheckMortality::<Runtime>::from(if immortal {
261            generic::Era::Immortal
262        } else {
263            generic::Era::mortal(period, current_block)
264        }),
265        frame_system::CheckNonce::<Runtime>::from(nonce.into()),
266        domain_check_weight::CheckWeight::<Runtime>::new(),
267        pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
268    );
269    (
270        generic::SignedPayload::<RuntimeCallFor<Runtime>, SignedExtraFor<Runtime>>::from_raw(
271            function,
272            extra.clone(),
273            ((), 1, 0, genesis_block, current_block_hash, (), (), ()),
274        ),
275        extra,
276    )
277}
278
279pub trait DomainRuntime {
280    type Keyring: Copy;
281    type AccountId: DeserializeOwned
282        + Encode
283        + Decode
284        + Clone
285        + Debug
286        + Display
287        + FromStr
288        + Sync
289        + Send
290        + 'static;
291    type Address: Encode + Decode;
292    type Signature: Encode + Decode;
293    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature;
294    fn account_id(key: Self::Keyring) -> Self::AccountId;
295    fn address(key: Self::Keyring) -> Self::Address;
296    fn to_seed(key: Self::Keyring) -> String;
297}
298
299impl DomainRuntime for evm_domain_test_runtime::Runtime {
300    type Keyring = EcdsaKeyring;
301    type AccountId = evm_domain_test_runtime::AccountId;
302    type Address = evm_domain_test_runtime::Address;
303    type Signature = evm_domain_test_runtime::Signature;
304
305    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature {
306        evm_domain_test_runtime::Signature::new(key.sign(payload))
307    }
308
309    fn account_id(key: Self::Keyring) -> Self::AccountId {
310        key.to_account_id()
311    }
312
313    fn address(key: Self::Keyring) -> Self::Address {
314        key.to_account_id()
315    }
316
317    fn to_seed(key: Self::Keyring) -> String {
318        key.to_seed()
319    }
320}
321
322impl DomainRuntime for auto_id_domain_test_runtime::Runtime {
323    type Keyring = Sr25519Keyring;
324    type AccountId = auto_id_domain_test_runtime::AccountId;
325    type Address = auto_id_domain_test_runtime::Address;
326    type Signature = auto_id_domain_test_runtime::Signature;
327
328    fn sign(key: Self::Keyring, payload: &[u8]) -> Self::Signature {
329        key.sign(payload).into()
330    }
331
332    fn account_id(key: Self::Keyring) -> Self::AccountId {
333        key.to_account_id()
334    }
335
336    fn address(key: Self::Keyring) -> Self::Address {
337        sp_runtime::MultiAddress::Id(key.to_account_id())
338    }
339
340    fn to_seed(key: Self::Keyring) -> String {
341        key.to_seed()
342    }
343}
344
345/// Construct an extrinsic that can be applied to the test runtime.
346pub fn construct_extrinsic_generic<Runtime, Client>(
347    client: impl AsRef<Client>,
348    function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
349    caller: Runtime::Keyring,
350    immortal: bool,
351    nonce: u32,
352    tip: BalanceOf<Runtime>,
353) -> UncheckedExtrinsicFor<Runtime>
354where
355    Runtime: frame_system::Config<Hash = H256>
356        + pallet_transaction_payment::Config
357        + DomainRuntime
358        + Send
359        + Sync,
360    Runtime::RuntimeCall:
361        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
362    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
363    u64: From<BlockNumberFor<Runtime>>,
364    Client: HeaderBackend<Block>,
365    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
366        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + Clone,
367{
368    let function = function.into();
369    let (raw_payload, extra) =
370        construct_extrinsic_raw_payload(client, function.clone(), immortal, nonce, tip);
371    let signature = raw_payload.using_encoded(|e| Runtime::sign(caller, e));
372    let address = Runtime::address(caller);
373    UncheckedExtrinsicFor::<Runtime>::new_signed(function, address, signature, extra)
374}
375
376/// Construct an unsigned extrinsic that can be applied to the test runtime.
377pub fn construct_unsigned_extrinsic<Runtime>(
378    function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
379) -> UncheckedExtrinsicFor<Runtime>
380where
381    Runtime: frame_system::Config<Hash = H256>
382        + pallet_transaction_payment::Config
383        + DomainRuntime
384        + Send
385        + Sync,
386    Runtime::RuntimeCall:
387        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
388    BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
389{
390    let function = function.into();
391    UncheckedExtrinsicFor::<Runtime>::new_bare(function)
392}