Skip to content
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
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ members = [
"api-server/web-server", # API server, for light-wallets and block explorers: web-server.
"blockprod", # Block production with whatever consensus algorithm.
"chainstate", # Code on chainstate of blocks and transactions.
"chainstate/db-dumper", # A tool for dumping the contents of the chainstate db.
"chainstate/test-suite", # Tests for the chainstate, separated to make use of the chainstate test framework.
"common", # Everything else, until it's moved to another crate.
"consensus", # Consensus related logic.
Expand Down Expand Up @@ -86,6 +87,7 @@ default-members = [
"api-server/scanner-daemon",
"api-server/web-server",
"chainstate",
"chainstate/db-dumper",
"common",
"crypto",
"dns-server",
Expand Down
39 changes: 39 additions & 0 deletions chainstate/db-dumper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "chainstate-db-dumper"
license.workspace = true
version.workspace = true
edition.workspace = true
rust-version.workspace = true

[lib]
name = "chainstate_db_dumper_lib"
path = "src/dumper_lib/lib.rs"

[dependencies]
chainstate = { path = ".." }
chainstate-launcher = { path = "../launcher" }
chainstate-storage = { path = "../storage" }
chainstate-types = { path = "../types" }
common = { path = "../../common" }
logging = { path = "../../logging" }
storage-lmdb = { path = "../../storage/lmdb" }
utils = { path = "../../utils" }

anyhow.workspace = true
clap.workspace = true
itertools.workspace = true
strum.workspace = true
thiserror.workspace = true

[dev-dependencies]
crypto = { path = "../../crypto" }
mocks = { path = "../../mocks" }
test-utils = { path = "../../test-utils" }

ctor.workspace = true
hex.workspace = true
rstest.workspace = true

[[bin]]
name = "chainstate-db-dumper"
path = "src/dumper/main.rs"
61 changes: 61 additions & 0 deletions chainstate/db-dumper/src/dumper/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2021-2025 RBB S.r.l
// opensource@mintlayer.org
// SPDX-License-Identifier: MIT
// Licensed under the MIT License;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use chainstate_launcher::SUBDIRECTORY_LMDB;
use logging::{init_logging, log};
use utils::default_data_dir::default_data_dir_for_chain;

use chainstate_db_dumper_lib::{dump_blocks_to_file, parse_block_output_fields_list};

use crate::options::{default_fields, Options};

mod options;

fn run() -> anyhow::Result<()> {
let opts = Options::parse();
let chain_type = opts.chain_type.chain_type();
let db_dir = opts
.db_dir
.unwrap_or_else(|| default_data_dir_for_chain(chain_type.name()).join(SUBDIRECTORY_LMDB));
let fields = opts.fields.map(|fields| parse_block_output_fields_list(&fields)).transpose()?;
let fields = fields.as_deref().unwrap_or(default_fields(opts.mainchain_only));

log::info!("Using db dir {}", db_dir.display());

dump_blocks_to_file(
chain_type,
db_dir,
opts.mainchain_only,
opts.from_height,
fields,
&opts.output_file,
)?;
Ok(())
}

fn main() {
utils::rust_backtrace::enable();

if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "info");
}

init_logging();

run().unwrap_or_else(|err| {
eprintln!("Error: {err:?}");
std::process::exit(1)
})
}
117 changes: 117 additions & 0 deletions chainstate/db-dumper/src/dumper/options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (c) 2021-2025 RBB S.r.l
// opensource@mintlayer.org
// SPDX-License-Identifier: MIT
// Licensed under the MIT License;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::PathBuf;

use clap::FromArgMatches as _;
use itertools::Itertools as _;
use strum::IntoEnumIterator as _;

use common::chain::config::ChainType;

use chainstate_db_dumper_lib::{
BlockOutputField, DEFAULT_BLOCK_OUTPUT_FIELDS_MAINCHAIN_ONLY,
DEFAULT_BLOCK_OUTPUT_FIELDS_WITH_STALE_CHAINS,
};

#[derive(clap::ValueEnum, Debug, Clone)]
pub enum ChainTypeOption {
Mainnet,
Testnet,
Regtest,
Signet,
}

impl ChainTypeOption {
pub fn chain_type(&self) -> ChainType {
match self {
ChainTypeOption::Mainnet => ChainType::Mainnet,
ChainTypeOption::Testnet => ChainType::Testnet,
ChainTypeOption::Regtest => ChainType::Regtest,
ChainTypeOption::Signet => ChainType::Signet,
}
}
}

const MAINCHAIN_ONLY_OPT_NAME: &str = "mainchain-only";

/// Dump block information from the chainstate db into a CSV file
#[derive(clap::Parser, Debug, Clone)]
pub struct Options {
/// Chain type
#[clap(short, long = "chain-type")]
pub chain_type: ChainTypeOption,

/// The path to the chainstate-lmdb directory.
///
/// The default value is the default location corresponding to the specified chain type.
#[clap(short, long = "db-dir")]
pub db_dir: Option<PathBuf>,

/// Output file
#[clap(short, long = "output-file")]
pub output_file: PathBuf,

/// Whether to only dump mainchain blocks
#[clap(long = MAINCHAIN_ONLY_OPT_NAME, action = clap::ArgAction::Set, default_value_t = true)]
pub mainchain_only: bool,

/// Block height to start from
#[clap(long = "from_height", default_value_t = 0)]
pub from_height: u64,

/// This help string
#[clap(long = "fields")]
pub fields: Option<String>,
}

impl Options {
/// Build the command adding custom description to "fields".
pub fn build() -> clap::Command {
let default_fields_mc_only = default_fields(true).iter().join(",");
let default_fields_all_blocks = default_fields(false).iter().join(",");
let all_fields = BlockOutputField::iter().join(", ");

let fields_help = format!(
concat!(
"Comma-separated list of fields to dump.\n",
"The default value depends on --{}, if true: '{}', if false: '{}'\n",
"All possible fields are: {}"
),
MAINCHAIN_ONLY_OPT_NAME, default_fields_mc_only, default_fields_all_blocks, all_fields
);

let cmd = <Self as clap::CommandFactory>::command();
cmd.mut_arg("fields", |arg| arg.help(fields_help))
}

/// Custom `parse` function that used `build` defined above.
pub fn parse() -> Self {
let matches = Self::build().get_matches();

match Self::from_arg_matches(&matches) {
Ok(this) => this,
Err(err) => err.exit(),
}
}
}

pub fn default_fields(mainchain_only: bool) -> &'static [BlockOutputField] {
if mainchain_only {
&DEFAULT_BLOCK_OUTPUT_FIELDS_MAINCHAIN_ONLY
} else {
&DEFAULT_BLOCK_OUTPUT_FIELDS_WITH_STALE_CHAINS
}
}
Loading
Loading