From ff477eb2f0af2f20a8ba3066906ba88b9e81d1d5 Mon Sep 17 00:00:00 2001 From: oars Date: Mon, 15 Dec 2025 12:00:32 +0300 Subject: [PATCH] script/research/tx-replayer: a tool to replay a transaction by resetting the blockchain database to a height before the transaction was added at --- script/research/tx-replayer/.gitignore | 6 ++ script/research/tx-replayer/Cargo.toml | 24 ++++++++ script/research/tx-replayer/Makefile | 37 ++++++++++++ script/research/tx-replayer/src/main.rs | 78 +++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 script/research/tx-replayer/.gitignore create mode 100644 script/research/tx-replayer/Cargo.toml create mode 100644 script/research/tx-replayer/Makefile create mode 100644 script/research/tx-replayer/src/main.rs diff --git a/script/research/tx-replayer/.gitignore b/script/research/tx-replayer/.gitignore new file mode 100644 index 000000000..ed9fe32c1 --- /dev/null +++ b/script/research/tx-replayer/.gitignore @@ -0,0 +1,6 @@ +/target +Cargo.lock +rustfmt.toml +tx-replayer +*.zst +*.json.gz diff --git a/script/research/tx-replayer/Cargo.toml b/script/research/tx-replayer/Cargo.toml new file mode 100644 index 000000000..4d54fe71c --- /dev/null +++ b/script/research/tx-replayer/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tx-replayer" +version = "0.1.0" +description = "CLI-utility to replay transactions for analysis" +authors = ["Dyne.org foundation "] +repository = "https://codeberg.org/darkrenaissance/darkfi" +license = "AGPL-3.0-only" +edition = "2024" + +[workspace] + +[dependencies] +darkfi = {path = "../../../", features = ["validator"]} +darkfi-sdk = {path = "../../../src/sdk"} +sled-overlay = {version = "0.1.10"} +smol = {version = "2.0.2"} +clap = {version = "4.4.11", features = ["derive"]} + +[patch.crates-io] +halo2_proofs = {git="https://github.com/parazyd/halo2", branch="v031"} +halo2_gadgets = {git="https://github.com/parazyd/halo2", branch="v031"} + +[profile.release] +debug = true diff --git a/script/research/tx-replayer/Makefile b/script/research/tx-replayer/Makefile new file mode 100644 index 000000000..521fff826 --- /dev/null +++ b/script/research/tx-replayer/Makefile @@ -0,0 +1,37 @@ +.POSIX: + +# Install prefix +PREFIX = $(HOME)/.cargo + +# Cargo binary +CARGO = cargo + +# Compile target +RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2) +# Uncomment when doing musl static builds +#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes + +SRC = \ + Cargo.toml \ + $(shell find src -type f -name '*.rs') \ + +BIN = $(shell grep '^name = ' Cargo.toml | cut -d' ' -f3 | tr -d '"') + +all: $(BIN) + +$(BIN): $(SRC) + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@ + cp -f target/$(RUST_TARGET)/release/$@ $@ + +fmt: + $(CARGO) +nightly fmt --all + +clippy: + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clippy --target=$(RUST_TARGET) \ + --release --all-features --workspace --tests + +clean: + RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(BIN) + rm -f $(BIN) + +.PHONY: all fmt clippy clean diff --git a/script/research/tx-replayer/src/main.rs b/script/research/tx-replayer/src/main.rs new file mode 100644 index 000000000..fe0a4aedf --- /dev/null +++ b/script/research/tx-replayer/src/main.rs @@ -0,0 +1,78 @@ +use clap::Parser; +use darkfi::{ + blockchain::{Blockchain, BlockchainOverlay, BlockchainOverlayPtr}, + cli_desc, + util::path::expand_path, + validator::verification::verify_transaction, + zk::VerifyingKey, +}; +use darkfi_sdk::{crypto::MerkleTree, tx::TransactionHash}; +use std::collections::HashMap; + +#[derive(Parser)] +#[command(about = cli_desc!())] +struct Args { + #[arg(short, long)] + database_path: String, + #[arg(short, long)] + tx_hash: String, +} + +fn main() { + smol::block_on(async { + let args = Args::parse(); + replay_tx(args).await; + }); +} + +async fn replay_tx(args: Args) { + let db_path = expand_path(&args.database_path).unwrap(); + let sled_db = sled_overlay::sled::open(&db_path).unwrap(); + + let blockchain = Blockchain::new(&sled_db).unwrap(); + let txh: TransactionHash = args.tx_hash.parse().unwrap(); + let tx = blockchain.transactions.get(&[txh], true).unwrap().first().unwrap().clone().unwrap(); + + let (overlay, new_height) = rollback_database(&blockchain, txh).await; + + let mut vks: HashMap<[u8; 32], HashMap> = HashMap::new(); + for call in &tx.calls { + vks.insert(call.data.contract_id.to_bytes(), HashMap::new()); + } + + let result = + verify_transaction(&overlay, new_height, 2, &tx, &mut MerkleTree::new(1), &mut vks, true) + .await + .unwrap(); + + println!("Verify Transaction Result: {:?}", result); +} + +/// Resets the blockchain in memory to a height before the transaction. +async fn rollback_database( + blockchain: &Blockchain, + txh: TransactionHash, +) -> (BlockchainOverlayPtr, u32) { + let (tx_height, _) = + blockchain.transactions.get_location(&[txh], true).unwrap().first().unwrap().unwrap(); + + let new_height = tx_height - 1; + println!("Rolling back database to Height: {new_height}"); + + let (last, _) = blockchain.last().unwrap(); + let heights: Vec = (new_height + 1..=last).rev().collect(); + let inverse_diffs = blockchain.blocks.get_state_inverse_diff(&heights, true).unwrap(); + + let overlay = BlockchainOverlay::new(blockchain).unwrap(); + + let overlay_lock = overlay.lock().unwrap(); + let mut lock = overlay_lock.overlay.lock().unwrap(); + for inverse_diff in inverse_diffs { + let inverse_diff = inverse_diff.unwrap(); + lock.add_diff(&inverse_diff).unwrap(); + } + drop(lock); + drop(overlay_lock); + + (overlay, new_height) +}