Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(db): add command to dump transaction state roots #3071

Merged
merged 4 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/rooch-common/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
// SPDX-License-Identifier: Apache-2.0

pub mod humanize;
pub mod vec;
192 changes: 192 additions & 0 deletions crates/rooch-common/src/utils/vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

// find the last true element in the array:
// the array is sorted by the predicate, and the predicate is true for the first n elements and false for the rest.
pub fn find_last_true<T>(arr: &[T], predicate: impl Fn(&T) -> bool) -> Option<&T> {
if arr.is_empty() {
return None;
}
if !predicate(&arr[0]) {
return None;
}
if predicate(&arr[arr.len() - 1]) {
return Some(&arr[arr.len() - 1]);
}

// binary search
let mut left = 0;
let mut right = arr.len() - 1;

while left + 1 < right {
let mid = left + (right - left) / 2;
if predicate(&arr[mid]) {
left = mid; // mid is true, the final answer is mid or on the right
} else {
right = mid; // mid is false, the final answer is on the left
}
}

// left is the last true position
Some(&arr[left])
}

#[cfg(test)]
mod tests {
use super::*;

mod find_last_true {
use super::*;

#[derive(Debug, PartialEq)]
struct TestItem {
id: usize,
value: bool,
}

impl TestItem {
fn new(id: usize, value: bool) -> Self {
Self { id, value }
}
}

#[test]
fn test_empty_array() {
let items: Vec<TestItem> = vec![];
let result = find_last_true(&items, |item| item.value);
assert!(result.is_none());
}

#[test]
fn test_single_element_true() {
let items = vec![TestItem::new(0, true)];
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(0));
}

#[test]
fn test_single_element_false() {
let items = vec![TestItem::new(0, false)];
let result = find_last_true(&items, |item| item.value);
assert_eq!(result, None);
}

#[test]
fn test_all_true() {
let items = vec![
TestItem::new(1, true),
TestItem::new(2, true),
TestItem::new(3, true),
];
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(3));
}

#[test]
fn test_all_false() {
let items = vec![
TestItem::new(1, false),
TestItem::new(2, false),
TestItem::new(3, false),
];
let result = find_last_true(&items, |item| item.value);
assert_eq!(result, None);
}

#[test]
fn test_odd_length_middle_transition() {
let items = vec![
TestItem::new(1, true),
TestItem::new(2, true),
TestItem::new(3, true),
TestItem::new(4, false),
TestItem::new(5, false),
];
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(3));
}

#[test]
fn test_even_length_middle_transition() {
let items = vec![
TestItem::new(1, true),
TestItem::new(2, true),
TestItem::new(3, false),
TestItem::new(4, false),
];
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(2));
}

#[test]
fn test_only_first_true() {
let items = vec![
TestItem::new(1, true),
TestItem::new(2, false),
TestItem::new(3, false),
TestItem::new(4, false),
];
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(1));
}

#[test]
fn test_only_last_true() {
let items = vec![
TestItem::new(1, false),
TestItem::new(2, false),
TestItem::new(3, false),
TestItem::new(4, true),
];
let result = find_last_true(&items, |item| item.value);
assert_eq!(result, None); // no sorted array, so no result
}

#[test]
fn test_large_array() {
let mut items = Vec::new();
for i in 1..1000 {
items.push(TestItem::new(i, i <= 500));
}
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(500));

let mut items = Vec::new();
for i in 1..1001 {
items.push(TestItem::new(i, i <= 500));
}
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(500));

let mut items = Vec::new();
for i in 1..1001 {
items.push(TestItem::new(i, i <= 501));
}
let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(501));
}

