/home/runner/work/creditcoin/creditcoin/node/src/service.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. |
2 | | |
3 | | mod nonce_monitor; |
4 | | |
5 | | use crate::cli::Cli; |
6 | | use codec::Encode; |
7 | | use creditcoin_node_runtime::{self, opaque::Block, RuntimeApi}; |
8 | | use sc_client_api::{Backend, ExecutorProvider}; |
9 | | pub use sc_executor::NativeElseWasmExecutor; |
10 | | use sc_keystore::LocalKeystore; |
11 | | use sc_service::{ |
12 | | error::Error as ServiceError, Configuration, TaskManager, TransactionPoolOptions, |
13 | | }; |
14 | | use sc_telemetry::{Telemetry, TelemetryWorker}; |
15 | | use sc_transaction_pool::PoolLimit; |
16 | | use sha3pow::Sha3Algorithm; |
17 | | use sp_inherents::CreateInherentDataProviders; |
18 | | use sp_runtime::{app_crypto::Ss58Codec, offchain::DbExternalities, traits::IdentifyAccount}; |
19 | | use std::{sync::Arc, thread, time::Duration}; |
20 | | |
21 | | // Our native executor instance. |
22 | | pub struct ExecutorDispatch; |
23 | | |
24 | | impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { |
25 | | type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; |
26 | | |
27 | 0 | fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> { |
28 | 0 | creditcoin_node_runtime::api::dispatch(method, data) |
29 | 0 | } |
30 | | |
31 | 0 | fn native_version() -> sc_executor::NativeVersion { |
32 | 0 | creditcoin_node_runtime::native_version() |
33 | 0 | } |
34 | | } |
35 | | |
36 | | type FullClient = |
37 | | sc_service::TFullClient<Block, RuntimeApi, NativeElseWasmExecutor<ExecutorDispatch>>; |
38 | | type FullBackend = sc_service::TFullBackend<Block>; |
39 | | type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>; |
40 | | type PartialComponentsType<T> = sc_service::PartialComponents< |
41 | | FullClient, |
42 | | FullBackend, |
43 | | FullSelectChain, |
44 | | sc_consensus::DefaultImportQueue<Block, FullClient>, |
45 | | sc_transaction_pool::FullPool<Block, FullClient>, |
46 | | ( |
47 | | sc_consensus_pow::PowBlockImport< |
48 | | Block, |
49 | | Arc<FullClient>, |
50 | | FullClient, |
51 | | FullSelectChain, |
52 | | Sha3Algorithm<FullClient>, |
53 | | sp_consensus::CanAuthorWithNativeVersion< |
54 | | <FullClient as ExecutorProvider<Block>>::Executor, |
55 | | >, |
56 | | T, |
57 | | >, |
58 | | Option<Telemetry>, |
59 | | ), |
60 | | >; |
61 | | |
62 | | /// Creates a transaction pool config where the limits are 5x the default, unless a limit has been set higher manually |
63 | 0 | fn create_transaction_pool_config(mut config: TransactionPoolOptions) -> TransactionPoolOptions { |
64 | 0 | let set_limit = |limit: &mut PoolLimit, default: &PoolLimit| { |
65 | 0 | // set the value to `max(5 * default_value, current_value)` |
66 | 0 | let new_setting = |curr: usize, def: usize| curr.max(def.saturating_mul(5)); |
67 | 0 |
|
68 | 0 | limit.count = new_setting(limit.count, default.count); |
69 | 0 | limit.total_bytes = new_setting(limit.total_bytes, default.total_bytes); |
70 | 0 | }; |
71 | 0 | let default = TransactionPoolOptions::default(); |
72 | 0 | set_limit(&mut config.future, &default.future); |
73 | 0 | set_limit(&mut config.ready, &default.ready); |
74 | 0 | config |
75 | 0 | } |
76 | | |
77 | 0 | pub fn new_partial( |
78 | 0 | config: &Configuration, |
79 | 0 | ) -> Result<PartialComponentsType<impl CreateInherentDataProviders<Block, ()>>, ServiceError> { |
80 | 0 | if config.keystore_remote.is_some() { |
81 | 0 | return Err(ServiceError::Other("Remote Keystores are not supported.".to_string())); |
82 | 0 | } |
83 | | |
84 | 0 | let telemetry = config |
85 | 0 | .telemetry_endpoints |
86 | 0 | .clone() |
87 | 0 | .filter(|x| !x.is_empty()) |
88 | 0 | .map(|endpoints| -> Result<_, sc_telemetry::Error> { |
89 | 0 | let worker = TelemetryWorker::new(16)?; |
90 | 0 | let telemetry = worker.handle().new_telemetry(endpoints); |
91 | 0 | Ok((worker, telemetry)) |
92 | 0 | }) |
93 | 0 | .transpose()?; |
94 | | |
95 | 0 | let executor = NativeElseWasmExecutor::<ExecutorDispatch>::new( |
96 | 0 | config.wasm_method, |
97 | 0 | config.default_heap_pages, |
98 | 0 | config.max_runtime_instances, |
99 | 0 | config.runtime_cache_size, |
100 | 0 | ); |
101 | | |
102 | 0 | let (client, backend, keystore_container, task_manager) = |
103 | 0 | sc_service::new_full_parts::<Block, RuntimeApi, _>( |
104 | 0 | config, |
105 | 0 | telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), |
106 | 0 | executor, |
107 | 0 | )?; |
108 | 0 | let client = Arc::new(client); |
109 | 0 |
|
110 | 0 | let telemetry = telemetry.map(|(worker, telemetry)| { |
111 | 0 | task_manager.spawn_handle().spawn("telemetry", "telemetry_tasks", worker.run()); |
112 | 0 | telemetry |
113 | 0 | }); |
114 | 0 |
|
115 | 0 | let select_chain = sc_consensus::LongestChain::new(backend.clone()); |
116 | 0 |
|
117 | 0 | let tx_pool_config = create_transaction_pool_config(config.transaction_pool.clone()); |
118 | 0 | let transaction_pool = sc_transaction_pool::BasicPool::new_full( |
119 | 0 | tx_pool_config, |
120 | 0 | config.role.is_authority().into(), |
121 | 0 | config.prometheus_registry(), |
122 | 0 | task_manager.spawn_essential_handle(), |
123 | 0 | client.clone(), |
124 | 0 | ); |
125 | 0 |
|
126 | 0 | let can_author_with = sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone()); |
127 | 0 |
|
128 | 0 | let algorithm = Sha3Algorithm::new(client.clone()); |
129 | 0 |
|
130 | 0 | let pow_block_import = sc_consensus_pow::PowBlockImport::new( |
131 | 0 | client.clone(), |
132 | 0 | client.clone(), |
133 | 0 | algorithm.clone(), |
134 | 0 | 0, |
135 | 0 | select_chain.clone(), |
136 | 0 | move |_, ()| async move { |
137 | 0 | let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); |
138 | 0 | Ok(timestamp) |
139 | 0 | }, |
140 | 0 | can_author_with, |
141 | 0 | ); |
142 | | |
143 | 0 | let import_queue = sc_consensus_pow::import_queue( |
144 | 0 | Box::new(pow_block_import.clone()), |
145 | 0 | None, |
146 | 0 | algorithm, |
147 | 0 | &task_manager.spawn_essential_handle(), |
148 | 0 | config.prometheus_registry(), |
149 | 0 | )?; |
150 | | |
151 | 0 | Ok(sc_service::PartialComponents { |
152 | 0 | client, |
153 | 0 | backend, |
154 | 0 | task_manager, |
155 | 0 | import_queue, |
156 | 0 | keystore_container, |
157 | 0 | select_chain, |
158 | 0 | transaction_pool, |
159 | 0 | other: (pow_block_import, telemetry), |
160 | 0 | }) |
161 | 0 | } |
162 | | |
163 | 0 | fn remote_keystore(_url: &str) -> Result<Arc<LocalKeystore>, &'static str> { |
164 | 0 | // FIXME: here would the concrete keystore be built, |
165 | 0 | // must return a concrete type (NOT `LocalKeystore`) that |
166 | 0 | // implements `CryptoStore` and `SyncCryptoStore` |
167 | 0 | Err("Remote Keystore not supported.") |
168 | 0 | } |
169 | | |
170 | | pub fn decode_mining_key( |
171 | | mining_key: Option<&str>, |
172 | | ) -> Result<creditcoin_node_runtime::AccountId, String> { |
173 | 0 | if let Some(key) = mining_key { |
174 | | // raw public key |
175 | 0 | if let Some(key_without_prefix) = key.strip_prefix("0x") { |
176 | 0 | let key_bytes = hex::decode(&key_without_prefix) |
177 | 0 | .map_err(|e| format!("Invalid mining key, expected hex: {}", e))?; |
178 | | Ok(creditcoin_node_runtime::Signer::from( |
179 | 0 | sp_core::ecdsa::Public::from_full(&key_bytes) |
180 | 0 | .map_err(|_| String::from("Invalid mining key, expected 33 bytes"))?, |
181 | | ) |
182 | 0 | .into_account()) |
183 | | } else { |
184 | | // ss58 encoded key |
185 | 0 | match sp_core::ecdsa::Public::from_ss58check(key) { |
186 | 0 | Ok(key) => Ok(creditcoin_node_runtime::Signer::from(key).into_account()), |
187 | 0 | Err(err) => match creditcoin_node_runtime::AccountId::from_ss58check(key) { |
188 | 0 | Ok(account_id) => Ok(account_id), |
189 | 0 | Err(e) => { |
190 | 0 | let msg = format!("Invalid mining key, failed to interpret it as an ECDSA public key (error: {}) and as an account ID (error: {})", err, e); |
191 | 0 | log::error!("{}", msg); |
192 | 0 | Err(msg) |
193 | | }, |
194 | | }, |
195 | | } |
196 | | } |
197 | | } else { |
198 | 0 | Err("The node is configured for mining but is missing a mining key".into()) |
199 | | } |
200 | 0 | } |
201 | | |
202 | | /// Builds a new service for a full client. |
203 | 0 | pub fn new_full(config: Configuration, cli: Cli) -> Result<TaskManager, ServiceError> { |
204 | 0 | let Cli { |
205 | 0 | rpc_mapping, mining_key, mining_threads, monitor_nonce: monitor_nonce_account, .. |
206 | | } = cli; |
207 | | |
208 | | let sc_service::PartialComponents { |
209 | 0 | client, |
210 | 0 | backend, |
211 | 0 | mut task_manager, |
212 | 0 | import_queue, |
213 | 0 | mut keystore_container, |
214 | 0 | select_chain, |
215 | 0 | transaction_pool, |
216 | 0 | other: (pow_block_import, mut telemetry), |
217 | 0 | } = new_partial(&config)?; |
218 | | |
219 | 0 | if let Some(url) = &config.keystore_remote { |
220 | 0 | match remote_keystore(url) { |
221 | 0 | Ok(k) => keystore_container.set_remote_keystore(k), |
222 | 0 | Err(e) => { |
223 | 0 | return Err(ServiceError::Other(format!( |
224 | 0 | "Error hooking up remote keystore for {}: {}", |
225 | 0 | url, e |
226 | 0 | ))) |
227 | | }, |
228 | | }; |
229 | 0 | } |
230 | | |
231 | 0 | let (network, system_rpc_tx, network_starter) = |
232 | 0 | sc_service::build_network(sc_service::BuildNetworkParams { |
233 | 0 | config: &config, |
234 | 0 | client: client.clone(), |
235 | 0 | transaction_pool: transaction_pool.clone(), |
236 | 0 | spawn_handle: task_manager.spawn_handle(), |
237 | 0 | import_queue, |
238 | 0 | block_announce_validator_builder: None, |
239 | 0 | warp_sync: None, |
240 | 0 | })?; |
241 | | |
242 | 0 | if config.offchain_worker.enabled { |
243 | 0 | sc_service::build_offchain_workers( |
244 | 0 | &config, |
245 | 0 | task_manager.spawn_handle(), |
246 | 0 | client.clone(), |
247 | 0 | network.clone(), |
248 | 0 | ); |
249 | 0 | if let Some(mapping) = rpc_mapping { |
250 | 0 | let storage = backend.offchain_storage().unwrap(); |
251 | 0 | let mut offchain_db = sc_offchain::OffchainDb::new(storage); |
252 | 0 | for (chain, uri) in mapping { |
253 | 0 | let mut key = Vec::from(chain.as_bytes()); |
254 | 0 | key.extend("-rpc-uri".bytes()); |
255 | 0 | offchain_db.local_storage_set( |
256 | 0 | sp_core::offchain::StorageKind::PERSISTENT, |
257 | 0 | &key, |
258 | 0 | &uri.encode(), |
259 | 0 | ); |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } |
263 | | |
264 | 0 | let role = config.role.clone(); |
265 | 0 | let _force_authoring = config.force_authoring; |
266 | 0 | let _backoff_authoring_blocks: Option<()> = None; |
267 | 0 | let _name = config.network.node_name.clone(); |
268 | 0 | let _enable_grandpa = !config.disable_grandpa; |
269 | 0 | let prometheus_registry = config.prometheus_registry().cloned(); |
270 | 0 | let mining_metrics = primitives::metrics::MiningMetrics::new(prometheus_registry.as_ref())?; |
271 | | |
272 | 0 | let rpc_extensions_builder = { |
273 | 0 | let client = client.clone(); |
274 | 0 | let pool = transaction_pool.clone(); |
275 | 0 |
|
276 | 0 | let mining_metrics = mining_metrics.clone(); |
277 | 0 | Box::new(move |deny_unsafe, _| { |
278 | 0 | let deps = crate::rpc::FullDeps { |
279 | 0 | client: client.clone(), |
280 | 0 | pool: pool.clone(), |
281 | 0 | deny_unsafe, |
282 | 0 | mining_metrics: mining_metrics.clone(), |
283 | 0 | }; |
284 | 0 |
|
285 | 0 | Ok(crate::rpc::create_full(deps)) |
286 | 0 | }) |
287 | | }; |
288 | | |
289 | 0 | let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { |
290 | 0 | network: network.clone(), |
291 | 0 | client: client.clone(), |
292 | 0 | keystore: keystore_container.sync_keystore(), |
293 | 0 | task_manager: &mut task_manager, |
294 | 0 | transaction_pool: transaction_pool.clone(), |
295 | 0 | rpc_extensions_builder, |
296 | 0 | backend: backend.clone(), |
297 | 0 | system_rpc_tx, |
298 | 0 | config, |
299 | 0 | telemetry: telemetry.as_mut(), |
300 | 0 | })?; |
301 | | |
302 | 0 | if let Some(monitor_target) = monitor_nonce_account { |
303 | 0 | if let Some(registry) = prometheus_registry.clone() { |
304 | 0 | task_manager.spawn_handle().spawn("nonce_metrics", None, { |
305 | 0 | nonce_monitor::task(nonce_monitor::TaskArgs { |
306 | 0 | registry, |
307 | 0 | monitor_target, |
308 | 0 | handlers: rpc_handlers, |
309 | 0 | backend, |
310 | 0 | keystore: keystore_container.keystore(), |
311 | 0 | }) |
312 | 0 | }); |
313 | 0 | } |
314 | 0 | } |
315 | | |
316 | 0 | if role.is_authority() { |
317 | 0 | let mining_key = decode_mining_key(mining_key.as_deref())?; |
318 | 0 | let proposer_factory = sc_basic_authorship::ProposerFactory::new( |
319 | 0 | task_manager.spawn_handle(), |
320 | 0 | client.clone(), |
321 | 0 | transaction_pool, |
322 | 0 | prometheus_registry.as_ref(), |
323 | 0 | telemetry.as_ref().map(|x| x.handle()), |
324 | 0 | ); |
325 | 0 |
|
326 | 0 | let can_author_with = |
327 | 0 | sp_consensus::CanAuthorWithNativeVersion::new(client.executor().clone()); |
328 | 0 |
|
329 | 0 | let algorithm = Sha3Algorithm::new(client.clone()); |
330 | 0 |
|
331 | 0 | let (worker, worker_task) = sc_consensus_pow::start_mining_worker( |
332 | 0 | Box::new(pow_block_import), |
333 | 0 | client.clone(), |
334 | 0 | select_chain, |
335 | 0 | algorithm, |
336 | 0 | proposer_factory, |
337 | 0 | network.clone(), |
338 | 0 | network, |
339 | 0 | Some(mining_key.encode()), |
340 | 0 | move |_, ()| async move { |
341 | 0 | let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); |
342 | 0 | Ok(timestamp) |
343 | 0 | }, |
344 | 0 | Duration::from_secs(10), |
345 | 0 | Duration::from_secs(10), |
346 | 0 | can_author_with, |
347 | 0 | ); |
348 | 0 |
|
349 | 0 | task_manager |
350 | 0 | .spawn_essential_handle() |
351 | 0 | .spawn_blocking("pow", "pow_group", worker_task); |
352 | 0 |
|
353 | 0 | let threads = mining_threads.unwrap_or_else(num_cpus::get); |
354 | 0 | for _ in 0..threads { |
355 | 0 | if let Some(keystore) = keystore_container.local_keystore() { |
356 | 0 | let worker = worker.clone(); |
357 | 0 | let client = client.clone(); |
358 | 0 | let mining_metrics = mining_metrics.clone(); |
359 | 0 | thread::spawn(move || { |
360 | 0 | let mut count = 0; |
361 | 0 | loop { |
362 | 0 | let metadata = worker.metadata(); |
363 | 0 | let version = worker.version(); |
364 | 0 | if let Some(metadata) = metadata { |
365 | | loop { |
366 | | match sha3pow::mine( |
367 | 0 | client.as_ref(), |
368 | 0 | &keystore, |
369 | 0 | &metadata.pre_hash, |
370 | 0 | metadata.pre_runtime.as_ref().map(|v| &v[..]), |
371 | 0 | metadata.difficulty, |
372 | | ) { |
373 | 0 | Ok(Some(seal)) => { |
374 | 0 | if version == worker.version() { |
375 | 0 | let _ = |
376 | 0 | futures_lite::future::block_on(worker.submit(seal)); |
377 | 0 | } |
378 | | }, |
379 | 0 | Ok(None) => { |
380 | 0 | count += 1; |
381 | 0 | }, |
382 | 0 | Err(e) => eprintln!("Mining error: {}", e), |
383 | | } |
384 | 0 | if count >= 1_000_000 { |
385 | 0 | mining_metrics.add(count); |
386 | 0 | count = 0; |
387 | 0 | } |
388 | 0 | if version != worker.version() { |
389 | 0 | break; |
390 | 0 | } |
391 | | } |
392 | 0 | } |
393 | | } |
394 | 0 | }); |
395 | 0 | } |
396 | | } |
397 | 0 | } |
398 | | |
399 | | // if the node isn't actively participating in consensus then it doesn't |
400 | | // need a keystore, regardless of which protocol we use below. |
401 | 0 | let _keystore = |
402 | 0 | if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None }; |
403 | | |
404 | 0 | network_starter.start_network(); |
405 | 0 | Ok(task_manager) |
406 | 0 | } |