executor/
db_bootstrapper.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8#![forbid(unsafe_code)]
9
10use crate::{vm::VMExecutor, Executor};
11use anyhow::{ensure, format_err, Result};
12use cached_pos_ledger_db::CachedPosLedgerDB;
13use consensus_types::db::FakeLedgerBlockDB;
14use diem_crypto::{hash::PRE_GENESIS_BLOCK_ID, HashValue};
15use diem_logger::prelude::*;
16use diem_state_view::{StateView, StateViewId};
17use diem_types::{
18    access_path::AccessPath,
19    account_address::AccountAddress,
20    account_config::diem_root_address,
21    block_info::{
22        BlockInfo, PivotBlockDecision, GENESIS_EPOCH, GENESIS_ROUND,
23        GENESIS_TIMESTAMP_USECS,
24    },
25    diem_timestamp::DiemTimestampResource,
26    ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
27    on_chain_config::{config_address, ConfigurationResource},
28    term_state::NodeID,
29    transaction::Transaction,
30    waypoint::Waypoint,
31};
32use executor_types::BlockExecutor;
33use move_core_types::move_resource::MoveResource;
34use pow_types::FakePowHandler;
35use std::{collections::btree_map::BTreeMap, sync::Arc};
36use storage_interface::{
37    state_view::VerifiedStateView, DbReaderWriter, TreeState,
38};
39
40pub fn generate_waypoint<V: VMExecutor>(
41    db: &DbReaderWriter, genesis_txn: &Transaction,
42) -> Result<Waypoint> {
43    let tree_state = db.reader.get_latest_tree_state()?;
44
45    // TODO(lpl): initial nodes are not passed.
46    // genesis ledger info (including pivot decision) is not used.
47    let committer = calculate_genesis::<V>(
48        db,
49        tree_state,
50        genesis_txn,
51        None,
52        Vec::new(),
53        Vec::new(),
54        Vec::new(),
55    )?;
56    Ok(committer.waypoint)
57}
58
59/// If current version + 1 != waypoint.version(), return Ok(false) indicating
60/// skipping the txn. otherwise apply the txn and commit it if the result
61/// matches the waypoint. Returns Ok(true) if committed otherwise Err.
62pub fn maybe_bootstrap<V: VMExecutor>(
63    db: &DbReaderWriter, genesis_txn: &Transaction, waypoint: Waypoint,
64    genesis_pivot_decision: Option<PivotBlockDecision>, initial_seed: Vec<u8>,
65    initial_nodes: Vec<(NodeID, u64)>,
66    initial_committee: Vec<(AccountAddress, u64)>,
67) -> Result<bool> {
68    let tree_state = db.reader.get_latest_tree_state()?;
69    // if the waypoint is not targeted with the genesis txn, it may be either
70    // already bootstrapped, or aiming for state sync to catch up.
71    if tree_state.num_transactions != waypoint.version() {
72        diem_info!(waypoint = %waypoint, "Skip genesis txn.");
73        return Ok(false);
74    }
75    diem_debug!(
76        "genesis_txn={:?}, initial_nodes={:?} ",
77        genesis_txn,
78        initial_nodes,
79    );
80
81    let committer = calculate_genesis::<V>(
82        db,
83        tree_state,
84        genesis_txn,
85        genesis_pivot_decision,
86        initial_seed,
87        initial_nodes,
88        initial_committee,
89    )?;
90    ensure!(
91        waypoint == committer.waypoint(),
92        "Waypoint verification failed. Expected {:?}, got {:?}.",
93        waypoint,
94        committer.waypoint(),
95    );
96    committer.commit()?;
97    Ok(true)
98}
99
100pub struct GenesisCommitter<V: VMExecutor> {
101    executor: Executor<V>,
102    ledger_info_with_sigs: LedgerInfoWithSignatures,
103    waypoint: Waypoint,
104}
105
106impl<V: VMExecutor> GenesisCommitter<V> {
107    pub fn new(
108        executor: Executor<V>, ledger_info_with_sigs: LedgerInfoWithSignatures,
109    ) -> Result<Self> {
110        let waypoint =
111            Waypoint::new_epoch_boundary(ledger_info_with_sigs.ledger_info())?;
112
113        Ok(Self {
114            executor,
115            ledger_info_with_sigs,
116            waypoint,
117        })
118    }
119
120    pub fn waypoint(&self) -> Waypoint { self.waypoint }
121
122    pub fn commit(self) -> Result<()> {
123        self.executor.commit_blocks(
124            vec![genesis_block_id()],
125            self.ledger_info_with_sigs,
126        )?;
127        diem_info!("Genesis commited.");
128        // DB bootstrapped, avoid anything that could fail after this.
129
130        Ok(())
131    }
132}
133
134pub fn calculate_genesis<V: VMExecutor>(
135    db: &DbReaderWriter, tree_state: TreeState, genesis_txn: &Transaction,
136    genesis_pivot_decision: Option<PivotBlockDecision>, initial_seed: Vec<u8>,
137    initial_nodes: Vec<(NodeID, u64)>,
138    initial_committee: Vec<(AccountAddress, u64)>,
139) -> Result<GenesisCommitter<V>> {
140    // DB bootstrapper works on either an empty transaction accumulator or an
141    // existing block chain. In the very extreme and sad situation of losing
142    // quorum among validators, we refer to the second use case said above.
143    let genesis_version = tree_state.num_transactions;
144    let db_with_cache = Arc::new(CachedPosLedgerDB::new_on_unbootstrapped_db(
145        db.clone(),
146        tree_state,
147        initial_seed,
148        initial_nodes,
149        initial_committee,
150        genesis_pivot_decision.clone(),
151    ));
152    let executor = Executor::<V>::new(
153        db_with_cache,
154        // This will not be used in genesis execution.
155        Arc::new(FakePowHandler {}),
156        Arc::new(FakeLedgerBlockDB {}),
157    );
158
159    let block_id = HashValue::zero();
160    let epoch = if genesis_version == 0 {
161        GENESIS_EPOCH
162    } else {
163        let executor_trees =
164            executor.get_executed_trees(*PRE_GENESIS_BLOCK_ID)?;
165        let state_view = executor.get_executed_state_view(
166            StateViewId::Miscellaneous,
167            &executor_trees,
168        );
169        get_state_epoch(&state_view)?
170    };
171
172    // Create a block with genesis_txn being the only txn. Execute it then
173    // commit it immediately.
174    let result = executor.execute_block(
175        (block_id, vec![genesis_txn.clone()]),
176        *PRE_GENESIS_BLOCK_ID,
177        // Use `catch_up_mode=false` for genesis to calculate VDF output.
178        false,
179    )?;
180
181    let root_hash = result.root_hash();
182    let next_epoch_state = result.epoch_state().as_ref().ok_or_else(|| {
183        format_err!("Genesis transaction must emit a epoch change.")
184    })?;
185    let executed_trees = executor.get_executed_trees(block_id)?;
186    let state_view = executor
187        .get_executed_state_view(StateViewId::Miscellaneous, &executed_trees);
188    diem_debug!(
189        "after genesis: epoch_state={:?}, pos_state={:?}",
190        next_epoch_state,
191        state_view.pos_state().epoch_state()
192    );
193    let timestamp_usecs = if genesis_version == 0 {
194        // TODO(aldenhu): fix existing tests before using real timestamp and
195        // check on-chain epoch.
196        GENESIS_TIMESTAMP_USECS
197    } else {
198        let next_epoch = epoch
199            .checked_add(1)
200            .ok_or_else(|| format_err!("integer overflow occurred"))?;
201
202        ensure!(
203            next_epoch == get_state_epoch(&state_view)?,
204            "Genesis txn didn't bump epoch."
205        );
206        get_state_timestamp(&state_view)?
207    };
208
209    let ledger_info_with_sigs = LedgerInfoWithSignatures::new(
210        LedgerInfo::new(
211            BlockInfo::new(
212                epoch,
213                GENESIS_ROUND,
214                block_id,
215                root_hash,
216                genesis_version,
217                timestamp_usecs,
218                Some(next_epoch_state.clone()),
219                genesis_pivot_decision,
220            ),
221            HashValue::zero(), /* consensus_data_hash */
222        ),
223        BTreeMap::default(), /* signatures */
224    );
225
226    let committer = GenesisCommitter::new(executor, ledger_info_with_sigs)?;
227    diem_info!(
228        "Genesis calculated: ledger_info_with_sigs {:?}, waypoint {:?}",
229        committer.ledger_info_with_sigs,
230        committer.waypoint,
231    );
232    Ok(committer)
233}
234
235fn get_state_timestamp(state_view: &VerifiedStateView) -> Result<u64> {
236    let rsrc_bytes = &state_view
237        .get(&AccessPath::new(
238            diem_root_address(),
239            DiemTimestampResource::resource_path(),
240        ))?
241        .ok_or_else(|| format_err!("DiemTimestampResource missing."))?;
242    let rsrc = bcs::from_bytes::<DiemTimestampResource>(&rsrc_bytes)?;
243    Ok(rsrc.diem_timestamp.microseconds)
244}
245
246fn get_state_epoch(state_view: &VerifiedStateView) -> Result<u64> {
247    let rsrc_bytes = &state_view
248        .get(&AccessPath::new(
249            config_address(),
250            ConfigurationResource::resource_path(),
251        ))?
252        .ok_or_else(|| format_err!("ConfigurationResource missing."))?;
253    let rsrc = bcs::from_bytes::<ConfigurationResource>(&rsrc_bytes)?;
254    Ok(rsrc.epoch())
255}
256
257fn genesis_block_id() -> HashValue { HashValue::zero() }