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