mirror of
https://github.com/alexgo-io/bitcoin-indexer.git
synced 2026-01-12 16:52:57 +08:00
feat: improve cli experience
This commit is contained in:
@@ -1,19 +1,27 @@
|
||||
use crate::config::generator::generate_config;
|
||||
use crate::config::{Config, PredicatesApi};
|
||||
use crate::scan::bitcoin::scan_bitcoin_chainstate_via_rpc_using_predicate;
|
||||
use crate::service::Service;
|
||||
|
||||
use crate::db::{
|
||||
delete_data_in_hord_db, find_all_inscriptions_in_block, find_last_block_inserted,
|
||||
delete_data_in_hord_db, find_all_inscription_transfers, find_all_inscriptions_in_block,
|
||||
find_all_transfers_in_block, find_initial_inscription_transfer_data, find_inscription_with_id,
|
||||
find_last_block_inserted, find_latest_inscription_block_height,
|
||||
find_lazy_block_at_block_height, find_watched_satpoint_for_inscription, initialize_hord_db,
|
||||
insert_entry_in_locations, open_readonly_hord_db_conn, open_readonly_hord_db_conn_rocks_db,
|
||||
open_readwrite_hord_db_conn, open_readwrite_hord_db_conn_rocks_db,
|
||||
remove_entry_from_inscriptions, retrieve_satoshi_point_using_lazy_storage,
|
||||
open_readwrite_hord_db_conn, open_readwrite_hord_db_conn_rocks_db, rebuild_rocks_db,
|
||||
remove_entries_from_locations_at_block_height, retrieve_satoshi_point_using_lazy_storage,
|
||||
};
|
||||
use crate::hord::{
|
||||
self, new_traversals_lazy_cache, retrieve_inscribed_satoshi_points_from_block,
|
||||
update_storage_and_augment_bitcoin_block_with_inscription_transfer_data, Storage,
|
||||
};
|
||||
use chainhook_sdk::chainhooks::types::ChainhookFullSpecification;
|
||||
use chainhook_sdk::bitcoincore_rpc::{Auth, Client, RpcApi};
|
||||
use chainhook_sdk::chainhooks::types::HttpHook;
|
||||
use chainhook_sdk::chainhooks::types::{
|
||||
BitcoinChainhookFullSpecification, BitcoinChainhookNetworkSpecification, BitcoinPredicateType,
|
||||
ChainhookFullSpecification, HookAction, OrdinalOperations,
|
||||
};
|
||||
use chainhook_sdk::indexer::bitcoin::{
|
||||
download_and_parse_block_with_retry, retrieve_block_hash_with_retry,
|
||||
};
|
||||
@@ -22,7 +30,7 @@ use chainhook_sdk::utils::Context;
|
||||
use chainhook_types::{BitcoinBlockData, BlockIdentifier, TransactionIdentifier};
|
||||
use clap::{Parser, Subcommand};
|
||||
use hiro_system_kit;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::io::{BufReader, Read};
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
@@ -41,6 +49,9 @@ enum Command {
|
||||
/// Generate a new configuration file
|
||||
#[clap(subcommand)]
|
||||
Config(ConfigCommand),
|
||||
/// Scan the Bitcoin chain for inscriptions
|
||||
#[clap(subcommand)]
|
||||
Scan(ScanCommand),
|
||||
/// Stream Bitcoin blocks and index ordinals inscriptions and transfers
|
||||
#[clap(subcommand)]
|
||||
Service(ServiceCommand),
|
||||
@@ -50,6 +61,128 @@ enum Command {
|
||||
/// Test inscriptions and transfers computation algorithms
|
||||
#[clap(subcommand)]
|
||||
Test(TestCommand),
|
||||
/// Db maintenance related commands
|
||||
#[clap(subcommand)]
|
||||
Repair(RepairCommand),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Clone, Debug)]
|
||||
enum ScanCommand {
|
||||
/// Scans blocks for Ordinals activities
|
||||
#[clap(name = "blocks", bin_name = "blocks")]
|
||||
Blocks(ScanBlocksCommand),
|
||||
/// Retrieve activities for a given inscription
|
||||
#[clap(name = "inscription", bin_name = "inscription")]
|
||||
Inscription(ScanInscriptionCommand),
|
||||
}
|
||||
|
||||
#[derive(Parser, PartialEq, Clone, Debug)]
|
||||
struct ScanBlocksCommand {
|
||||
/// Starting block
|
||||
pub start_block: u64,
|
||||
/// Starting block
|
||||
pub end_block: u64,
|
||||
/// Target Regtest network
|
||||
#[clap(
|
||||
long = "regtest",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub regtest: bool,
|
||||
/// Target Testnet network
|
||||
#[clap(
|
||||
long = "testnet",
|
||||
conflicts_with = "regtest",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub testnet: bool,
|
||||
/// Target Mainnet network
|
||||
#[clap(
|
||||
long = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub mainnet: bool,
|
||||
/// Load config file path
|
||||
#[clap(
|
||||
long = "config-path",
|
||||
conflicts_with = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub config_path: Option<String>,
|
||||
/// HTTP Post activity to a URL
|
||||
#[clap(long = "post-to")]
|
||||
pub post_to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Parser, PartialEq, Clone, Debug)]
|
||||
struct ScanInscriptionCommand {
|
||||
/// Inscription Id
|
||||
pub inscription_id: String,
|
||||
/// Target Regtest network
|
||||
#[clap(
|
||||
long = "regtest",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub regtest: bool,
|
||||
/// Target Testnet network
|
||||
#[clap(
|
||||
long = "testnet",
|
||||
conflicts_with = "regtest",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub testnet: bool,
|
||||
/// Target Mainnet network
|
||||
#[clap(
|
||||
long = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub mainnet: bool,
|
||||
/// Load config file path
|
||||
#[clap(
|
||||
long = "config-path",
|
||||
conflicts_with = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub config_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Clone, Debug)]
|
||||
enum RepairCommand {
|
||||
/// Rewrite blocks hord db
|
||||
#[clap(name = "blocks", bin_name = "blocks")]
|
||||
Blocks(RepairBlocksCommand),
|
||||
/// Rewrite blocks hord db
|
||||
#[clap(name = "transfers", bin_name = "transfers")]
|
||||
Transfers(RepairTransfersCommand),
|
||||
}
|
||||
|
||||
#[derive(Parser, PartialEq, Clone, Debug)]
|
||||
struct RepairBlocksCommand {
|
||||
/// Starting block
|
||||
pub start_block: u64,
|
||||
/// Starting block
|
||||
pub end_block: u64,
|
||||
/// Network threads
|
||||
pub network_threads: usize,
|
||||
/// Load config file path
|
||||
#[clap(long = "config-path")]
|
||||
pub config_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Parser, PartialEq, Clone, Debug)]
|
||||
struct RepairTransfersCommand {
|
||||
/// Starting block
|
||||
pub start_block: u64,
|
||||
/// Starting block
|
||||
pub end_block: u64,
|
||||
/// Load config file path
|
||||
#[clap(long = "config-path")]
|
||||
pub config_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Clone, Debug)]
|
||||
@@ -62,17 +195,17 @@ enum ConfigCommand {
|
||||
|
||||
#[derive(Parser, PartialEq, Clone, Debug)]
|
||||
struct NewConfig {
|
||||
/// Target Devnet network
|
||||
/// Target Regtest network
|
||||
#[clap(
|
||||
long = "devnet",
|
||||
long = "regtest",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub devnet: bool,
|
||||
pub regtest: bool,
|
||||
/// Target Testnet network
|
||||
#[clap(
|
||||
long = "testnet",
|
||||
conflicts_with = "devnet",
|
||||
conflicts_with = "regtest",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub testnet: bool,
|
||||
@@ -80,7 +213,7 @@ struct NewConfig {
|
||||
#[clap(
|
||||
long = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "devnet"
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub mainnet: bool,
|
||||
}
|
||||
@@ -94,17 +227,17 @@ enum ServiceCommand {
|
||||
|
||||
#[derive(Parser, PartialEq, Clone, Debug)]
|
||||
struct StartCommand {
|
||||
/// Target Devnet network
|
||||
/// Target Regtest network
|
||||
#[clap(
|
||||
long = "devnet",
|
||||
long = "regtest",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub devnet: bool,
|
||||
pub regtest: bool,
|
||||
/// Target Testnet network
|
||||
#[clap(
|
||||
long = "testnet",
|
||||
conflicts_with = "devnet",
|
||||
conflicts_with = "regtest",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub testnet: bool,
|
||||
@@ -112,7 +245,7 @@ struct StartCommand {
|
||||
#[clap(
|
||||
long = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "devnet"
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub mainnet: bool,
|
||||
/// Load config file path
|
||||
@@ -120,15 +253,15 @@ struct StartCommand {
|
||||
long = "config-path",
|
||||
conflicts_with = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "devnet"
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub config_path: Option<String>,
|
||||
/// Specify relative path of the chainhooks (yaml format) to evaluate
|
||||
#[clap(long = "predicate-path")]
|
||||
pub predicates_paths: Vec<String>,
|
||||
/// Start REST API for managing predicates
|
||||
#[clap(long = "start-http-api")]
|
||||
pub start_http_api: bool,
|
||||
#[clap(long = "post-to")]
|
||||
pub post_to: Vec<String>,
|
||||
/// Block height where hord will start posting Ordinals activities
|
||||
#[clap(long = "start-at-block")]
|
||||
pub start_at_block: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Clone, Debug)]
|
||||
@@ -163,17 +296,17 @@ struct ScanInscriptionsCommand {
|
||||
pub block_height: u64,
|
||||
/// Txid
|
||||
pub txid: Option<String>,
|
||||
/// Target Devnet network
|
||||
/// Target Regtest network
|
||||
#[clap(
|
||||
long = "devnet",
|
||||
long = "regtest",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub devnet: bool,
|
||||
pub regtest: bool,
|
||||
/// Target Testnet network
|
||||
#[clap(
|
||||
long = "testnet",
|
||||
conflicts_with = "devnet",
|
||||
conflicts_with = "regtest",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub testnet: bool,
|
||||
@@ -181,7 +314,7 @@ struct ScanInscriptionsCommand {
|
||||
#[clap(
|
||||
long = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "devnet"
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub mainnet: bool,
|
||||
/// Load config file path
|
||||
@@ -189,7 +322,7 @@ struct ScanInscriptionsCommand {
|
||||
long = "config-path",
|
||||
conflicts_with = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "devnet"
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub config_path: Option<String>,
|
||||
}
|
||||
@@ -200,17 +333,17 @@ struct ScanTransfersCommand {
|
||||
pub inscription_id: String,
|
||||
/// Block height
|
||||
pub block_height: Option<u64>,
|
||||
/// Target Devnet network
|
||||
/// Target Regtest network
|
||||
#[clap(
|
||||
long = "devnet",
|
||||
long = "regtest",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub devnet: bool,
|
||||
pub regtest: bool,
|
||||
/// Target Testnet network
|
||||
#[clap(
|
||||
long = "testnet",
|
||||
conflicts_with = "devnet",
|
||||
conflicts_with = "regtest",
|
||||
conflicts_with = "mainnet"
|
||||
)]
|
||||
pub testnet: bool,
|
||||
@@ -218,7 +351,7 @@ struct ScanTransfersCommand {
|
||||
#[clap(
|
||||
long = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "devnet"
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub mainnet: bool,
|
||||
/// Load config file path
|
||||
@@ -226,7 +359,7 @@ struct ScanTransfersCommand {
|
||||
long = "config-path",
|
||||
conflicts_with = "mainnet",
|
||||
conflicts_with = "testnet",
|
||||
conflicts_with = "devnet"
|
||||
conflicts_with = "regtest"
|
||||
)]
|
||||
pub config_path: Option<String>,
|
||||
}
|
||||
@@ -241,7 +374,7 @@ struct UpdateHordDbCommand {
|
||||
#[clap(long = "config-path")]
|
||||
pub config_path: Option<String>,
|
||||
/// Transfers only
|
||||
pub transfers_only: bool,
|
||||
pub transfers_only: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Parser, PartialEq, Clone, Debug)]
|
||||
@@ -299,37 +432,197 @@ pub fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
match hiro_system_kit::nestable_block_on(handle_command(opts, ctx)) {
|
||||
match hiro_system_kit::nestable_block_on(handle_command(opts, &ctx)) {
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
error!(ctx.expect_logger(), "{e}");
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
process::exit(1);
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
|
||||
pub enum BlockHeights {
|
||||
OpenEndedRange(u64),
|
||||
BlockRange(u64, u64),
|
||||
Blocks(Vec<u64>),
|
||||
}
|
||||
|
||||
impl BlockHeights {
|
||||
pub fn get_sorted_entries(&self) -> VecDeque<u64> {
|
||||
let mut entries = VecDeque::new();
|
||||
match &self {
|
||||
BlockHeights::OpenEndedRange(min) => {}
|
||||
BlockHeights::BlockRange(start, end) => {
|
||||
let min = *start.min(end);
|
||||
let max = *start.max(end);
|
||||
for i in min..=max {
|
||||
entries.push_back(i);
|
||||
}
|
||||
}
|
||||
BlockHeights::Blocks(heights) => {
|
||||
let mut sorted_entries = heights.clone();
|
||||
sorted_entries.sort();
|
||||
let mut unique_sorted_entries = BTreeSet::new();
|
||||
for entry in sorted_entries.into_iter() {
|
||||
unique_sorted_entries.insert(entry);
|
||||
}
|
||||
for entry in unique_sorted_entries.into_iter() {
|
||||
entries.push_back(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
entries
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> {
|
||||
match opts.command {
|
||||
Command::Scan(ScanCommand::Blocks(cmd)) => {
|
||||
let config: Config =
|
||||
Config::default(cmd.regtest, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
// Download dataset if required
|
||||
// If console:
|
||||
// - Replay based on SQLite queries
|
||||
// If post-to:
|
||||
// - Replay that requires connection to bitcoind
|
||||
let mut block_range =
|
||||
BlockHeights::BlockRange(cmd.start_block, cmd.end_block).get_sorted_entries();
|
||||
|
||||
let bitcoind_required = cmd.post_to.is_some();
|
||||
if let Some(ref post_to) = cmd.post_to {
|
||||
let tip = check_bitcoind_connection(&config).await?;
|
||||
if tip < cmd.end_block {
|
||||
error!(ctx.expect_logger(), "unable to scan block range [{}, {}]: underlying bitcoind synchronized until block {} ", cmd.start_block, cmd.end_block, tip);
|
||||
}
|
||||
|
||||
let predicate_spec = build_predicate_from_cli(
|
||||
&config,
|
||||
&post_to,
|
||||
cmd.start_block,
|
||||
Some(cmd.end_block),
|
||||
)?
|
||||
.into_selected_network_specification(&config.network.bitcoin_network)?;
|
||||
scan_bitcoin_chainstate_via_rpc_using_predicate(&predicate_spec, &config, &ctx)
|
||||
.await?;
|
||||
} else {
|
||||
let inscriptions_db_conn =
|
||||
open_readonly_hord_db_conn(&config.expected_cache_path(), &ctx)?;
|
||||
while let Some(block_height) = block_range.pop_front() {
|
||||
let mut total_transfers = 0;
|
||||
let inscriptions =
|
||||
find_all_inscriptions_in_block(&block_height, &inscriptions_db_conn, &ctx);
|
||||
let mut locations =
|
||||
find_all_transfers_in_block(&block_height, &inscriptions_db_conn, &ctx);
|
||||
for (txid, inscription) in inscriptions.iter() {
|
||||
println!("Inscription {} revealed at block #{} (inscription_number {}, ordinal_number {})", inscription.get_inscription_id(), block_height, inscription.inscription_number, inscription.ordinal_number);
|
||||
if let Some(transfers) = locations.remove(&inscription.get_inscription_id())
|
||||
{
|
||||
for t in transfers.iter().skip(1) {
|
||||
total_transfers += 1;
|
||||
println!(
|
||||
"\t→ Transferred in transaction {}",
|
||||
t.transaction_identifier_location.hash
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (inscription_id, transfers) in locations.iter() {
|
||||
println!("Inscription {}", inscription_id);
|
||||
for t in transfers.iter() {
|
||||
total_transfers += 1;
|
||||
println!(
|
||||
"\t→ Transferred in transaction {}",
|
||||
t.transaction_identifier_location.hash
|
||||
);
|
||||
}
|
||||
}
|
||||
println!(
|
||||
"Inscriptions revealed: {}, inscriptions transferred: {total_transfers}",
|
||||
inscriptions.len()
|
||||
);
|
||||
|
||||
println!("-----");
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Scan(ScanCommand::Inscription(cmd)) => {
|
||||
let config: Config =
|
||||
Config::default(cmd.regtest, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
let inscriptions_db_conn =
|
||||
open_readonly_hord_db_conn(&config.expected_cache_path(), &ctx)?;
|
||||
let (inscription, block_height) =
|
||||
match find_inscription_with_id(&cmd.inscription_id, &inscriptions_db_conn, &ctx)? {
|
||||
Some(entry) => entry,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"unable to retrieve inscription {}",
|
||||
cmd.inscription_id
|
||||
));
|
||||
}
|
||||
};
|
||||
println!(
|
||||
"Inscription {} revealed at block #{} (inscription_number {}, ordinal_number {})",
|
||||
inscription.get_inscription_id(),
|
||||
block_height,
|
||||
inscription.inscription_number,
|
||||
inscription.ordinal_number
|
||||
);
|
||||
let transfers = find_all_inscription_transfers(
|
||||
&inscription.get_inscription_id(),
|
||||
&inscriptions_db_conn,
|
||||
&ctx,
|
||||
);
|
||||
for (transfer, block_height) in transfers.iter().skip(1) {
|
||||
println!(
|
||||
"\t→ Transferred in transaction {} (block {block_height})",
|
||||
transfer.transaction_identifier_location.hash
|
||||
);
|
||||
}
|
||||
println!("Number of transfers: {}", transfers.len() - 1);
|
||||
}
|
||||
Command::Service(subcmd) => match subcmd {
|
||||
ServiceCommand::Start(cmd) => {
|
||||
let mut config =
|
||||
Config::default(cmd.devnet, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
// We disable the API if a predicate was passed, and the --enable-
|
||||
if cmd.predicates_paths.len() > 0 && !cmd.start_http_api {
|
||||
config.http_api = PredicatesApi::Off;
|
||||
}
|
||||
let config =
|
||||
Config::default(cmd.regtest, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
|
||||
let _ = initialize_hord_db(&config.expected_cache_path(), &ctx);
|
||||
|
||||
let predicates = cmd
|
||||
.predicates_paths
|
||||
.iter()
|
||||
.map(|p| load_predicate_from_path(p))
|
||||
.collect::<Result<Vec<ChainhookFullSpecification>, _>>()?;
|
||||
let inscriptions_db_conn =
|
||||
open_readonly_hord_db_conn(&config.expected_cache_path(), &ctx)?;
|
||||
|
||||
let last_known_block =
|
||||
find_latest_inscription_block_height(&inscriptions_db_conn, &ctx)?;
|
||||
|
||||
let hord_config = config.get_hord_config();
|
||||
|
||||
let start_block = match cmd.start_at_block {
|
||||
Some(entry) => entry,
|
||||
None => match last_known_block {
|
||||
Some(entry) => entry,
|
||||
None => {
|
||||
warn!(ctx.expect_logger(), "Inscription ingestion will start at block {} once hord internal indexes are built", hord_config.first_inscription_height);
|
||||
hord_config.first_inscription_height
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut predicates = vec![];
|
||||
|
||||
for post_to in cmd.post_to.iter() {
|
||||
let predicate = build_predicate_from_cli(&config, post_to, start_block, None)?;
|
||||
predicates.push(ChainhookFullSpecification::Bitcoin(predicate));
|
||||
}
|
||||
|
||||
// let predicates = cmd
|
||||
// .predicates_paths
|
||||
// .iter()
|
||||
// .map(|p| load_predicate_from_path(p))
|
||||
// .collect::<Result<Vec<ChainhookFullSpecification>, _>>()?;
|
||||
|
||||
info!(ctx.expect_logger(), "Starting service...",);
|
||||
|
||||
let mut service = Service::new(config, ctx);
|
||||
let mut service = Service::new(config, ctx.clone());
|
||||
return service.run(predicates).await;
|
||||
}
|
||||
},
|
||||
@@ -337,7 +630,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
|
||||
ConfigCommand::New(cmd) => {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
let config = Config::default(cmd.devnet, cmd.testnet, cmd.mainnet, &None)?;
|
||||
let config = Config::default(cmd.regtest, cmd.testnet, cmd.mainnet, &None)?;
|
||||
let config_content = generate_config(&config.network.bitcoin_network);
|
||||
let mut file_path = PathBuf::new();
|
||||
file_path.push("Hord.toml");
|
||||
@@ -349,7 +642,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
|
||||
}
|
||||
},
|
||||
Command::Test(TestCommand::Inscriptions(cmd)) => {
|
||||
let config = Config::default(cmd.devnet, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
let config = Config::default(cmd.regtest, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
|
||||
let tip_height = {
|
||||
let hord_db_conn =
|
||||
@@ -419,7 +712,7 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
Command::Test(TestCommand::Transfers(cmd)) => {
|
||||
let config = Config::default(cmd.devnet, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
let config = Config::default(cmd.regtest, cmd.testnet, cmd.mainnet, &cmd.config_path)?;
|
||||
|
||||
let inscriptions_db_conn =
|
||||
open_readonly_hord_db_conn(&config.expected_cache_path(), &ctx)?;
|
||||
@@ -509,26 +802,67 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
|
||||
info!(ctx.expect_logger(), "Database hord up to date");
|
||||
}
|
||||
}
|
||||
Command::Repair(subcmd) => match subcmd {
|
||||
RepairCommand::Blocks(cmd) => {
|
||||
let config = Config::default(false, false, false, &cmd.config_path)?;
|
||||
let mut hord_config = config.get_hord_config();
|
||||
hord_config.network_thread_max = cmd.network_threads;
|
||||
|
||||
let bitcoin_config = BitcoinConfig {
|
||||
username: config.network.bitcoind_rpc_username.clone(),
|
||||
password: config.network.bitcoind_rpc_password.clone(),
|
||||
rpc_url: config.network.bitcoind_rpc_url.clone(),
|
||||
network: config.network.bitcoin_network.clone(),
|
||||
bitcoin_block_signaling: config.network.bitcoin_block_signaling.clone(),
|
||||
};
|
||||
let blocks_db =
|
||||
open_readwrite_hord_db_conn_rocks_db(&config.expected_cache_path(), &ctx)?;
|
||||
|
||||
rebuild_rocks_db(
|
||||
&bitcoin_config,
|
||||
&blocks_db,
|
||||
cmd.start_block,
|
||||
cmd.end_block,
|
||||
&config.get_hord_config(),
|
||||
&ctx,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
RepairCommand::Transfers(cmd) => {
|
||||
let config = Config::default(false, false, false, &cmd.config_path)?;
|
||||
let service = Service::new(config, ctx.clone());
|
||||
service.replay_transfers(cmd.start_block, cmd.end_block, None)?;
|
||||
}
|
||||
},
|
||||
Command::Db(HordDbCommand::Rewrite(cmd)) => {
|
||||
let config = Config::default(false, false, false, &cmd.config_path)?;
|
||||
if cmd.transfers_only {
|
||||
let transfers_only = cmd.transfers_only.unwrap_or(true);
|
||||
if transfers_only {
|
||||
println!("Transfers only");
|
||||
let mut inscriptions_db_conn_rw =
|
||||
open_readwrite_hord_db_conn(&config.expected_cache_path(), &ctx)?;
|
||||
let inscriptions_db_conn =
|
||||
open_readonly_hord_db_conn(&config.expected_cache_path(), &ctx)?;
|
||||
|
||||
for cursor in cmd.start_block..cmd.end_block {
|
||||
for cursor in cmd.start_block..=cmd.end_block {
|
||||
println!("Cleaning transfers from block {}", cursor);
|
||||
let inscriptions =
|
||||
find_all_inscriptions_in_block(&cursor, &inscriptions_db_conn, &ctx);
|
||||
println!(
|
||||
"{} inscriptions retrieved at block {}",
|
||||
inscriptions.len(),
|
||||
cursor
|
||||
);
|
||||
|
||||
let transaction = inscriptions_db_conn_rw.transaction().unwrap();
|
||||
|
||||
remove_entries_from_locations_at_block_height(&cursor, &transaction, &ctx);
|
||||
|
||||
for (_, entry) in inscriptions.iter() {
|
||||
remove_entry_from_inscriptions(
|
||||
&entry.get_inscription_id(),
|
||||
&transaction,
|
||||
&ctx,
|
||||
);
|
||||
let inscription_id = entry.get_inscription_id();
|
||||
println!("Processing inscription {}", inscription_id);
|
||||
insert_entry_in_locations(
|
||||
&entry.get_inscription_id(),
|
||||
&inscription_id,
|
||||
cursor,
|
||||
&entry.transfer_data,
|
||||
&transaction,
|
||||
@@ -538,6 +872,8 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
|
||||
transaction.commit().unwrap();
|
||||
}
|
||||
|
||||
println!("Fetching blocks");
|
||||
|
||||
let bitcoin_config = BitcoinConfig {
|
||||
username: config.network.bitcoind_rpc_username.clone(),
|
||||
password: config.network.bitcoind_rpc_password.clone(),
|
||||
@@ -545,19 +881,30 @@ async fn handle_command(opts: Opts, ctx: Context) -> Result<(), String> {
|
||||
network: config.network.bitcoin_network.clone(),
|
||||
bitcoin_block_signaling: config.network.bitcoin_block_signaling.clone(),
|
||||
};
|
||||
|
||||
let (tx, rx) = channel();
|
||||
for cursor in cmd.start_block..cmd.end_block {
|
||||
let block = fetch_and_standardize_block(cursor, &bitcoin_config, &ctx)
|
||||
.await
|
||||
.unwrap();
|
||||
let _ = tx.send(block);
|
||||
}
|
||||
let moved_ctx = ctx.clone();
|
||||
hiro_system_kit::thread_named("Block fetch")
|
||||
.spawn(move || {
|
||||
for cursor in cmd.start_block..=cmd.end_block {
|
||||
println!("Fetching block {}", cursor);
|
||||
let future =
|
||||
fetch_and_standardize_block(cursor, &bitcoin_config, &moved_ctx);
|
||||
|
||||
let block = hiro_system_kit::nestable_block_on(future).unwrap();
|
||||
|
||||
let _ = tx.send(block);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let inscriptions_db_conn_rw =
|
||||
open_readwrite_hord_db_conn(&config.expected_cache_path(), &ctx)?;
|
||||
let mut storage = Storage::Sqlite(&inscriptions_db_conn_rw);
|
||||
while let Ok(mut block) = rx.recv() {
|
||||
println!(
|
||||
"Rewriting transfers for block {}",
|
||||
block.block_identifier.index
|
||||
);
|
||||
update_storage_and_augment_bitcoin_block_with_inscription_transfer_data(
|
||||
&mut block,
|
||||
&mut storage,
|
||||
@@ -666,3 +1013,65 @@ pub async fn fetch_and_standardize_block(
|
||||
hord::parse_ordinals_and_standardize_block(block_breakdown, &bitcoin_config.network, &ctx)
|
||||
.map_err(|(e, _)| e)
|
||||
}
|
||||
|
||||
pub fn build_predicate_from_cli(
|
||||
config: &Config,
|
||||
post_to: &str,
|
||||
start_block: u64,
|
||||
end_block: Option<u64>,
|
||||
) -> Result<BitcoinChainhookFullSpecification, String> {
|
||||
let mut networks = BTreeMap::new();
|
||||
// Retrieve last block height known, and display it
|
||||
networks.insert(
|
||||
config.network.bitcoin_network.clone(),
|
||||
BitcoinChainhookNetworkSpecification {
|
||||
start_block: Some(start_block),
|
||||
end_block,
|
||||
expire_after_occurrence: None,
|
||||
include_proof: None,
|
||||
include_inputs: None,
|
||||
include_outputs: None,
|
||||
include_witness: None,
|
||||
predicate: BitcoinPredicateType::OrdinalsProtocol(OrdinalOperations::InscriptionFeed),
|
||||
action: HookAction::HttpPost(HttpHook {
|
||||
url: post_to.to_string(),
|
||||
authorization_header: "".to_string(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
let predicate = BitcoinChainhookFullSpecification {
|
||||
uuid: post_to.to_string(),
|
||||
owner_uuid: None,
|
||||
name: post_to.to_string(),
|
||||
version: 1,
|
||||
networks,
|
||||
};
|
||||
|
||||
Ok(predicate)
|
||||
}
|
||||
|
||||
pub async fn check_bitcoind_connection(config: &Config) -> Result<u64, String> {
|
||||
let auth = Auth::UserPass(
|
||||
config.network.bitcoind_rpc_username.clone(),
|
||||
config.network.bitcoind_rpc_password.clone(),
|
||||
);
|
||||
|
||||
let bitcoin_rpc = match Client::new(&config.network.bitcoind_rpc_url, auth) {
|
||||
Ok(con) => con,
|
||||
Err(message) => {
|
||||
return Err(format!(
|
||||
"unable to connect to bitcoind: {}",
|
||||
message.to_string()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let end_block = match bitcoin_rpc.get_blockchain_info() {
|
||||
Ok(result) => result.blocks.saturating_sub(1),
|
||||
Err(e) => {
|
||||
return Err(format!("unable to connect to bitcoind: {}", e.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(end_block)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user