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(array_windows, assert_matches, box_into_inner, more_qualified_paths)]
62
63mod aux_schema;
64mod bundle_processor;
65mod bundle_producer_election_solver;
66mod domain_block_processor;
67pub mod domain_bundle_producer;
68pub mod domain_bundle_proposer;
69mod domain_worker;
70mod fetch_domain_bootstrap_info;
71mod fraud_proof;
72mod operator;
73pub mod snap_sync;
74#[cfg(test)]
75mod tests;
76mod utils;
77
78pub use self::aux_schema::load_execution_receipt;
79pub use self::fetch_domain_bootstrap_info::{BootstrapResult, fetch_domain_bootstrap_info};
80pub use self::operator::Operator;
81pub use self::utils::{DomainBlockImportNotification, OperatorSlotInfo};
82pub use domain_worker::OpaqueBundleFor;
83use futures::Stream;
84use futures::channel::mpsc;
85use sc_client_api::{AuxStore, BlockImportNotification};
86use sc_consensus::BoxBlockImport;
87use sc_network::service::traits::NetworkService;
88use sc_network_sync::SyncingService;
89use sc_network_sync::block_relay_protocol::BlockDownloader;
90use sc_network_sync::service::network::NetworkServiceHandle;
91use sc_transaction_pool_api::OffchainTransactionPoolFactory;
92use sc_utils::mpsc::TracingUnboundedSender;
93use snap_sync::ConsensusChainSyncParams;
94use sp_blockchain::HeaderBackend;
95use sp_consensus::SyncOracle;
96use sp_consensus_slots::Slot;
97use sp_domain_digests::AsPredigest;
98use sp_domains::bundle::Bundle;
99use sp_domains::execution_receipt::ExecutionReceiptFor as ExecutionReceipt;
100use sp_domains::{DomainId, OperatorId};
101use sp_keystore::KeystorePtr;
102use sp_runtime::DigestItem;
103use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
104use std::marker::PhantomData;
105use std::sync::Arc;
106use std::sync::atomic::{AtomicBool, Ordering};
107use subspace_core_primitives::pot::PotOutput;
108use subspace_runtime_primitives::{Balance, BlockHashFor, ExtrinsicFor, HeaderFor};
109
110/// Domain sync oracle.
111///
112/// Sync oracle wrapper checks whether domain snap sync is finished in addition to the underlying
113/// sync oracle.
114#[derive(Debug, Clone)]
115pub struct DomainChainSyncOracle<SO>
116where
117    SO: SyncOracle + Send + Sync,
118{
119    domain_snap_sync_finished: Option<Arc<AtomicBool>>,
120    inner: SO,
121}
122
123impl<SO> SyncOracle for DomainChainSyncOracle<SO>
124where
125    SO: SyncOracle + Send + Sync,
126{
127    fn is_major_syncing(&self) -> bool {
128        self.inner.is_major_syncing()
129            || self
130                .domain_snap_sync_finished
131                .as_ref()
132                .map(|sync_finished| !sync_finished.load(Ordering::Acquire))
133                .unwrap_or_default()
134    }
135
136    fn is_offline(&self) -> bool {
137        self.inner.is_offline()
138    }
139}
140
141impl<SO> DomainChainSyncOracle<SO>
142where
143    SO: SyncOracle + Send + Sync,
144{
145    /// Create new instance
146    pub fn new(sync_oracle: SO, domain_snap_sync_finished: Option<Arc<AtomicBool>>) -> Self {
147        Self {
148            domain_snap_sync_finished,
149            inner: sync_oracle,
150        }
151    }
152}
153
154pub type ExecutionReceiptFor<Block, CBlock> = ExecutionReceipt<HeaderFor<Block>, CBlock, Balance>;
155
156type BundleSender<Block, CBlock> = TracingUnboundedSender<
157    Bundle<ExtrinsicFor<Block>, NumberFor<CBlock>, BlockHashFor<CBlock>, HeaderFor<Block>, Balance>,
158>;
159
160/// Notification streams from the consensus chain driving the executor.
161pub struct OperatorStreams<CBlock, IBNS, CIBNS, NSNS, ASS> {
162    /// Pause the consensus block import when the consensus chain client
163    /// runs much faster than the domain client.
164    pub consensus_block_import_throttling_buffer_size: u32,
165    /// Notification about to be imported.
166    ///
167    /// Fired before the completion of entire block import pipeline.
168    pub block_importing_notification_stream: IBNS,
169    /// Consensus block import notification from the client.
170    ///
171    /// Fired after the completion of entire block import pipeline.
172    pub imported_block_notification_stream: CIBNS,
173    /// New slot arrives.
174    pub new_slot_notification_stream: NSNS,
175    /// The acknowledgement sender only used in test to ensure all of
176    /// the operator's previous tasks are finished
177    pub acknowledgement_sender_stream: ASS,
178    pub _phantom: PhantomData<CBlock>,
179}
180
181type NewSlotNotification = (Slot, PotOutput);
182
183pub struct OperatorParams<
184    Block,
185    CBlock,
186    Client,
187    CClient,
188    TransactionPool,
189    Backend,
190    E,
191    IBNS,
192    CIBNS,
193    NSNS,
194    ASS,
195> where
196    Block: BlockT,
197    CBlock: BlockT,
198    IBNS: Stream<Item = (NumberFor<CBlock>, mpsc::Sender<()>)> + Send + 'static,
199    CIBNS: Stream<Item = BlockImportNotification<CBlock>> + Send + 'static,
200    NSNS: Stream<Item = NewSlotNotification> + Send + 'static,
201    ASS: Stream<Item = mpsc::Sender<()>> + Send + 'static,
202{
203    pub domain_id: DomainId,
204    pub domain_created_at: NumberFor<CBlock>,
205    pub consensus_client: Arc<CClient>,
206    pub consensus_offchain_tx_pool_factory: OffchainTransactionPoolFactory<CBlock>,
207    pub domain_sync_oracle: Arc<dyn SyncOracle + Send + Sync>,
208    pub client: Arc<Client>,
209    pub transaction_pool: Arc<TransactionPool>,
210    pub backend: Arc<Backend>,
211    pub code_executor: Arc<E>,
212    pub maybe_operator_id: Option<OperatorId>,
213    pub keystore: KeystorePtr,
214    pub bundle_sender: Arc<BundleSender<Block, CBlock>>,
215    pub operator_streams: OperatorStreams<CBlock, IBNS, CIBNS, NSNS, ASS>,
216    pub consensus_confirmation_depth_k: NumberFor<CBlock>,
217    pub challenge_period: NumberFor<CBlock>,
218    pub block_import: Arc<BoxBlockImport<Block>>,
219    pub skip_empty_bundle_production: bool,
220    pub skip_out_of_order_slot: bool,
221    pub sync_service: Arc<SyncingService<Block>>,
222    pub network_service: Arc<dyn NetworkService>,
223    pub block_downloader: Arc<dyn BlockDownloader<Block>>,
224    pub consensus_chain_sync_params: Option<ConsensusChainSyncParams<CBlock, Block::Header>>,
225    pub domain_fork_id: Option<String>,
226    pub domain_network_service_handle: NetworkServiceHandle,
227}
228
229pub fn load_execution_receipt_by_domain_hash<Block, CBlock, Client>(
230    domain_client: &Client,
231    domain_hash: Block::Hash,
232    domain_number: NumberFor<Block>,
233) -> Result<ExecutionReceiptFor<Block, CBlock>, sp_blockchain::Error>
234where
235    Block: BlockT,
236    CBlock: BlockT,
237    Client: AuxStore + HeaderBackend<Block>,
238{
239    let domain_header = domain_client.header(domain_hash)?.ok_or_else(|| {
240        sp_blockchain::Error::Backend(format!(
241            "Header for domain block {domain_hash}#{domain_number} not found"
242        ))
243    })?;
244
245    let consensus_block_hash = domain_header
246        .digest()
247        .convert_first(DigestItem::as_consensus_block_info)
248        .ok_or_else(|| {
249            sp_blockchain::Error::Application(format!(
250                "Domain block header {domain_hash}#{domain_number} must have consensus block info predigest"
251            ).into())
252        })?;
253
254    // Get receipt by consensus block hash
255    load_execution_receipt::<_, Block, CBlock>(domain_client, consensus_block_hash)?.ok_or_else(
256        || {
257            sp_blockchain::Error::Backend(format!(
258                "Receipt for consensus block {consensus_block_hash} and domain block \
259                {domain_hash}#{domain_number} not found"
260            ))
261        },
262    )
263}