subcoin_service/
lib.rs

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