domain_client_operator/
lib.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3
4// Substrate 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// Substrate 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//! # Domain Operator
18//!
19//! ## Domains
20//!
21//! Domains, the enshrined rollups solution of Subspace, is a configurable execution
22//! framework allowing for the simple, secure and low-cost deployment of application
23//! specific blockchain called domain.
24//!
25//! ## Operators
26//!
27//! In Subspace, the farmers offering the storage resources are responsible for maintaining
28//! the consensus layer, operators are a separate class of contributors in the system focusing
29//! on the execution layer, they provide the necessary computational resources to maintain the
30//! blockchain state by running domains. Some deposits as the stake are required to be an operator.
31//!
32//! Specifically, operators have the responsibility of producing a [`Bundle`] which contains a
33//! number of [`ExecutionReceipt`]s on each slot notified from the consensus chain. The operators
34//! are primarily driven by two events from the consensus chain.
35//!
36//! - On each new slot, operators will attempt to solve a domain-specific bundle election
37//!   challenge derived from a global randomness provided by the consensus chain. Upon finding
38//!   a solution to the challenge, they will start producing a bundle: they will collect a set
39//!   of extrinsics from the transaction pool which are verified to be able to cover the transaction
40//!   fee. With these collected extrinsics, the bundle election solution and proper receipts, a
41//!   [`Bundle`] can be constructed and then be submitted to the consensus chain. The transactions
42//!   included in each bundle are uninterpretable blob from the consensus chain's perspective.
43//!
44//! - On each imported consensus block, operators will extract all the needed bundles from it
45//!   and convert the bundles to a list of extrinsics, construct a custom [`BlockBuilder`] to
46//!   build a domain block. The execution trace of all the extrinsics and hooks like
47//!   `initialize_block`/`finalize_block` will be recorded during the domain block execution.
48//!   Once the domain block is imported successfully, the [`ExecutionReceipt`] of this block
49//!   will be generated and stored locally.
50//!
51//! The receipt of each domain block contains all the intermediate state roots during the block
52//! execution, which will be gossiped in the domain subnet (in future). All operators whether
53//! running as an authority or a full node will compute each block and generate an execution receipt
54//! independently, once the execution receipt received from the network does not match the one
55//! produced locally, a [`FraudProof`] will be generated and reported to the consensus chain
56//! accordingly.
57//!
58//! [`BlockBuilder`]: ../domain_block_builder/struct.BlockBuilder.html
59//! [`FraudProof`]: ../sp_domains/struct.FraudProof.html
60
61#![feature(
62    array_windows,
63    assert_matches,
64    box_into_inner,
65    duration_constructors_lite,
66    more_qualified_paths
67)]
68
69mod aux_schema;
70mod bundle_processor;
71mod bundle_producer_election_solver;
72mod domain_block_processor;
73pub mod domain_bundle_producer;
74pub mod domain_bundle_proposer;
75mod domain_worker;
76mod fetch_domain_bootstrap_info;
77mod fraud_proof;
78mod operator;
79pub mod snap_sync;
80#[cfg(test)]
81mod tests;
82mod utils;
83
84pub use self::aux_schema::load_execution_receipt;
85pub use self::fetch_domain_bootstrap_info::{BootstrapResult, fetch_domain_bootstrap_info};
86pub use self::operator::Operator;
87pub use self::utils::{DomainBlockImportNotification, OperatorSlotInfo};
88pub use domain_worker::OpaqueBundleFor;
89use futures::Stream;
90use futures::channel::mpsc;
91use sc_client_api::{AuxStore, BlockImportNotification};
92use sc_consensus::BoxBlockImport;
93use sc_network::service::traits::NetworkService;
94use sc_network_sync::SyncingService;
95use sc_network_sync::block_relay_protocol::BlockDownloader;
96use sc_network_sync::service::network::NetworkServiceHandle;
97use sc_transaction_pool_api::OffchainTransactionPoolFactory;
98use sc_utils::mpsc::TracingUnboundedSender;
99use snap_sync::ConsensusChainSyncParams;
100use sp_blockchain::HeaderBackend;
101use sp_consensus::SyncOracle;
102use sp_consensus_slots::Slot;
103use sp_domain_digests::AsPredigest;
104use sp_domains::bundle::Bundle;
105use sp_domains::execution_receipt::ExecutionReceiptFor as ExecutionReceipt;
106use sp_domains::{DomainId, OperatorId};
107use sp_keystore::KeystorePtr;
108use sp_runtime::DigestItem;
109use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
110use std::marker::PhantomData;
111use std::sync::Arc;
112use std::sync::atomic::{AtomicBool, Ordering};
113use subspace_core_primitives::pot::PotOutput;
114use subspace_runtime_primitives::{Balance, BlockHashFor, ExtrinsicFor, HeaderFor};
115
116/// Domain sync oracle.
117///
118/// Sync oracle wrapper checks whether domain snap sync is finished in addition to the underlying
119/// sync oracle.
120#[derive(Debug, Clone)]
121pub struct DomainChainSyncOracle<SO>
122where
123    SO: SyncOracle + Send + Sync,
124{
125    domain_snap_sync_finished: Option<Arc<AtomicBool>>,
126    inner: SO,
127}
128
129impl<SO> SyncOracle for DomainChainSyncOracle<SO>
130where
131    SO: SyncOracle + Send + Sync,
132{
133    fn is_major_syncing(&self) -> bool {
134        self.inner.is_major_syncing()
135            || self
136                .domain_snap_sync_finished
137                .as_ref()
138                .map(|sync_finished| !sync_finished.load(Ordering::Acquire))
139                .unwrap_or_default()
140    }
141
142    fn is_offline(&self) -> bool {
143        self.inner.is_offline()
144    }
145}
146
147impl<SO> DomainChainSyncOracle<SO>
148where
149    SO: SyncOracle + Send + Sync,
150{
151    /// Create new instance
152    pub fn new(sync_oracle: SO, domain_snap_sync_finished: Option<Arc<AtomicBool>>) -> Self {
153        Self {
154            domain_snap_sync_finished,
155            inner: sync_oracle,
156        }
157    }
158}
159
160pub type ExecutionReceiptFor<Block, CBlock> = ExecutionReceipt<HeaderFor<Block>, CBlock, Balance>;
161
162type BundleSender<Block, CBlock> = TracingUnboundedSender<
163    Bundle<ExtrinsicFor<Block>, NumberFor<CBlock>, BlockHashFor<CBlock>, HeaderFor<Block>, Balance>,
164>;
165
166/// Notification streams from the consensus chain driving the executor.
167pub struct OperatorStreams<CBlock, IBNS, CIBNS, NSNS, ASS> {
168    /// Pause the consensus block import when the consensus chain client
169    /// runs much faster than the domain client.
170    pub consensus_block_import_throttling_buffer_size: u32,
171    /// Notification about to be imported.
172    ///
173    /// Fired before the completion of entire block import pipeline.
174    pub block_importing_notification_stream: IBNS,
175    /// Consensus block import notification from the client.
176    ///
177    /// Fired after the completion of entire block import pipeline.
178    pub imported_block_notification_stream: CIBNS,
179    /// New slot arrives.
180    pub new_slot_notification_stream: NSNS,
181    /// The acknowledgement sender only used in test to ensure all of
182    /// the operator's previous tasks are finished
183    pub acknowledgement_sender_stream: ASS,
184    pub _phantom: PhantomData<CBlock>,
185}
186
187type NewSlotNotification = (Slot, PotOutput);
188
189pub struct OperatorParams<
190    Block,
191    CBlock,
192    Client,
193    CClient,
194    TransactionPool,
195    Backend,
196    E,
197    IBNS,
198    CIBNS,
199    NSNS,
200    ASS,
201> where
202    Block: BlockT,
203    CBlock: BlockT,
204    IBNS: Stream<Item = (NumberFor<CBlock>, mpsc::Sender<()>)> + Send + 'static,
205    CIBNS: Stream<Item = BlockImportNotification<CBlock>> + Send + 'static,
206    NSNS: Stream<Item = NewSlotNotification> + Send + 'static,
207    ASS: Stream<Item = mpsc::Sender<()>> + Send + 'static,
208{
209    pub domain_id: DomainId,
210    pub domain_created_at: NumberFor<CBlock>,
211    pub consensus_client: Arc<CClient>,
212    pub consensus_offchain_tx_pool_factory: OffchainTransactionPoolFactory<CBlock>,
213    pub domain_sync_oracle: Arc<dyn SyncOracle + Send + Sync>,
214    pub client: Arc<Client>,
215    pub transaction_pool: Arc<TransactionPool>,
216    pub backend: Arc<Backend>,
217    pub code_executor: Arc<E>,
218    pub maybe_operator_id: Option<OperatorId>,
219    pub keystore: KeystorePtr,
220    pub bundle_sender: Arc<BundleSender<Block, CBlock>>,
221    pub operator_streams: OperatorStreams<CBlock, IBNS, CIBNS, NSNS, ASS>,
222    pub consensus_confirmation_depth_k: NumberFor<CBlock>,
223    pub challenge_period: NumberFor<CBlock>,
224    pub block_import: Arc<BoxBlockImport<Block>>,
225    pub skip_empty_bundle_production: bool,
226    pub skip_out_of_order_slot: bool,
227    pub sync_service: Arc<SyncingService<Block>>,
228    pub network_service: Arc<dyn NetworkService>,
229    pub block_downloader: Arc<dyn BlockDownloader<Block>>,
230    pub consensus_chain_sync_params: Option<ConsensusChainSyncParams<CBlock, Block::Header>>,
231    pub domain_fork_id: Option<String>,
232    pub domain_network_service_handle: NetworkServiceHandle,
233}
234
235pub fn load_execution_receipt_by_domain_hash<Block, CBlock, Client>(
236    domain_client: &Client,
237    domain_hash: Block::Hash,
238    domain_number: NumberFor<Block>,
239) -> Result<ExecutionReceiptFor<Block, CBlock>, sp_blockchain::Error>
240where
241    Block: BlockT,
242    CBlock: BlockT,
243    Client: AuxStore + HeaderBackend<Block>,
244{
245    let domain_header = domain_client.header(domain_hash)?.ok_or_else(|| {
246        sp_blockchain::Error::Backend(format!(
247            "Header for domain block {domain_hash}#{domain_number} not found"
248        ))
249    })?;
250
251    let consensus_block_hash = domain_header
252        .digest()
253        .convert_first(DigestItem::as_consensus_block_info)
254        .ok_or_else(|| {
255            sp_blockchain::Error::Application(format!(
256                "Domain block header {domain_hash}#{domain_number} must have consensus block info predigest"
257            ).into())
258        })?;
259
260    // Get receipt by consensus block hash
261    load_execution_receipt::<_, Block, CBlock>(domain_client, consensus_block_hash)?.ok_or_else(
262        || {
263            sp_blockchain::Error::Backend(format!(
264                "Receipt for consensus block {consensus_block_hash} and domain block \
265                {domain_hash}#{domain_number} not found"
266            ))
267        },
268    )
269}