subcoin_service/
lib.rs

1//! Subcoin service implementation. Specialized wrapper over substrate service.
2
3#![allow(deprecated)]
4
5pub mod chain_spec;
6mod finalizer;
7mod genesis_block_builder;
8pub mod network_request_handler;
9mod transaction_adapter;
10pub mod transaction_pool;
11
12use bitcoin::hashes::Hash;
13use futures::FutureExt;
14use sc_client_api::{AuxStore, HeaderBackend};
15use sc_consensus::import_queue::BasicQueue;
16use sc_consensus_nakamoto::SubstrateImportQueueVerifier;
17use sc_executor::NativeElseWasmExecutor;
18use sc_network_sync::SyncingService;
19use sc_service::config::PrometheusConfig;
20use sc_service::error::Error as ServiceError;
21use sc_service::{
22    Configuration, KeystoreContainer, MetricsService, NativeExecutionDispatch, TaskManager,
23};
24use sc_telemetry::{Telemetry, TelemetryWorker};
25use sc_utils::mpsc::TracingUnboundedSender;
26use sp_core::Encode;
27use sp_runtime::traits::Block as BlockT;
28use std::ops::Deref;
29use std::path::Path;
30use std::sync::Arc;
31use subcoin_runtime::RuntimeApi;
32use subcoin_runtime::interface::OpaqueBlock as Block;
33
34pub use finalizer::SubcoinFinalizer;
35pub use genesis_block_builder::GenesisBlockBuilder;
36pub use transaction_adapter::TransactionAdapter;
37
38/// This is a specialization of the general Substrate ChainSpec type.
39pub type ChainSpec = sc_service::GenericChainSpec;
40
41/// Disk backend client type.
42pub type FullClient =
43    sc_service::TFullClient<Block, RuntimeApi, NativeElseWasmExecutor<SubcoinExecutorDispatch>>;
44pub type FullBackend = sc_service::TFullBackend<Block>;
45type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
46
47/// Subcoin executor.
48pub struct SubcoinExecutorDispatch;
49
50impl NativeExecutionDispatch for SubcoinExecutorDispatch {
51    type ExtendHostFunctions = ();
52
53    fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
54        subcoin_runtime::api::dispatch(method, data)
55    }
56
57    fn native_version() -> sc_executor::NativeVersion {
58        subcoin_runtime::native_version()
59    }
60}
61
62/// This struct implements the trait [`subcoin_primitives::CoinStorageKey`].
63pub struct CoinStorageKey;
64
65impl subcoin_primitives::CoinStorageKey for CoinStorageKey {
66    fn storage_key(&self, txid: bitcoin::Txid, vout: u32) -> Vec<u8> {
67        pallet_bitcoin::coin_storage_key::<subcoin_runtime::Runtime>(txid, vout)
68    }
69
70    fn storage_prefix(&self) -> [u8; 32] {
71        pallet_bitcoin::coin_storage_prefix::<subcoin_runtime::Runtime>()
72    }
73}
74
75/// Subcoin node components.
76pub struct NodeComponents {
77    /// Client.
78    pub client: Arc<FullClient>,
79    /// Backend.
80    pub backend: Arc<FullBackend>,
81    /// Executor
82    pub executor: NativeElseWasmExecutor<SubcoinExecutorDispatch>,
83    /// Task manager.
84    pub task_manager: TaskManager,
85    pub keystore_container: KeystoreContainer,
86    pub telemetry: Option<Telemetry>,
87}
88
89/// Subcoin node configuration.
90pub struct SubcoinConfiguration<'a> {
91    pub network: bitcoin::Network,
92    pub config: &'a Configuration,
93    pub no_hardware_benchmarks: bool,
94    pub storage_monitor: sc_storage_monitor::StorageMonitorParams,
95}
96
97impl<'a> SubcoinConfiguration<'a> {
98    /// Creates a [`SubcoinConfiguration`] for test purposes.
99    pub fn test_config(config: &'a Configuration) -> Self {
100        Self {
101            network: bitcoin::Network::Bitcoin,
102            config,
103            no_hardware_benchmarks: true,
104            storage_monitor: Default::default(),
105        }
106    }
107}
108
109impl Deref for SubcoinConfiguration<'_> {
110    type Target = Configuration;
111
112    fn deref(&self) -> &Self::Target {
113        self.config
114    }
115}
116
117/// Insert the genesis block hash mapping into aux-db.
118pub fn initialize_genesis_block_hash_mapping<
119    Block: BlockT,
120    Client: HeaderBackend<Block> + AuxStore,
121>(
122    client: &Client,
123    bitcoin_network: bitcoin::Network,
124) {
125    // Initialize the genesis block hash mapping.
126    let substrate_genesis_hash: <Block as BlockT>::Hash = client.info().genesis_hash;
127    let bitcoin_genesis_hash = bitcoin::constants::genesis_block(bitcoin_network).block_hash();
128    client
129        .insert_aux(
130            &[(
131                bitcoin_genesis_hash.to_byte_array().as_slice(),
132                substrate_genesis_hash.encode().as_slice(),
133            )],
134            [],
135        )
136        .expect("Failed to store genesis block hash mapping");
137}
138
139/// Creates a new subcoin node.
140pub fn new_node(config: SubcoinConfiguration) -> Result<NodeComponents, ServiceError> {
141    let SubcoinConfiguration {
142        network: bitcoin_network,
143        config,
144        no_hardware_benchmarks: _,
145        storage_monitor,
146    } = config;
147
148    let telemetry = config
149        .telemetry_endpoints
150        .clone()
151        .filter(|x| !x.is_empty())
152        .map(|endpoints| -> Result<_, sc_telemetry::Error> {
153            let worker = TelemetryWorker::new(16)?;
154            let telemetry = worker.handle().new_telemetry(endpoints);
155            Ok((worker, telemetry))
156        })
157        .transpose()?;
158
159    // TODO: maintain the native executor on our own since it's deprecated upstream
160    let executor = sc_service::new_native_or_wasm_executor(config);
161
162    let backend = sc_service::new_db_backend(config.db_config())?;
163
164    let genesis_block_builder = GenesisBlockBuilder::<_, _, _, TransactionAdapter>::new(
165        bitcoin_network,
166        config.chain_spec.as_storage_builder(),
167        !config.no_genesis(),
168        backend.clone(),
169        executor.clone(),
170    )?;
171
172    let (client, backend, keystore_container, task_manager) =
173        sc_service::new_full_parts_with_genesis_builder::<Block, RuntimeApi, _, _>(
174            config,
175            telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
176            executor.clone(),
177            backend,
178            genesis_block_builder,
179            false,
180        )?;
181
182    // Initialize the genesis block hash mapping.
183    initialize_genesis_block_hash_mapping(&client, bitcoin_network);
184
185    let client = Arc::new(client);
186
187    let telemetry = telemetry.map(|(worker, telemetry)| {
188        task_manager
189            .spawn_handle()
190            .spawn("telemetry", None, worker.run());
191        telemetry
192    });
193
194    let database_path = config.database.path().map(Path::to_path_buf);
195
196    // TODO: frame_benchmarking_cli pulls in rocksdb due to its dep
197    // cumulus-client-parachain-inherent.
198    // let maybe_hwbench = (!no_hardware_benchmarks)
199    // .then(|| {database_path.as_ref().map(|db_path| {
200    // let _ = std::fs::create_dir_all(db_path);
201    // sc_sysinfo::gather_hwbench(Some(db_path), &frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE)
202    // })})
203    // .flatten();
204
205    // if let Some(hwbench) = maybe_hwbench {
206    // sc_sysinfo::print_hwbench(&hwbench);
207    // match frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench, config.role.is_authority()) {
208    // Err(err) if config.role.is_authority() => {
209    // tracing::warn!(
210    // "⚠️  The hardware does not meet the minimal requirements {err} for role 'Authority'.",
211    // );
212    // }
213    // _ => {}
214    // }
215
216    // if let Some(ref mut telemetry) = telemetry {
217    // let telemetry_handle = telemetry.handle();
218    // task_manager.spawn_handle().spawn(
219    // "telemetry_hwbench",
220    // None,
221    // sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench),
222    // );
223    // }
224    // }
225
226    if let Some(database_path) = database_path {
227        sc_storage_monitor::StorageMonitorService::try_spawn(
228            storage_monitor,
229            database_path,
230            &task_manager.spawn_essential_handle(),
231        )
232        .map_err(|e| ServiceError::Application(e.into()))?;
233    }
234
235    Ok(NodeComponents {
236        client,
237        backend,
238        executor,
239        task_manager,
240        keystore_container,
241        telemetry,
242    })
243}
244
245type SubstrateNetworkingParts = (
246    TracingUnboundedSender<sc_rpc::system::Request<Block>>,
247    Arc<SyncingService<Block>>,
248);
249
250/// Runs the Substrate networking.
251pub fn start_substrate_network<N>(
252    config: &mut Configuration,
253    client: Arc<FullClient>,
254    _backend: Arc<FullBackend>,
255    task_manager: &mut TaskManager,
256    bitcoin_network: bitcoin::Network,
257    mut telemetry: Option<Telemetry>,
258) -> Result<SubstrateNetworkingParts, ServiceError>
259where
260    N: sc_network::NetworkBackend<Block, <Block as BlockT>::Hash>,
261{
262    let mut net_config = sc_network::config::FullNetworkConfiguration::<
263        Block,
264        <Block as BlockT>::Hash,
265        N,
266    >::new(&config.network, config.prometheus_registry().cloned());
267
268    let network_request_protocol_config = {
269        // Allow both outgoing and incoming requests.
270        let (handler, protocol_config) = network_request_handler::NetworkRequestHandler::new::<N>(
271            &config.protocol_id(),
272            config.chain_spec.fork_id(),
273            client.clone(),
274            100,
275        );
276        task_manager.spawn_handle().spawn(
277            "subcoin-network-request-handler",
278            Some("networking"),
279            handler.run(),
280        );
281        protocol_config
282    };
283
284    net_config.add_request_response_protocol(network_request_protocol_config);
285
286    let metrics = N::register_notification_metrics(config.prometheus_registry());
287
288    let transaction_pool = Arc::from(
289        sc_transaction_pool::Builder::new(
290            task_manager.spawn_essential_handle(),
291            client.clone(),
292            config.role.is_authority().into(),
293        )
294        .with_options(config.transaction_pool.clone())
295        .with_prometheus(config.prometheus_registry())
296        .build(),
297    );
298
299    let import_queue = BasicQueue::new(
300        SubstrateImportQueueVerifier::new(client.clone(), bitcoin_network),
301        Box::new(client.clone()),
302        None,
303        &task_manager.spawn_essential_handle(),
304        None,
305    );
306
307    let (network, system_rpc_tx, _tx_handler_controller, sync_service) =
308        sc_service::build_network(sc_service::BuildNetworkParams {
309            config,
310            net_config,
311            client: client.clone(),
312            transaction_pool: transaction_pool.clone(),
313            spawn_handle: task_manager.spawn_handle(),
314            import_queue,
315            block_announce_validator_builder: None,
316            warp_sync_config: None,
317            block_relay: None,
318            metrics,
319        })?;
320
321    // Custom `sc_service::spawn_tasks` with some needless tasks removed.
322    let spawn_handle = task_manager.spawn_handle();
323
324    let sysinfo = sc_sysinfo::gather_sysinfo();
325    sc_sysinfo::print_sysinfo(&sysinfo);
326
327    let telemetry = telemetry
328        .as_mut()
329        .map(|telemetry| {
330            sc_service::init_telemetry(
331                config.network.node_name.clone(),
332                config.impl_name.clone(),
333                config.impl_version.clone(),
334                config.chain_spec.name().to_string(),
335                config.role.is_authority(),
336                network.clone(),
337                client.clone(),
338                telemetry,
339                Some(sysinfo),
340            )
341        })
342        .transpose()?;
343
344    // Prometheus metrics.
345    let metrics_service =
346        if let Some(PrometheusConfig { port, registry }) = config.prometheus_config.clone() {
347            // Set static metrics.
348            let metrics = MetricsService::with_prometheus(
349                telemetry,
350                &registry,
351                config.role,
352                &config.network.node_name,
353                &config.impl_version,
354            )?;
355            spawn_handle.spawn(
356                "prometheus-endpoint",
357                None,
358                substrate_prometheus_endpoint::init_prometheus(port, registry).map(drop),
359            );
360
361            metrics
362        } else {
363            MetricsService::new(telemetry)
364        };
365
366    // Periodically updated metrics and telemetry updates.
367    spawn_handle.spawn(
368        "telemetry-periodic-send",
369        None,
370        metrics_service.run(
371            client.clone(),
372            transaction_pool.clone(),
373            network.clone(),
374            sync_service.clone(),
375        ),
376    );
377
378    spawn_handle.spawn(
379        "substrate-informant",
380        None,
381        sc_informant::build(client.clone(), Arc::new(network), sync_service.clone()),
382    );
383
384    Ok((system_rpc_tx, sync_service))
385}
386
387type PartialComponents = sc_service::PartialComponents<
388    FullClient,
389    FullBackend,
390    FullSelectChain,
391    sc_consensus::DefaultImportQueue<Block>,
392    sc_transaction_pool::TransactionPoolHandle<Block, FullClient>,
393    Option<Telemetry>,
394>;
395
396/// Creates a partial node, for the chain ops commands.
397pub fn new_partial(
398    config: &Configuration,
399    bitcoin_network: bitcoin::Network,
400) -> Result<PartialComponents, ServiceError> {
401    let telemetry = config
402        .telemetry_endpoints
403        .clone()
404        .filter(|x| !x.is_empty())
405        .map(|endpoints| -> Result<_, sc_telemetry::Error> {
406            let worker = TelemetryWorker::new(16)?;
407            let telemetry = worker.handle().new_telemetry(endpoints);
408            Ok((worker, telemetry))
409        })
410        .transpose()?;
411
412    let executor = sc_service::new_native_or_wasm_executor(config);
413
414    let (client, backend, keystore_container, task_manager) =
415        sc_service::new_full_parts::<Block, RuntimeApi, _>(
416            config,
417            telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
418            executor,
419        )?;
420    let client = Arc::new(client);
421
422    let telemetry = telemetry.map(|(worker, telemetry)| {
423        task_manager
424            .spawn_handle()
425            .spawn("telemetry", None, worker.run());
426        telemetry
427    });
428
429    let select_chain = sc_consensus::LongestChain::new(backend.clone());
430
431    let transaction_pool = Arc::from(
432        sc_transaction_pool::Builder::new(
433            task_manager.spawn_essential_handle(),
434            client.clone(),
435            config.role.is_authority().into(),
436        )
437        .with_options(config.transaction_pool.clone())
438        .with_prometheus(config.prometheus_registry())
439        .build(),
440    );
441
442    let import_queue = BasicQueue::new(
443        SubstrateImportQueueVerifier::new(client.clone(), bitcoin_network),
444        Box::new(client.clone()),
445        None,
446        &task_manager.spawn_essential_handle(),
447        None,
448    );
449
450    Ok(sc_service::PartialComponents {
451        client,
452        backend,
453        task_manager,
454        import_queue,
455        keystore_container,
456        select_chain,
457        transaction_pool,
458        other: (telemetry),
459    })
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465    use crate::{NodeComponents, SubcoinConfiguration, new_node};
466    use subcoin_primitives::extract_bitcoin_block_hash;
467    use tokio::runtime::Handle;
468
469    #[tokio::test]
470    async fn test_bitcoin_block_hash_in_substrate_header() {
471        let runtime_handle = Handle::current();
472        let config = subcoin_test_service::test_configuration(runtime_handle);
473        let NodeComponents { client, .. } =
474            new_node(SubcoinConfiguration::test_config(&config)).expect("Failed to create node");
475
476        let substrate_genesis_header = client.header(client.info().genesis_hash).unwrap().unwrap();
477        let bitcoin_genesis_header =
478            bitcoin::constants::genesis_block(bitcoin::Network::Bitcoin).header;
479
480        let bitcoin_genesis_block_hash = bitcoin_genesis_header.block_hash();
481
482        assert_eq!(
483            extract_bitcoin_block_hash::<subcoin_runtime::interface::OpaqueBlock>(
484                &substrate_genesis_header
485            )
486            .unwrap(),
487            bitcoin_genesis_block_hash,
488        );
489    }
490}