feat: improve cli experience

This commit is contained in:
Ludo Galabru
2023-07-13 00:01:43 -04:00
parent 3a920966b6
commit e8656285b2

View File

@@ -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)
}