From 8e48c91de506162763c029e480e2cf40da3713ba Mon Sep 17 00:00:00 2001 From: Greg Martin Date: Fri, 31 Mar 2023 12:43:07 +0000 Subject: [PATCH 01/11] Allow running `find` on a range of sats. --- src/index.rs | 49 ++++++++++++++++++++++++++++++++++++++++++ src/subcommand/find.rs | 29 ++++++++++++++++++++----- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/index.rs b/src/index.rs index eb79d8f2a4..77a818c13c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -131,6 +131,13 @@ impl BitcoinCoreRpcResultExt for Result { } } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct FindRangeOutput { + pub start: u64, + pub size: u64, + pub satpoint: SatPoint, +} + impl Index { pub(crate) fn open(options: &Options) -> Result { let rpc_url = options.rpc_url(); @@ -636,6 +643,48 @@ impl Index { Ok(None) } + pub(crate) fn find_range(&self, search_start: u64, search_end: u64) -> Result>> { + self.require_sat_index("find")?; + + let rtx = self.begin_read()?; + + if rtx.block_count()? <= Sat(search_end - 1).height().n() { + return Ok(None); + } + + let mut remaining_sats = search_end - search_start; + + let outpoint_to_sat_ranges = rtx.0.open_table(OUTPOINT_TO_SAT_RANGES)?; + + let mut result = Vec::new(); + for (key, value) in outpoint_to_sat_ranges.range::<&[u8; 36]>(&[0; 36]..)? { + let mut offset = 0; + for chunk in value.value().chunks_exact(11) { + let (start, end) = SatRange::load(chunk.try_into().unwrap()); + if start < search_end && search_start < end { + let overlap_start = cmp::max(start, search_start); + let overlap_end = cmp::min(search_end, end); + result.push(FindRangeOutput { + start: overlap_start, + size: overlap_end - overlap_start, + satpoint: SatPoint { + outpoint: Entry::load(*key.value()), + offset: offset + overlap_start - start, + }, + }); + + remaining_sats -= overlap_end - overlap_start; + if remaining_sats == 0 { + break; + } + } + offset += end - start; + } + } + + Ok(Some(result)) + } + fn list_inner(&self, outpoint: OutPointValue) -> Result>> { Ok( self diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index fdf314cc48..66c75dcc65 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -4,6 +4,8 @@ use super::*; pub(crate) struct Find { #[clap(help = "Find output and offset of .")] sat: Sat, + #[clap(help = "Find output and offset of all sats in the range -.")] + end: Option, } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -17,12 +19,29 @@ impl Find { index.update()?; - match index.find(self.sat.0)? { - Some(satpoint) => { - print_json(Output { satpoint })?; - Ok(()) + match self.end { + Some(end) => { + if self.sat < end { + match index.find_range(self.sat.0, end.0)? { + Some(result) => { + print_json(result)?; + Ok(()) + } + None => Err(anyhow!("range has not been mined as of index height")), + } + } else { + Err(anyhow!("range is empty")) + } + } + None => { + match index.find(self.sat.0)? { + Some(satpoint) => { + print_json(Output { satpoint })?; + Ok(()) + } + None => Err(anyhow!("sat has not been mined as of index height")), + } } - None => Err(anyhow!("sat has not been mined as of index height")), } } } From 1764dd238853e3074631d4ebb6f1b83942ae5ab0 Mon Sep 17 00:00:00 2001 From: Greg Martin Date: Sun, 9 Apr 2023 21:37:58 +0000 Subject: [PATCH 02/11] Format --- src/index.rs | 6 +++++- src/subcommand/find.rs | 14 ++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/index.rs b/src/index.rs index 77a818c13c..686167aa23 100644 --- a/src/index.rs +++ b/src/index.rs @@ -643,7 +643,11 @@ impl Index { Ok(None) } - pub(crate) fn find_range(&self, search_start: u64, search_end: u64) -> Result>> { + pub(crate) fn find_range( + &self, + search_start: u64, + search_end: u64, + ) -> Result>> { self.require_sat_index("find")?; let rtx = self.begin_read()?; diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index 66c75dcc65..692b9da8a6 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -33,15 +33,13 @@ impl Find { Err(anyhow!("range is empty")) } } - None => { - match index.find(self.sat.0)? { - Some(satpoint) => { - print_json(Output { satpoint })?; - Ok(()) - } - None => Err(anyhow!("sat has not been mined as of index height")), + None => match index.find(self.sat.0)? { + Some(satpoint) => { + print_json(Output { satpoint })?; + Ok(()) } - } + None => Err(anyhow!("sat has not been mined as of index height")), + }, } } } From 1f353cf7e7e773a0c34d94ba55a5723c6bcb6825 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 21:50:42 +0200 Subject: [PATCH 03/11] add tests --- src/index.rs | 20 ++++++++--------- src/subcommand/find.rs | 7 ++++++ tests/find.rs | 49 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/index.rs b/src/index.rs index b239c4b111..396b363413 100644 --- a/src/index.rs +++ b/src/index.rs @@ -8,6 +8,7 @@ use { updater::Updater, }, super::*, + crate::subcommand::find::FindRangeOutput, crate::wallet::Wallet, bitcoin::block::Header, bitcoincore_rpc::{json::GetBlockHeaderResult, Client}, @@ -134,13 +135,6 @@ impl BitcoinCoreRpcResultExt for Result { } } -#[derive(Debug, PartialEq, Serialize, Deserialize)] -pub struct FindRangeOutput { - pub start: u64, - pub size: u64, - pub satpoint: SatPoint, -} - pub(crate) struct Index { client: Client, database: Database, @@ -837,23 +831,27 @@ impl Index { let mut result = Vec::new(); for range in outpoint_to_sat_ranges.range::<&[u8; 36]>(&[0; 36]..)? { - let (key, value) = range?; + let (outpoint_entry, sat_ranges_entry) = range?; + let mut offset = 0; - for chunk in value.value().chunks_exact(11) { - let (start, end) = SatRange::load(chunk.try_into().unwrap()); + for sat_range in sat_ranges_entry.value().chunks_exact(11) { + let (start, end) = SatRange::load(sat_range.try_into().unwrap()); + if start < search_end && search_start < end { let overlap_start = cmp::max(start, search_start); let overlap_end = cmp::min(search_end, end); + result.push(FindRangeOutput { start: overlap_start, size: overlap_end - overlap_start, satpoint: SatPoint { - outpoint: Entry::load(*key.value()), + outpoint: Entry::load(*outpoint_entry.value()), offset: offset + overlap_start - start, }, }); remaining_sats -= overlap_end - overlap_start; + if remaining_sats == 0 { break; } diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index b8e27c5927..7b8231089b 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -13,6 +13,13 @@ pub struct Output { pub satpoint: SatPoint, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct FindRangeOutput { + pub start: u64, + pub size: u64, + pub satpoint: SatPoint, +} + impl Find { pub(crate) fn run(self, options: Options) -> SubcommandResult { let index = Index::open(&options)?; diff --git a/tests/find.rs b/tests/find.rs index 63d55497f0..70b51f114e 100644 --- a/tests/find.rs +++ b/tests/find.rs @@ -1,4 +1,7 @@ -use {super::*, ord::subcommand::find::Output}; +use { + super::*, + ord::subcommand::find::{FindRangeOutput, Output}, +}; #[test] fn find_command_returns_satpoint_for_sat() { @@ -15,6 +18,50 @@ fn find_command_returns_satpoint_for_sat() { ); } +#[test] +fn find_range_command_returns_satpoints_and_ranges() { + let rpc_server = test_bitcoincore_rpc::spawn(); + + rpc_server.mine_blocks(1); + + pretty_assert_eq!( + CommandBuilder::new(format!("--index-sats find 0 {}", 55 * COIN_VALUE)) + .rpc_server(&rpc_server) + .run_and_deserialize_output::>(), + vec![ + FindRangeOutput { + start: 0, + size: 50 * COIN_VALUE, + satpoint: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0" + .parse() + .unwrap() + }, + FindRangeOutput { + start: 50 * COIN_VALUE, + size: 5 * COIN_VALUE, + satpoint: "30f2f037629c6a21c1f40ed39b9bd6278df39762d68d07f49582b23bcb23386a:0:0" + .parse() + .unwrap() + } + ] + ); +} + +#[test] +fn find_range_command_fails_for_unmined_sat_ranges() { + let rpc_server = test_bitcoincore_rpc::spawn(); + + CommandBuilder::new(format!( + "--index-sats find {} {}", + 50 * COIN_VALUE, + 100 * COIN_VALUE + )) + .rpc_server(&rpc_server) + .expected_exit_code(1) + .expected_stderr("error: range has not been mined as of index height\n") + .run_and_extract_stdout(); +} + #[test] fn unmined_sat() { let rpc_server = test_bitcoincore_rpc::spawn(); From 7222bc7c0a48c495eae257b263ddaab17609561d Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 21:58:44 +0200 Subject: [PATCH 04/11] quick fix --- src/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 396b363413..86719aab69 100644 --- a/src/index.rs +++ b/src/index.rs @@ -821,7 +821,7 @@ impl Index { let rtx = self.begin_read()?; - if rtx.block_count()? <= Sat(search_end - 1).height().n() { + if rtx.block_count()? < Sat(search_end - 1).height().n() + 1 { return Ok(None); } From 8d110bd84694b92018027a295240dad74fd113ba Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 22:02:27 +0200 Subject: [PATCH 05/11] quick fix --- src/index.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 86719aab69..5f0e254a9f 100644 --- a/src/index.rs +++ b/src/index.rs @@ -825,7 +825,9 @@ impl Index { return Ok(None); } - let mut remaining_sats = search_end - search_start; + let Some(mut remaining_sat) = search_end.checked_sub(search_start) else { + return Err(anyhow!("range end is before range start")); + }; let outpoint_to_sat_ranges = rtx.0.open_table(OUTPOINT_TO_SAT_RANGES)?; From 45469cfd4a0e6e2b1e9f1b0f4af867fa72fc880e Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 22:02:59 +0200 Subject: [PATCH 06/11] quick fix --- src/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 5f0e254a9f..5341c3f6b4 100644 --- a/src/index.rs +++ b/src/index.rs @@ -825,7 +825,7 @@ impl Index { return Ok(None); } - let Some(mut remaining_sat) = search_end.checked_sub(search_start) else { + let Some(mut remaining_sats) = search_end.checked_sub(search_start) else { return Err(anyhow!("range end is before range start")); }; From 2617fb44d997cad2de2f410be1eed34f09aa2feb Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 22:06:28 +0200 Subject: [PATCH 07/11] quick fix --- src/index.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/index.rs b/src/index.rs index 5341c3f6b4..2ab698dfcc 100644 --- a/src/index.rs +++ b/src/index.rs @@ -814,18 +814,18 @@ impl Index { pub(crate) fn find_range( &self, - search_start: u64, - search_end: u64, + range_start: u64, + range_end: u64, ) -> Result>> { self.require_sat_index("find")?; let rtx = self.begin_read()?; - if rtx.block_count()? < Sat(search_end - 1).height().n() + 1 { + if rtx.block_count()? < Sat(range_end - 1).height().n() + 1 { return Ok(None); } - let Some(mut remaining_sats) = search_end.checked_sub(search_start) else { + let Some(mut remaining_sats) = range_end.checked_sub(range_start) else { return Err(anyhow!("range end is before range start")); }; @@ -839,9 +839,9 @@ impl Index { for sat_range in sat_ranges_entry.value().chunks_exact(11) { let (start, end) = SatRange::load(sat_range.try_into().unwrap()); - if start < search_end && search_start < end { - let overlap_start = cmp::max(start, search_start); - let overlap_end = cmp::min(search_end, end); + if range_start < end && range_end > start { + let overlap_start = cmp::max(start, range_start); + let overlap_end = cmp::min(range_end, end); result.push(FindRangeOutput { start: overlap_start, From f1bbefbc717388c5fba358439d5752c5048a8ade Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 22:07:43 +0200 Subject: [PATCH 08/11] quick fix --- src/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 2ab698dfcc..f65ecbf0b1 100644 --- a/src/index.rs +++ b/src/index.rs @@ -839,7 +839,7 @@ impl Index { for sat_range in sat_ranges_entry.value().chunks_exact(11) { let (start, end) = SatRange::load(sat_range.try_into().unwrap()); - if range_start < end && range_end > start { + if end > range_start && start < range_end { let overlap_start = cmp::max(start, range_start); let overlap_end = cmp::min(range_end, end); From 86fc9ce0aeb860bcd6841969072450756da09710 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 22:11:14 +0200 Subject: [PATCH 09/11] quick fix --- src/index.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.rs b/src/index.rs index f65ecbf0b1..520b3c6452 100644 --- a/src/index.rs +++ b/src/index.rs @@ -840,8 +840,8 @@ impl Index { let (start, end) = SatRange::load(sat_range.try_into().unwrap()); if end > range_start && start < range_end { - let overlap_start = cmp::max(start, range_start); - let overlap_end = cmp::min(range_end, end); + let overlap_start = start.max(range_start); + let overlap_end = end.min(range_end); result.push(FindRangeOutput { start: overlap_start, From b84607dab62ae8e5789ebf5fe8bc4dcbbe779764 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 22:13:25 +0200 Subject: [PATCH 10/11] quick fix --- src/subcommand/find.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index 7b8231089b..198df60de5 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -4,7 +4,7 @@ use super::*; pub(crate) struct Find { #[arg(help = "Find output and offset of .")] sat: Sat, - #[clap(help = "Find output and offset of all sats in the range -.")] + #[clap(help = "Find output and offset of all sats in the range [, ).")] end: Option, } From c38f88179855d36fa2156a0716782cf8170597c1 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Tue, 19 Sep 2023 22:14:21 +0200 Subject: [PATCH 11/11] quick fix --- src/subcommand/find.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index 198df60de5..b52601a2c6 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -27,16 +27,10 @@ impl Find { index.update()?; match self.end { - Some(end) => { - if self.sat < end { - match index.find_range(self.sat.0, end.0)? { - Some(result) => Ok(Box::new(result)), - None => Err(anyhow!("range has not been mined as of index height")), - } - } else { - Err(anyhow!("range is empty")) - } - } + Some(end) => match index.find_range(self.sat.0, end.0)? { + Some(result) => Ok(Box::new(result)), + None => Err(anyhow!("range has not been mined as of index height")), + }, None => match index.find(self.sat.0)? { Some(satpoint) => Ok(Box::new(Output { satpoint })), None => Err(anyhow!("sat has not been mined as of index height")),