executor/
lib.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 std::{
11    collections::{BTreeMap, HashSet},
12    sync::Arc,
13};
14
15use anyhow::{anyhow, bail, ensure, format_err, Result};
16use fail::fail_point;
17
18use cached_pos_ledger_db::CachedPosLedgerDB;
19use cfx_types::H256;
20use consensus_types::db::LedgerBlockRW;
21use diem_crypto::{
22    hash::{CryptoHash, EventAccumulatorHasher, PRE_GENESIS_BLOCK_ID},
23    HashValue,
24};
25use diem_logger::prelude::*;
26use diem_state_view::StateViewId;
27use diem_types::{
28    block_info::PivotBlockDecision,
29    committed_block::CommittedBlock,
30    epoch_state::EpochState,
31    ledger_info::LedgerInfoWithSignatures,
32    on_chain_config::{self, ValidatorSet},
33    proof::accumulator::InMemoryAccumulator,
34    reward_distribution_event::{RewardDistributionEventV2, VoteCount},
35    term_state::{
36        ElectionEvent, RegisterEvent, RetireEvent, UpdateVotingPowerEvent,
37    },
38    transaction::{
39        Transaction, TransactionInfo, TransactionOutput, TransactionStatus,
40        TransactionToCommit, Version,
41    },
42};
43use executor_types::{
44    BlockExecutor, Error, ExecutedTrees, ProcessedVMOutput, StateComputeResult,
45    TransactionData,
46};
47use pow_types::PowInterface;
48use storage_interface::state_view::VerifiedStateView;
49
50use crate::{
51    logging::{LogEntry, LogSchema},
52    vm::PosVM,
53};
54use diem_types::term_state::{
55    decode_dispute_event,
56    pos_state_config::{PosStateConfigTrait, POS_STATE_CONFIG},
57};
58
59pub mod db_bootstrapper;
60mod logging;
61pub mod vm;
62
63/// `Executor` implements all functionalities the execution module needs to
64/// provide.
65pub struct Executor {
66    db_with_cache: Arc<CachedPosLedgerDB>,
67    consensus_db: Arc<dyn LedgerBlockRW>,
68    pow_handler: Arc<dyn PowInterface>,
69}
70
71impl Executor {
72    pub fn committed_block_id(&self) -> HashValue {
73        self.db_with_cache.committed_block_id()
74    }
75
76    /// Constructs an `Executor`.
77    pub fn new(
78        db_with_cache: Arc<CachedPosLedgerDB>,
79        pow_handler: Arc<dyn PowInterface>,
80        consensus_db: Arc<dyn LedgerBlockRW>,
81    ) -> Self {
82        Self {
83            db_with_cache,
84            consensus_db,
85            pow_handler,
86        }
87    }
88
89    /// Post-processing of what the VM outputs. Returns the entire block's
90    /// output.
91    fn process_vm_outputs(
92        &self, transactions: &[Transaction],
93        vm_outputs: Vec<TransactionOutput>, parent_trees: &ExecutedTrees,
94        parent_block_id: &HashValue, catch_up_mode: bool,
95    ) -> Result<ProcessedVMOutput> {
96        // The data of each individual transaction. For convenience purpose,
97        // even for the transactions that will be discarded, we will
98        // compute its in-memory Sparse Merkle Tree (it will be
99        // identical to the previous one).
100        let mut txn_data = vec![];
101        // The hash of each individual TransactionInfo object. This will not
102        // include the transactions that will be discarded, since they
103        // do not go into the transaction accumulator.
104        let mut txn_info_hashes = vec![];
105
106        let pivot_select_event_key =
107            PivotBlockDecision::pivot_select_event_key();
108        let election_event_key = ElectionEvent::event_key();
109        let retire_event_key = RetireEvent::event_key();
110        let register_event_key = RegisterEvent::event_key();
111        let update_voting_power_event_key = UpdateVotingPowerEvent::event_key();
112
113        // Find the next pivot block.
114        let mut pivot_decision = None;
115        let mut new_pos_state = parent_trees.pos_state().clone();
116        let parent_pivot_decision = new_pos_state.pivot_decision().clone();
117        for vm_output in vm_outputs.clone().into_iter() {
118            for event in vm_output.events() {
119                // check for pivot block selection.
120                if *event.key() == pivot_select_event_key {
121                    if pivot_decision.is_some() {
122                        bail!("Multiple pivot decisions in one block!");
123                    }
124                    pivot_decision = Some(PivotBlockDecision::from_bytes(
125                        event.event_data(),
126                    )?);
127                } else if *event.key() == election_event_key {
128                    let election_event =
129                        ElectionEvent::from_bytes(event.event_data())?;
130                    new_pos_state.new_node_elected(&election_event)?;
131                } else if let Some(dispute) = decode_dispute_event(event) {
132                    let (node_id, offense_epoch) = dispute?;
133                    new_pos_state.forfeit_node(&node_id, offense_epoch)?;
134                }
135            }
136        }
137
138        if *parent_block_id != *PRE_GENESIS_BLOCK_ID {
139            if let Some(pivot_decision) = &pivot_decision {
140                diem_debug!(
141                    "process_vm_outputs: parent={:?} parent_pivot={:?}",
142                    parent_block_id,
143                    parent_pivot_decision
144                );
145
146                // The check and event processing below will be skipped during
147                // PoS catching up, because pow has not processed these pivot
148                // decisions.
149                if !catch_up_mode {
150                    if !self.pow_handler.validate_proposal_pivot_decision(
151                        parent_pivot_decision.block_hash,
152                        pivot_decision.block_hash,
153                    ) {
154                        bail!("Invalid pivot decision for block");
155                    }
156
157                    // Verify if the proposer has packed all staking events as
158                    // expected.
159                    diem_debug!(
160                        "check staking events: parent={:?} me={:?}",
161                        parent_pivot_decision,
162                        pivot_decision
163                    );
164                    let staking_events = self.pow_handler.get_staking_events(
165                        parent_pivot_decision.height,
166                        pivot_decision.height,
167                        parent_pivot_decision.block_hash,
168                        pivot_decision.block_hash,
169                    )?;
170                    let mut staking_events_iter = staking_events.iter();
171                    for vm_output in vm_outputs.clone().into_iter() {
172                        for event in vm_output.events() {
173                            // check for pivot block selection.
174                            if *event.key() == register_event_key {
175                                let register_event = RegisterEvent::from_bytes(
176                                    event.event_data(),
177                                )?;
178                                match register_event.matches_staking_event(staking_events_iter.next().ok_or(anyhow!("More staking transactions packed than actual pow events"))?) {
179                                    Ok(true) => {}
180                                    Ok(false) => bail!("Packed staking transactions unmatch PoW events)"),
181                                    Err(e) => diem_error!("error decoding pow events: err={:?}", e),
182                                }
183                                new_pos_state
184                                    .register_node(register_event.node_id)?;
185                            } else if *event.key()
186                                == update_voting_power_event_key
187                            {
188                                let update_voting_power_event =
189                                    UpdateVotingPowerEvent::from_bytes(
190                                        event.event_data(),
191                                    )?;
192                                match update_voting_power_event.matches_staking_event(staking_events_iter.next().ok_or(anyhow!("More staking transactions packed than actual pow events"))?) {
193                                    Ok(true) => {}
194                                    Ok(false) => bail!("Packed staking transactions unmatch PoW events)"),
195                                    Err(e) => diem_error!("error decoding pow events: err={:?}", e),
196                                }
197                                new_pos_state.update_voting_power(
198                                    &update_voting_power_event.node_address,
199                                    update_voting_power_event.voting_power,
200                                )?;
201                            } else if *event.key() == retire_event_key {
202                                let retire_event = RetireEvent::from_bytes(
203                                    event.event_data(),
204                                )?;
205                                match retire_event.matches_staking_event(staking_events_iter.next().ok_or(anyhow!("More staking transactions packed than actual pow events"))?) {
206                                    Ok(true) => {}
207                                    Ok(false) => bail!("Packed staking transactions unmatch PoW events)"),
208                                    Err(e) => diem_error!("error decoding pow events: err={:?}", e),
209                                }
210                                new_pos_state.retire_node(
211                                    &retire_event.node_id,
212                                    retire_event.votes,
213                                )?;
214                            }
215                        }
216                    }
217                    ensure!(
218                        staking_events_iter.next().is_none(),
219                        "Not all PoW staking events are packed"
220                    );
221                } else {
222                    for vm_output in vm_outputs.clone().into_iter() {
223                        for event in vm_output.events() {
224                            // check for pivot block selection.
225                            if *event.key() == register_event_key {
226                                let register_event = RegisterEvent::from_bytes(
227                                    event.event_data(),
228                                )?;
229                                new_pos_state
230                                    .register_node(register_event.node_id)?;
231                            } else if *event.key()
232                                == update_voting_power_event_key
233                            {
234                                let update_voting_power_event =
235                                    UpdateVotingPowerEvent::from_bytes(
236                                        event.event_data(),
237                                    )?;
238                                new_pos_state.update_voting_power(
239                                    &update_voting_power_event.node_address,
240                                    update_voting_power_event.voting_power,
241                                )?;
242                            } else if *event.key() == retire_event_key {
243                                let retire_event = RetireEvent::from_bytes(
244                                    event.event_data(),
245                                )?;
246                                new_pos_state.retire_node(
247                                    &retire_event.node_id,
248                                    retire_event.votes,
249                                )?;
250                            }
251                        }
252                    }
253                }
254            } else {
255                // No new pivot decision, so there should be no staking-related
256                // transactions.
257                if vm_outputs.iter().any(|output| {
258                    output.events().iter().any(|event| {
259                        *event.key() == retire_event_key
260                            || *event.key() == update_voting_power_event_key
261                    })
262                }) {
263                    bail!("Should not pack staking related transactions");
264                }
265                pivot_decision = Some(parent_pivot_decision);
266            }
267        }
268        // TODO(lpl): This is only for pos-tool
269        if let Some(pivot_decision) = &pivot_decision {
270            new_pos_state.set_pivot_decision(pivot_decision.clone());
271        }
272        let mut next_epoch_state = new_pos_state.next_view()?;
273
274        let is_genesis =
275            next_epoch_state.as_ref().map_or(false, |es| es.epoch == 1);
276
277        for (vm_output, txn) in
278            itertools::zip_eq(vm_outputs.into_iter(), transactions.iter())
279        {
280            let event_tree = {
281                let event_hashes: Vec<_> =
282                    vm_output.events().iter().map(CryptoHash::hash).collect();
283                InMemoryAccumulator::<EventAccumulatorHasher>::from_leaves(
284                    &event_hashes,
285                )
286            };
287
288            let mut txn_info_hash = None;
289            match vm_output.status() {
290                TransactionStatus::Keep(status) => {
291                    // ensure!(
292                    //     !vm_output.write_set().is_empty(),
293                    //     "Transaction with empty write set should be
294                    // discarded.", );
295                    // Compute hash for the TransactionInfo object. We need the
296                    // hash of the transaction itself, the
297                    // state root hash as well as the event root hash.
298                    let txn_info = TransactionInfo::new(
299                        txn.hash(),
300                        Default::default(),
301                        event_tree.root_hash(),
302                        vm_output.gas_used(),
303                        status.clone(),
304                    );
305
306                    let real_txn_info_hash = txn_info.hash();
307                    txn_info_hashes.push(real_txn_info_hash);
308                    txn_info_hash = Some(real_txn_info_hash);
309                }
310                TransactionStatus::Discard(status) => {
311                    if !vm_output.events().is_empty() {
312                        diem_error!(
313                            "Discarded transaction has non-empty write set or events. \
314                             Transaction: {:?}. Status: {:?}.",
315                            txn, status,
316                        );
317                    }
318                }
319                TransactionStatus::Retry => (),
320            }
321
322            txn_data.push(TransactionData::new(
323                vm_output.events().to_vec(),
324                vm_output.status().clone(),
325                Arc::new(event_tree),
326                vm_output.gas_used(),
327                txn_info_hash,
328            ));
329        }
330
331        // For genesis, extract ValidatorSet directly from the epoch
332        // change event instead of going through the WriteSet →
333        // AccountState roundtrip.
334        if is_genesis {
335            let new_epoch_event_key = on_chain_config::new_epoch_event_key();
336            let validator_set = txn_data
337                .iter()
338                .flat_map(|td| td.events())
339                .find(|event| *event.key() == new_epoch_event_key)
340                .ok_or_else(|| format_err!("Genesis epoch event not found"))
341                .and_then(|event| {
342                    bcs::from_bytes::<ValidatorSet>(event.event_data()).map_err(
343                        |e| {
344                            format_err!(
345                                "Failed to deserialize ValidatorSet: {}",
346                                e
347                            )
348                        },
349                    )
350                })?;
351            next_epoch_state = Some(EpochState::new(
352                1,
353                (&validator_set).into(),
354                pivot_decision
355                    .as_ref()
356                    .map(|p| p.block_hash.as_bytes().to_vec())
357                    .unwrap_or(vec![]),
358            ))
359        }
360
361        let current_transaction_accumulator =
362            parent_trees.txn_accumulator().append(&txn_info_hashes);
363
364        Ok(ProcessedVMOutput::new(
365            txn_data,
366            ExecutedTrees::new_copy(
367                Arc::new(current_transaction_accumulator),
368                new_pos_state,
369            ),
370            next_epoch_state,
371            // TODO(lpl): Check if we need to assert it's Some.
372            pivot_decision,
373        ))
374    }
375
376    fn get_executed_trees(
377        &self, block_id: HashValue,
378    ) -> Result<ExecutedTrees, Error> {
379        let executed_trees = if block_id
380            == self.db_with_cache.cache.lock().committed_block_id()
381        {
382            self.db_with_cache.cache.lock().committed_trees().clone()
383        } else {
384            self.db_with_cache
385                .get_block(&block_id)?
386                .lock()
387                .output()
388                .executed_trees()
389                .clone()
390        };
391
392        Ok(executed_trees)
393    }
394
395    fn get_executed_state_view(
396        &self, id: StateViewId, executed_trees: &ExecutedTrees,
397    ) -> VerifiedStateView {
398        VerifiedStateView::new(id, executed_trees.pos_state().clone())
399    }
400}
401
402impl BlockExecutor for Executor {
403    fn committed_block_id(&self) -> Result<HashValue, Error> {
404        Ok(self.committed_block_id())
405    }
406
407    fn execute_block(
408        &self, block: (HashValue, Vec<Transaction>),
409        parent_block_id: HashValue, catch_up_mode: bool,
410    ) -> Result<StateComputeResult, Error> {
411        let (block_id, mut transactions) = block;
412
413        // Reconfiguration rule - if a block is a child of pending
414        // reconfiguration, it needs to be empty So we roll over the
415        // executed state until it's committed and we start new epoch.
416        let (output, state_compute_result) = if parent_block_id
417            != self.committed_block_id()
418            && self
419                .db_with_cache
420                .get_block(&parent_block_id)?
421                .lock()
422                .output()
423                .has_reconfiguration()
424        {
425            let parent = self.db_with_cache.get_block(&parent_block_id)?;
426            let parent_block = parent.lock();
427            let parent_output = parent_block.output();
428
429            diem_info!(
430                LogSchema::new(LogEntry::BlockExecutor).block_id(block_id),
431                "reconfig_descendant_block_received"
432            );
433
434            let mut output = ProcessedVMOutput::new(
435                vec![],
436                parent_output.executed_trees().clone(),
437                parent_output.epoch_state().clone(),
438                // The block has no pivot decision transaction, so it's the
439                // same as the parent.
440                parent_output.pivot_block().clone(),
441            );
442            output.set_pos_state_skipped();
443
444            let parent_accu = parent_output.executed_trees().txn_accumulator();
445            let state_compute_result = output.compute_result(
446                parent_accu.frozen_subtree_roots().clone(),
447                parent_accu.num_leaves(),
448            );
449
450            // Reset the reconfiguration suffix transactions to empty list.
451            transactions = vec![];
452
453            (output, state_compute_result)
454        } else {
455            diem_info!(
456                LogSchema::new(LogEntry::BlockExecutor).block_id(block_id),
457                "execute_block"
458            );
459
460            let parent_block_executed_trees =
461                self.get_executed_trees(parent_block_id)?;
462
463            let state_view = self.get_executed_state_view(
464                StateViewId::BlockExecution { block_id },
465                &parent_block_executed_trees,
466            );
467
468            // FIXME(lpl): Check the error processing in `execute_block`,
469            // `process_vm_outputs`, and transaction packing. We
470            // need to ensure that there is no packing behavior that
471            // makes all new proposals invalid during execution.
472            let vm_outputs = {
473                // trace_code_block!("executor::execute_block", {"block",
474                // block_id});
475                fail_point!("executor::vm_execute_block", |_| {
476                    Err(Error::from(anyhow::anyhow!(
477                        "Injected error in vm_execute_block"
478                    )))
479                });
480                PosVM::execute_block(
481                    transactions.clone(),
482                    &state_view,
483                    catch_up_mode,
484                )
485                .map_err(anyhow::Error::from)?
486            };
487
488            // trace_code_block!("executor::process_vm_outputs", {"block",
489            // block_id});
490            let status: Vec<_> = vm_outputs
491                .iter()
492                .map(TransactionOutput::status)
493                .cloned()
494                .collect();
495            if !status.is_empty() {
496                diem_trace!("Execution status: {:?}", status);
497            }
498
499            let output = self
500                .process_vm_outputs(
501                    &transactions,
502                    vm_outputs,
503                    &parent_block_executed_trees,
504                    &parent_block_id,
505                    catch_up_mode,
506                )
507                .map_err(|err| {
508                    format_err!("Failed to execute block: {}", err)
509                })?;
510
511            let parent_accu = parent_block_executed_trees.txn_accumulator();
512
513            diem_debug!("parent leaves: {}", parent_accu.num_leaves());
514            let state_compute_result = output.compute_result(
515                parent_accu.frozen_subtree_roots().clone(),
516                parent_accu.num_leaves(),
517            );
518            (output, state_compute_result)
519        };
520
521        // Add the output to the speculation_output_tree
522        self.db_with_cache
523            .add_block(parent_block_id, (block_id, transactions, output))?;
524
525        Ok(state_compute_result)
526    }
527
528    fn commit_blocks(
529        &self, block_ids: Vec<HashValue>,
530        ledger_info_with_sigs: LedgerInfoWithSignatures,
531    ) -> Result<Vec<Transaction>, Error> {
532        let mut pos_state_to_commit = self
533            .get_executed_trees(
534                ledger_info_with_sigs.ledger_info().consensus_block_id(),
535            )?
536            .pos_state()
537            .clone();
538
539        // TODO(lpl): Implement force_retire better?
540        // Process pos_state to apply force_retire.
541        if ledger_info_with_sigs.ledger_info().ends_epoch()
542            && ledger_info_with_sigs.ledger_info().epoch() != 0
543        {
544            let ending_block =
545                ledger_info_with_sigs.ledger_info().consensus_block_id();
546            let mut elected = BTreeMap::new();
547            let mut voted_block_id = ending_block;
548            // `self.cache.committed_trees` should be within this epoch and
549            // before ending_block.
550            let verifier = self
551                .db_with_cache
552                .cache
553                .lock()
554                .committed_trees()
555                .pos_state()
556                .epoch_state()
557                .verifier()
558                // Clone to avoid possible deadlock.
559                .clone();
560            for committee_member in verifier.address_to_validator_info().keys()
561            {
562                elected.insert(*committee_member, VoteCount::default());
563            }
564            loop {
565                let block = self
566                    .consensus_db
567                    .get_ledger_block(&voted_block_id)?
568                    .unwrap();
569                diem_trace!("count vote for block {:?}", block);
570                if block.quorum_cert().ledger_info().signatures().len() == 0 {
571                    // parent is round-0 virtual block and has not voters, so we
572                    // just add `leader_count` and break the loop.
573                    if let Some(author) = block.author() {
574                        let leader_status =
575                            elected.get_mut(&author).expect("in epoch state");
576                        leader_status.leader_count += 1;
577                    }
578                    break;
579                }
580                if let Some(author) = block.author() {
581                    let leader_status =
582                        elected.get_mut(&author).expect("in epoch state");
583                    leader_status.leader_count += 1;
584                    leader_status.included_vote_count += verifier
585                        .extra_vote_count(
586                            block
587                                .quorum_cert()
588                                .ledger_info()
589                                .signatures()
590                                .keys(),
591                        )
592                        .unwrap();
593                }
594                for voter in
595                    block.quorum_cert().ledger_info().signatures().keys()
596                {
597                    elected
598                        .get_mut(&voter)
599                        .expect("in epoch state")
600                        .vote_count +=
601                        verifier.get_voting_power(voter).unwrap();
602                }
603                voted_block_id = block.parent_id();
604            }
605            let mut force_retired = HashSet::new();
606
607            // Force retire the nodes that have not voted in this term.
608            for (node, vote_count) in elected.iter_mut() {
609                if vote_count.vote_count == 0 {
610                    force_retired.insert(node);
611                } else {
612                    vote_count.total_votes =
613                        verifier.get_voting_power(node).unwrap_or(0);
614                    if vote_count.total_votes == 0 {
615                        diem_warn!("Node {:?} has voted for epoch {} without voting power.",
616                            node,
617                            pos_state_to_commit.epoch_state().epoch);
618                    }
619                }
620            }
621
622            if !force_retired.is_empty() {
623                // `end_epoch` has been checked above and is excluded below.
624                let end_epoch = ledger_info_with_sigs.ledger_info().epoch();
625                let start_epoch = end_epoch.saturating_sub(
626                    POS_STATE_CONFIG.force_retire_check_epoch_count(
627                        pos_state_to_commit.current_view(),
628                    ),
629                ) + 1;
630                // Check more past epochs to see if the nodes in `force_retired`
631                // have voted.
632                for end_ledger_info in self
633                    .db_with_cache
634                    .db
635                    .reader
636                    .get_epoch_ending_ledger_infos(start_epoch, end_epoch)?
637                    .get_all_ledger_infos()
638                {
639                    let mut voted_block_id =
640                        end_ledger_info.ledger_info().consensus_block_id();
641                    loop {
642                        let block = self
643                            .consensus_db
644                            .get_ledger_block(&voted_block_id)?
645                            .unwrap();
646                        if block.quorum_cert().ledger_info().signatures().len()
647                            == 0
648                        {
649                            break;
650                        }
651                        for voter in block
652                            .quorum_cert()
653                            .ledger_info()
654                            .signatures()
655                            .keys()
656                        {
657                            // Find a vote, so the node will not be force
658                            // retired.
659                            force_retired.remove(voter);
660                        }
661                        voted_block_id = block.parent_id();
662                    }
663                }
664                for node in force_retired {
665                    pos_state_to_commit.force_retire_node(&node)?;
666                }
667            }
668
669            let reward_event = RewardDistributionEventV2 {
670                candidates: pos_state_to_commit.next_evicted_term(),
671                elected: elected
672                    .into_iter()
673                    .map(|(k, v)| (H256::from_slice(k.as_ref()), v))
674                    .collect(),
675                view: pos_state_to_commit.current_view(),
676            };
677            self.db_with_cache.db.writer.save_reward_event(
678                ledger_info_with_sigs.ledger_info().epoch(),
679                &reward_event,
680            )?;
681            self.db_with_cache
682                .get_block(&ending_block)
683                .expect("latest committed block not pruned")
684                .lock()
685                .replace_pos_state(pos_state_to_commit.clone());
686        }
687
688        diem_info!(
689            LogSchema::new(LogEntry::BlockExecutor).block_id(
690                ledger_info_with_sigs.ledger_info().consensus_block_id()
691            ),
692            "commit_block"
693        );
694
695        let version = ledger_info_with_sigs.ledger_info().version();
696
697        let num_txns_in_li = version
698            .checked_add(1)
699            .ok_or_else(|| format_err!("version + 1 overflows"))?;
700        let num_persistent_txns = self
701            .db_with_cache
702            .cache
703            .lock()
704            .synced_trees()
705            .txn_accumulator()
706            .num_leaves();
707
708        if num_txns_in_li < num_persistent_txns {
709            return Err(Error::InternalError {
710                error: format!(
711                    "Try to commit stale transactions with the last version as {}",
712                    version
713                ),
714            });
715        }
716
717        // All transactions that need to go to storage. In the above example,
718        // this means all the transactions in A, B and C whose status ==
719        // TransactionStatus::Keep. This must be done before calculate
720        // potential skipping of transactions in idempotent commit.
721        let mut txns_to_keep = vec![];
722        let arc_blocks = block_ids
723            .iter()
724            .map(|id| self.db_with_cache.get_block(id))
725            .collect::<Result<Vec<_>, Error>>()?;
726        let blocks = arc_blocks.iter().map(|b| b.lock()).collect::<Vec<_>>();
727        let mut committed_blocks = Vec::new();
728        let mut signatures_vec = Vec::new();
729        if ledger_info_with_sigs.ledger_info().epoch() != 0 {
730            for (i, b) in blocks.iter().enumerate() {
731                let ledger_block = self
732                    .consensus_db
733                    .get_ledger_block(&b.id())
734                    .unwrap()
735                    .unwrap();
736                let view =
737                    b.output().executed_trees().pos_state().current_view();
738                committed_blocks.push(CommittedBlock {
739                    hash: b.id(),
740                    epoch: ledger_block.epoch(),
741                    miner: ledger_block.author(),
742                    parent_hash: ledger_block.parent_id(),
743                    round: ledger_block.round(),
744                    pivot_decision: b.output().pivot_block().clone().unwrap(),
745                    version: b.output().version().unwrap(),
746                    timestamp: ledger_block.timestamp_usecs(),
747                    view,
748                    is_skipped: b
749                        .output()
750                        .executed_trees()
751                        .pos_state()
752                        .skipped(),
753                });
754                // The signatures of each block is in the qc of the next block.
755                if i != 0 {
756                    signatures_vec.push((
757                        ledger_block.quorum_cert().certified_block().id(),
758                        ledger_block.quorum_cert().ledger_info().clone(),
759                    ));
760                }
761            }
762            let last_block = blocks.last().expect("not empty").id();
763            if let Some(qc) = self.consensus_db.get_qc_for_block(&last_block)? {
764                signatures_vec.push((last_block, qc.ledger_info().clone()));
765            } else {
766                // If we are catching up, all QCs come from retrieved blocks, so
767                // we cannot get the QC that votes for the last
768                // block in an epoch as the QC is within another
769                // unknown child block.
770                assert!(ledger_info_with_sigs.ledger_info().ends_epoch());
771            }
772        } else {
773            committed_blocks.push(CommittedBlock {
774                hash: ledger_info_with_sigs.ledger_info().consensus_block_id(),
775                epoch: 0,
776                round: 0,
777                miner: None,
778                parent_hash: HashValue::default(),
779                pivot_decision: ledger_info_with_sigs
780                    .ledger_info()
781                    .pivot_decision()
782                    .unwrap()
783                    .clone(),
784                version: ledger_info_with_sigs.ledger_info().version(),
785                timestamp: ledger_info_with_sigs
786                    .ledger_info()
787                    .timestamp_usecs(),
788                view: 1,
789                is_skipped: false,
790            });
791        }
792        for (txn, txn_data) in blocks.iter().flat_map(|block| {
793            itertools::zip_eq(
794                block.transactions(),
795                block.output().transaction_data(),
796            )
797        }) {
798            if let TransactionStatus::Keep(recorded_status) = txn_data.status()
799            {
800                txns_to_keep.push(TransactionToCommit::new(
801                    txn.clone(),
802                    txn_data.events().to_vec(),
803                    txn_data.gas_used(),
804                    recorded_status.clone(),
805                ));
806            }
807        }
808
809        let last_block = blocks
810            .last()
811            .ok_or_else(|| format_err!("CommittableBlockBatch is empty"))?;
812
813        // Check that the version in ledger info (computed by consensus) matches
814        // the version computed by us.
815        let num_txns_in_speculative_accumulator = last_block
816            .output()
817            .executed_trees()
818            .txn_accumulator()
819            .num_leaves();
820        assert_eq!(
821            num_txns_in_li, num_txns_in_speculative_accumulator as Version,
822            "Number of transactions in ledger info ({}) does not match number of transactions \
823             in accumulator ({}).",
824            num_txns_in_li, num_txns_in_speculative_accumulator,
825        );
826
827        let num_txns_to_keep = txns_to_keep.len() as u64;
828
829        // Skip txns that are already committed to allow failures in state sync
830        // process.
831        let first_version_to_keep = num_txns_in_li - num_txns_to_keep;
832        assert!(
833            first_version_to_keep <= num_persistent_txns,
834            "first_version {} in the blocks to commit cannot exceed # of committed txns: {}.",
835            first_version_to_keep,
836            num_persistent_txns
837        );
838
839        let num_txns_to_skip = num_persistent_txns - first_version_to_keep;
840        let first_version_to_commit = first_version_to_keep + num_txns_to_skip;
841
842        if num_txns_to_skip != 0 {
843            diem_debug!(
844                LogSchema::new(LogEntry::BlockExecutor)
845                    .latest_synced_version(num_persistent_txns - 1)
846                    .first_version_to_keep(first_version_to_keep)
847                    .num_txns_to_keep(num_txns_to_keep)
848                    .first_version_to_commit(first_version_to_commit),
849                "skip_transactions_when_committing"
850            );
851        }
852
853        // Skip duplicate txns that are already persistent.
854        let txns_to_commit = &txns_to_keep[num_txns_to_skip as usize..];
855
856        let num_txns_to_commit = txns_to_commit.len() as u64;
857        {
858            assert_eq!(
859                first_version_to_commit,
860                num_txns_in_li - num_txns_to_commit
861            );
862            fail_point!("executor::commit_blocks", |_| {
863                Err(Error::from(anyhow::anyhow!(
864                    "Injected error in commit_blocks"
865                )))
866            });
867            self.db_with_cache.db.writer.save_transactions(
868                txns_to_commit,
869                first_version_to_commit,
870                Some(&ledger_info_with_sigs),
871                Some(pos_state_to_commit),
872                committed_blocks,
873                signatures_vec,
874            )?;
875        }
876
877        let committed_txns: Vec<Transaction> = txns_to_commit
878            .iter()
879            .map(|txn| txn.transaction().clone())
880            .collect();
881
882        // Drop block locks before prune() which needs to re-lock them.
883        drop(blocks);
884        drop(arc_blocks);
885
886        let old_committed_block = self.db_with_cache.prune(
887            ledger_info_with_sigs.ledger_info(),
888            committed_txns.clone(),
889        )?;
890        self.db_with_cache
891            .db
892            .writer
893            .delete_pos_state_by_block(&old_committed_block)?;
894
895        Ok(committed_txns)
896    }
897}