pezsc_client_api/
client.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! A set of APIs supported by the client along with their primitives.
20
21use pezsp_consensus::BlockOrigin;
22use pezsp_core::storage::StorageKey;
23use pezsp_runtime::{
24	generic::SignedBlock,
25	traits::{Block as BlockT, NumberFor},
26	Justifications,
27};
28use std::{
29	collections::HashSet,
30	fmt::{self, Debug},
31	sync::Arc,
32};
33
34use crate::{
35	blockchain::Info, notifications::StorageEventStream, FinalizeSummary, ImportSummary, StaleBlock,
36};
37
38use pezsc_transaction_pool_api::ChainEvent;
39use pezsc_utils::mpsc::{TracingUnboundedReceiver, TracingUnboundedSender};
40use pezsp_blockchain;
41
42/// Type that implements `futures::Stream` of block import events.
43pub type ImportNotifications<Block> = TracingUnboundedReceiver<BlockImportNotification<Block>>;
44
45/// A stream of block finality notifications.
46pub type FinalityNotifications<Block> = TracingUnboundedReceiver<FinalityNotification<Block>>;
47
48/// Expected hashes of blocks at given heights.
49///
50/// This may be used as chain spec extension to set trusted checkpoints, i.e.
51/// the client will refuse to import a block with a different hash at the given
52/// height.
53pub type ForkBlocks<Block> = Option<Vec<(NumberFor<Block>, <Block as BlockT>::Hash)>>;
54
55/// Known bad block hashes.
56///
57/// This may be used as chain spec extension to filter out known, unwanted forks.
58pub type BadBlocks<Block> = Option<HashSet<<Block as BlockT>::Hash>>;
59
60/// Figure out the block type for a given type (for now, just a `Client`).
61pub trait BlockOf {
62	/// The type of the block.
63	type Type: BlockT;
64}
65
66/// A source of blockchain events.
67pub trait BlockchainEvents<Block: BlockT> {
68	/// Get block import event stream.
69	///
70	/// Not guaranteed to be fired for every imported block. Use
71	/// `every_import_notification_stream()` if you want a notification of every imported block
72	/// regardless.
73	///
74	/// The events for this notification stream are emitted:
75	/// - During initial sync process: if there is a re-org while importing blocks. See
76	/// [here](https://github.com/pezkuwichain/pezkuwi-sdk/issues/222#issuecomment-694091901) for the
77	/// rationale behind this.
78	/// - After initial sync process: on every imported block, regardless of whether it is
79	/// the new best block or not, causes a re-org or not.
80	fn import_notification_stream(&self) -> ImportNotifications<Block>;
81
82	/// Get a stream of every imported block.
83	fn every_import_notification_stream(&self) -> ImportNotifications<Block>;
84
85	/// Get a stream of finality notifications. Not guaranteed to be fired for every
86	/// finalized block.
87	fn finality_notification_stream(&self) -> FinalityNotifications<Block>;
88
89	/// Get storage changes event stream.
90	///
91	/// Passing `None` as `filter_keys` subscribes to all storage changes.
92	fn storage_changes_notification_stream(
93		&self,
94		filter_keys: Option<&[StorageKey]>,
95		child_filter_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
96	) -> pezsp_blockchain::Result<StorageEventStream<Block::Hash>>;
97}
98
99/// List of operations to be performed on storage aux data.
100/// First tuple element is the encoded data key.
101/// Second tuple element is the encoded optional data to write.
102/// If `None`, the key and the associated data are deleted from storage.
103pub type AuxDataOperations = Vec<(Vec<u8>, Option<Vec<u8>>)>;
104
105/// Callback invoked before committing the operations created during block import.
106/// This gives the opportunity to perform auxiliary pre-commit actions and optionally
107/// enqueue further storage write operations to be atomically performed on commit.
108pub type OnImportAction<Block> =
109	Box<dyn (Fn(&BlockImportNotification<Block>) -> AuxDataOperations) + Send>;
110
111/// Callback invoked before committing the operations created during block finalization.
112/// This gives the opportunity to perform auxiliary pre-commit actions and optionally
113/// enqueue further storage write operations to be atomically performed on commit.
114pub type OnFinalityAction<Block> =
115	Box<dyn (Fn(&FinalityNotification<Block>) -> AuxDataOperations) + Send>;
116
117/// Interface to perform auxiliary actions before committing a block import or
118/// finality operation.
119pub trait PreCommitActions<Block: BlockT> {
120	/// Actions to be performed on block import.
121	fn register_import_action(&self, op: OnImportAction<Block>);
122
123	/// Actions to be performed on block finalization.
124	fn register_finality_action(&self, op: OnFinalityAction<Block>);
125}
126
127/// Interface for fetching block data.
128pub trait BlockBackend<Block: BlockT> {
129	/// Get block body by ID. Returns `None` if the body is not stored.
130	fn block_body(
131		&self,
132		hash: Block::Hash,
133	) -> pezsp_blockchain::Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
134
135	/// Get all indexed transactions for a block,
136	/// including renewed transactions.
137	///
138	/// Note that this will only fetch transactions
139	/// that are indexed by the runtime with `storage_index_transaction`.
140	fn block_indexed_body(
141		&self,
142		hash: Block::Hash,
143	) -> pezsp_blockchain::Result<Option<Vec<Vec<u8>>>>;
144
145	/// Get full block by hash.
146	fn block(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<SignedBlock<Block>>>;
147
148	/// Get block status by block hash.
149	fn block_status(
150		&self,
151		hash: Block::Hash,
152	) -> pezsp_blockchain::Result<pezsp_consensus::BlockStatus>;
153
154	/// Get block justifications for the block with the given hash.
155	fn justifications(&self, hash: Block::Hash)
156		-> pezsp_blockchain::Result<Option<Justifications>>;
157
158	/// Get block hash by number.
159	fn block_hash(&self, number: NumberFor<Block>)
160		-> pezsp_blockchain::Result<Option<Block::Hash>>;
161
162	/// Get single indexed transaction by content hash.
163	///
164	/// Note that this will only fetch transactions
165	/// that are indexed by the runtime with `storage_index_transaction`.
166	fn indexed_transaction(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<Vec<u8>>>;
167
168	/// Check if transaction index exists.
169	fn has_indexed_transaction(&self, hash: Block::Hash) -> pezsp_blockchain::Result<bool> {
170		Ok(self.indexed_transaction(hash)?.is_some())
171	}
172
173	/// Tells whether the current client configuration requires full-sync mode.
174	fn requires_full_sync(&self) -> bool;
175}
176
177/// Provide a list of potential uncle headers for a given block.
178pub trait ProvideUncles<Block: BlockT> {
179	/// Gets the uncles of the block with `target_hash` going back `max_generation` ancestors.
180	fn uncles(
181		&self,
182		target_hash: Block::Hash,
183		max_generation: NumberFor<Block>,
184	) -> pezsp_blockchain::Result<Vec<Block::Header>>;
185}
186
187/// Client info
188#[derive(Debug, Clone)]
189pub struct ClientInfo<Block: BlockT> {
190	/// Best block hash.
191	pub chain: Info<Block>,
192	/// Usage info, if backend supports this.
193	pub usage: Option<UsageInfo>,
194}
195
196/// A wrapper to store the size of some memory.
197#[derive(Default, Clone, Debug, Copy)]
198pub struct MemorySize(usize);
199
200impl MemorySize {
201	/// Creates `Self` from the given `bytes` size.
202	pub fn from_bytes(bytes: usize) -> Self {
203		Self(bytes)
204	}
205
206	/// Returns the memory size as bytes.
207	pub fn as_bytes(self) -> usize {
208		self.0
209	}
210}
211
212impl fmt::Display for MemorySize {
213	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214		if self.0 < 1024 {
215			write!(f, "{} bytes", self.0)
216		} else if self.0 < 1024 * 1024 {
217			write!(f, "{:.2} KiB", self.0 as f64 / 1024f64)
218		} else if self.0 < 1024 * 1024 * 1024 {
219			write!(f, "{:.2} MiB", self.0 as f64 / (1024f64 * 1024f64))
220		} else {
221			write!(f, "{:.2} GiB", self.0 as f64 / (1024f64 * 1024f64 * 1024f64))
222		}
223	}
224}
225
226/// Memory statistics for client instance.
227#[derive(Default, Clone, Debug)]
228pub struct MemoryInfo {
229	/// Size of state cache.
230	pub state_cache: MemorySize,
231	/// Size of backend database cache.
232	pub database_cache: MemorySize,
233}
234
235/// I/O statistics for client instance.
236#[derive(Default, Clone, Debug)]
237pub struct IoInfo {
238	/// Number of transactions.
239	pub transactions: u64,
240	/// Total bytes read from disk.
241	pub bytes_read: u64,
242	/// Total bytes written to disk.
243	pub bytes_written: u64,
244	/// Total key writes to disk.
245	pub writes: u64,
246	/// Total key reads from disk.
247	pub reads: u64,
248	/// Average size of the transaction.
249	pub average_transaction_size: u64,
250	/// State reads (keys)
251	pub state_reads: u64,
252	/// State reads (keys) from cache.
253	pub state_reads_cache: u64,
254	/// State reads (keys)
255	pub state_writes: u64,
256	/// State write (keys) already cached.
257	pub state_writes_cache: u64,
258	/// State write (trie nodes) to backend db.
259	pub state_writes_nodes: u64,
260}
261
262/// Usage statistics for running client instance.
263///
264/// Returning backend determines the scope of these stats,
265/// but usually it is either from service start or from previous
266/// gathering of the statistics.
267#[derive(Default, Clone, Debug)]
268pub struct UsageInfo {
269	/// Memory statistics.
270	pub memory: MemoryInfo,
271	/// I/O statistics.
272	pub io: IoInfo,
273}
274
275impl fmt::Display for UsageInfo {
276	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277		write!(
278			f,
279			"caches: ({} state, {} db overlay), \
280			 i/o: ({} tx, {} write, {} read, {} avg tx, {}/{} key cache reads/total, {} trie nodes writes)",
281			self.memory.state_cache,
282			self.memory.database_cache,
283			self.io.transactions,
284			self.io.bytes_written,
285			self.io.bytes_read,
286			self.io.average_transaction_size,
287			self.io.state_reads_cache,
288			self.io.state_reads,
289			self.io.state_writes_nodes,
290		)
291	}
292}
293
294/// Sends a message to the pinning-worker once dropped to unpin a block in the backend.
295pub struct UnpinHandleInner<Block: BlockT> {
296	/// Hash of the block pinned by this handle
297	hash: Block::Hash,
298	unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
299}
300
301impl<Block: BlockT> Debug for UnpinHandleInner<Block> {
302	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303		f.debug_struct("UnpinHandleInner").field("pinned_block", &self.hash).finish()
304	}
305}
306
307impl<Block: BlockT> UnpinHandleInner<Block> {
308	/// Create a new [`UnpinHandleInner`]
309	pub fn new(
310		hash: Block::Hash,
311		unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
312	) -> Self {
313		Self { hash, unpin_worker_sender }
314	}
315}
316
317impl<Block: BlockT> Drop for UnpinHandleInner<Block> {
318	fn drop(&mut self) {
319		if let Err(err) =
320			self.unpin_worker_sender.unbounded_send(UnpinWorkerMessage::Unpin(self.hash))
321		{
322			log::debug!(target: "db", "Unable to unpin block with hash: {}, error: {:?}", self.hash, err);
323		};
324	}
325}
326
327/// Message that signals notification-based pinning actions to the pinning-worker.
328///
329/// When the notification is dropped, an `Unpin` message should be sent to the worker.
330#[derive(Debug)]
331pub enum UnpinWorkerMessage<Block: BlockT> {
332	/// Should be sent when a import or finality notification is created.
333	AnnouncePin(Block::Hash),
334	/// Should be sent when a import or finality notification is dropped.
335	Unpin(Block::Hash),
336}
337
338/// Keeps a specific block pinned while the handle is alive.
339/// Once the last handle instance for a given block is dropped, the
340/// block is unpinned in the [`Backend`](crate::backend::Backend::unpin_block).
341#[derive(Debug, Clone)]
342pub struct UnpinHandle<Block: BlockT>(Arc<UnpinHandleInner<Block>>);
343
344impl<Block: BlockT> UnpinHandle<Block> {
345	/// Create a new [`UnpinHandle`]
346	pub fn new(
347		hash: Block::Hash,
348		unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
349	) -> UnpinHandle<Block> {
350		UnpinHandle(Arc::new(UnpinHandleInner::new(hash, unpin_worker_sender)))
351	}
352
353	/// Hash of the block this handle is unpinning on drop
354	pub fn hash(&self) -> Block::Hash {
355		self.0.hash
356	}
357}
358
359/// Summary of an imported block
360#[derive(Clone, Debug)]
361pub struct BlockImportNotification<Block: BlockT> {
362	/// Imported block header hash.
363	pub hash: Block::Hash,
364	/// Imported block origin.
365	pub origin: BlockOrigin,
366	/// Imported block header.
367	pub header: Block::Header,
368	/// Is this the new best block.
369	pub is_new_best: bool,
370	/// Tree route from old best to new best parent.
371	///
372	/// If `None`, there was no re-org while importing.
373	pub tree_route: Option<Arc<pezsp_blockchain::TreeRoute<Block>>>,
374	/// Handle to unpin the block this notification is for
375	unpin_handle: UnpinHandle<Block>,
376}
377
378impl<Block: BlockT> BlockImportNotification<Block> {
379	/// Create new notification
380	pub fn new(
381		hash: Block::Hash,
382		origin: BlockOrigin,
383		header: Block::Header,
384		is_new_best: bool,
385		tree_route: Option<Arc<pezsp_blockchain::TreeRoute<Block>>>,
386		unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
387	) -> Self {
388		Self {
389			hash,
390			origin,
391			header,
392			is_new_best,
393			tree_route,
394			unpin_handle: UnpinHandle::new(hash, unpin_worker_sender),
395		}
396	}
397
398	/// Consume this notification and extract the unpin handle.
399	///
400	/// Note: Only use this if you want to keep the block pinned in the backend.
401	pub fn into_unpin_handle(self) -> UnpinHandle<Block> {
402		self.unpin_handle
403	}
404}
405
406/// Summary of a finalized block.
407#[derive(Clone, Debug)]
408pub struct FinalityNotification<Block: BlockT> {
409	/// Finalized block header hash.
410	pub hash: Block::Hash,
411	/// Finalized block header.
412	pub header: Block::Header,
413	/// Path from the old finalized to new finalized parent (implicitly finalized blocks).
414	///
415	/// This maps to the range `(old_finalized, new_finalized)`.
416	pub tree_route: Arc<[Block::Hash]>,
417	/// Stale blocks.
418	pub stale_blocks: Arc<[Arc<StaleBlock<Block>>]>,
419	/// Handle to unpin the block this notification is for
420	unpin_handle: UnpinHandle<Block>,
421}
422
423impl<B: BlockT> TryFrom<BlockImportNotification<B>> for ChainEvent<B> {
424	type Error = ();
425
426	fn try_from(n: BlockImportNotification<B>) -> Result<Self, ()> {
427		if n.is_new_best {
428			Ok(Self::NewBestBlock { hash: n.hash, tree_route: n.tree_route })
429		} else {
430			Err(())
431		}
432	}
433}
434
435impl<B: BlockT> From<FinalityNotification<B>> for ChainEvent<B> {
436	fn from(n: FinalityNotification<B>) -> Self {
437		Self::Finalized { hash: n.hash, tree_route: n.tree_route }
438	}
439}
440
441impl<Block: BlockT> FinalityNotification<Block> {
442	/// Create finality notification from finality summary.
443	pub fn from_summary(
444		mut summary: FinalizeSummary<Block>,
445		unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
446	) -> FinalityNotification<Block> {
447		let hash = summary.finalized.pop().unwrap_or_default();
448		FinalityNotification {
449			hash,
450			header: summary.header,
451			tree_route: Arc::from(summary.finalized),
452			stale_blocks: Arc::from(
453				summary.stale_blocks.into_iter().map(Arc::from).collect::<Vec<_>>(),
454			),
455			unpin_handle: UnpinHandle::new(hash, unpin_worker_sender),
456		}
457	}
458
459	/// Consume this notification and extract the unpin handle.
460	///
461	/// Note: Only use this if you want to keep the block pinned in the backend.
462	pub fn into_unpin_handle(self) -> UnpinHandle<Block> {
463		self.unpin_handle
464	}
465}
466
467impl<Block: BlockT> BlockImportNotification<Block> {
468	/// Create finality notification from finality summary.
469	pub fn from_summary(
470		summary: ImportSummary<Block>,
471		unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
472	) -> BlockImportNotification<Block> {
473		let hash = summary.hash;
474		BlockImportNotification {
475			hash,
476			origin: summary.origin,
477			header: summary.header,
478			is_new_best: summary.is_new_best,
479			tree_route: summary.tree_route.map(Arc::new),
480			unpin_handle: UnpinHandle::new(hash, unpin_worker_sender),
481		}
482	}
483}