#[test]
fn test_various_transition_points() {
// Test cases with different transition points
let test_cases = [
(vec![true], 0),
(vec![true, false], 0),
(vec![true, true, false], 1),
(vec![true, true, true, false], 2),
(vec![true, true, true, true, false], 3),
];

for (i, (values, expected)) in test_cases.iter().enumerate() {
let items: Vec<TestItem> = values
.iter()
.enumerate()
.map(|(id, &v)| TestItem::new(id, v))
.collect();

let result = find_last_true(&items, |item| item.value).map(|item| item.id);
assert_eq!(result, Some(*expected), "Failed at test case {}", i);
}
}
}
}
20 changes: 9 additions & 11 deletions crates/rooch/src/commands/da/commands/exec.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::commands::da::commands::{build_rooch_db, LedgerTxGetter, TxOrderHashBlockGetter};
use crate::commands::da::commands::{build_rooch_db, LedgerTxGetter, TxDAIndexer};
use anyhow::Context;
use bitcoin::hashes::Hash;
use bitcoin_client::actor::client::BitcoinClientConfig;
Expand Down Expand Up @@ -108,13 +108,13 @@ impl ExecCommand {

let (order_state_pair, tx_order_end) = self.load_order_state_pair();
let ledger_tx_loader = LedgerTxGetter::new(self.segment_dir.clone())?;
let tx_order_hash_block_list = TxOrderHashBlockGetter::load_from_file(
let tx_da_indexer = TxDAIndexer::load_from_file(
self.order_hash_path.clone(),
moveos_store.transaction_store,
)?;
Ok(ExecInner {
ledger_tx_getter: ledger_tx_loader,
tx_order_hash_block_getter: tx_order_hash_block_list,
tx_da_indexer,
order_state_pair,
tx_order_end,
bitcoin_client_proxy,
Expand Down Expand Up @@ -149,7 +149,7 @@ impl ExecCommand {

struct ExecInner {
ledger_tx_getter: LedgerTxGetter,
tx_order_hash_block_getter: TxOrderHashBlockGetter,
tx_da_indexer: TxDAIndexer,
order_state_pair: HashMap<u64, H256>,
tx_order_end: u64,

Expand Down Expand Up @@ -264,7 +264,7 @@ impl ExecInner {
}

async fn produce_tx(&self, tx: Sender<ExecMsg>) -> anyhow::Result<()> {
let last_executed_opt = self.tx_order_hash_block_getter.find_last_executed()?;
let last_executed_opt = self.tx_da_indexer.find_last_executed()?;
let mut next_tx_order = last_executed_opt
.clone()
.map(|v| v.tx_order + 1)
Expand All @@ -283,9 +283,8 @@ impl ExecInner {
if let (Some(rollback), Some(last_executed)) = (self.rollback, last_executed_opt.clone()) {
let last_executed_tx_order = last_executed.tx_order;
if rollback < last_executed_tx_order {
let new_last_and_rollback = self
.tx_order_hash_block_getter
.slice(rollback, last_executed_tx_order)?;
let new_last_and_rollback =
self.tx_da_indexer.slice(rollback, last_executed_tx_order)?;
// split into two parts, the first get execution info for new startup, all others rollback
let (new_last, rollback_part) = new_last_and_rollback.split_first().unwrap();
tracing::info!(
Expand All @@ -304,9 +303,8 @@ impl ExecInner {
)
})?;
}
let rollback_execution_info = self
.tx_order_hash_block_getter
.get_execution_info(new_last.tx_hash)?;
let rollback_execution_info =
self.tx_da_indexer.get_execution_info(new_last.tx_hash)?;
self.update_startup_info_after_rollback(rollback_execution_info.unwrap())?;
next_block_number = new_last.block_number;
next_tx_order = rollback + 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::commands::da::commands::{LedgerTxGetter, TxOrderHashBlock};
use crate::commands::da::commands::{LedgerTxGetter, TxDAIndex};
use rooch_types::error::{RoochError, RoochResult};
use std::fs::File;
use std::io::BufWriter;
use std::io::Write;
use std::path::PathBuf;

/// Dump tx_order:tx_hash:block_number to a file from segments
/// Index tx_order:tx_hash:block_number to a file from segments
#[derive(Debug, clap::Parser)]
pub struct DumpTxOrderHashCommand {
pub struct IndexTxCommand {
#[clap(long = "segment-dir")]
pub segment_dir: PathBuf,
#[clap(long = "output")]
pub output: PathBuf,
}

impl DumpTxOrderHashCommand {
impl IndexTxCommand {
pub fn execute(self) -> RoochResult<()> {
let ledger_tx_loader = LedgerTxGetter::new(self.segment_dir)?;
let mut block_number = ledger_tx_loader.get_min_chunk_id();
Expand Down Expand Up @@ -46,7 +46,7 @@ impl DumpTxOrderHashCommand {
writeln!(
writer,
"{}",
TxOrderHashBlock::new(tx_order, tx_hash, block_number)
TxDAIndex::new(tx_order, tx_hash, block_number)
)?;
expected_tx_order += 1;
}
Expand Down
Loading
Loading