Skip to content

Commit

Permalink
Re-implement find (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
terror authored Apr 15, 2022
1 parent 3c2dfb8 commit 45f5729
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 10 deletions.
10 changes: 9 additions & 1 deletion src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl Index {
self.database.print_info()
}

fn decode_ordinal_range(bytes: [u8; 11]) -> (u64, u64) {
pub(crate) fn decode_ordinal_range(bytes: [u8; 11]) -> (u64, u64) {
let n = u128::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8],
bytes[9], bytes[10], 0, 0, 0, 0, 0,
Expand Down Expand Up @@ -228,6 +228,14 @@ impl Index {
}
}

pub(crate) fn find(&self, ordinal: Ordinal) -> Result<Option<SatPoint>> {
if self.database.height()? <= ordinal.height().0 {
return Ok(None);
}

self.database.find(ordinal)
}

pub(crate) fn list(&self, outpoint: OutPoint) -> Result<Option<Vec<(u64, u64)>>> {
let mut outpoint_encoded = Vec::new();
outpoint.consensus_encode(&mut outpoint_encoded)?;
Expand Down
25 changes: 25 additions & 0 deletions src/lmdb_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,31 @@ impl Database {
.map(|ranges| ranges.to_vec()),
)
}

pub(crate) fn find(&self, ordinal: Ordinal) -> Result<Option<SatPoint>> {
let tx = lmdb::ReadTransaction::new(self.environment.clone())?;

let access = tx.access();

let mut cursor = tx.cursor(&self.outpoint_to_ordinal_ranges)?;

while let Some((key, value)) = cursor.next::<[u8], [u8]>(&access).into_option()? {
let mut offset = 0;
for chunk in value.chunks_exact(11) {
let (start, end) = Index::decode_ordinal_range(chunk.try_into().unwrap());
if start <= ordinal.0 && ordinal.0 < end {
let outpoint: OutPoint = Decodable::consensus_decode(key)?;
return Ok(Some(SatPoint {
outpoint,
offset: offset + ordinal.0 - start,
}));
}
offset += end - start;
}
}

Ok(None)
}
}

pub(crate) struct WriteTransaction<'a> {
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use {
crate::{
arguments::Arguments, bytes::Bytes, epoch::Epoch, height::Height, index::Index,
options::Options, ordinal::Ordinal, subcommand::Subcommand,
options::Options, ordinal::Ordinal, sat_point::SatPoint, subcommand::Subcommand,
},
anyhow::{anyhow, Context, Error},
axum::{extract, http::StatusCode, response::IntoResponse, routing::get, Json, Router},
Expand Down
40 changes: 40 additions & 0 deletions src/redb_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,46 @@ impl Database {
Ok(())
}

pub(crate) fn height(&self) -> Result<u64> {
let tx = self.0.begin_read()?;

let height_to_hash = tx.open_table(&HEIGHT_TO_HASH)?;

Ok(
height_to_hash
.range(0..)?
.rev()
.next()
.map(|(height, _hash)| height + 1)
.unwrap_or(0),
)
}

pub(crate) fn find(&self, ordinal: Ordinal) -> Result<Option<SatPoint>> {
let rtx = self.0.begin_read()?;

let outpoint_to_ordinal_ranges = rtx.open_table(&OUTPOINT_TO_ORDINAL_RANGES)?;

let mut cursor = outpoint_to_ordinal_ranges.range([]..)?;

while let Some((key, value)) = cursor.next() {
let mut offset = 0;
for chunk in value.chunks_exact(11) {
let (start, end) = Index::decode_ordinal_range(chunk.try_into().unwrap());
if start <= ordinal.0 && ordinal.0 < end {
let outpoint: OutPoint = Decodable::consensus_decode(key)?;
return Ok(Some(SatPoint {
outpoint,
offset: offset + ordinal.0 - start,
}));
}
offset += end - start;
}
}

Ok(None)
}

pub(crate) fn list(&self, outpoint: &[u8]) -> Result<Option<Vec<u8>>> {
Ok(
self
Expand Down
3 changes: 3 additions & 0 deletions src/subcommand.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::*;

mod epochs;
mod find;
mod index;
mod info;
mod list;
Expand All @@ -13,6 +14,7 @@ mod traits;
#[derive(Parser)]
pub(crate) enum Subcommand {
Epochs,
Find(find::Find),
Index,
List(list::List),
Name(name::Name),
Expand All @@ -27,6 +29,7 @@ impl Subcommand {
pub(crate) fn run(self, options: Options) -> Result<()> {
match self {
Self::Epochs => epochs::run(),
Self::Find(find) => find.run(options),
Self::Index => index::run(options),
Self::List(list) => list.run(options),
Self::Name(name) => name.run(),
Expand Down
20 changes: 20 additions & 0 deletions src/subcommand/find.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use super::*;

#[derive(Parser)]
pub(crate) struct Find {
ordinal: Ordinal,
}

impl Find {
pub(crate) fn run(self, options: Options) -> Result<()> {
let index = Index::index(&options)?;

match index.find(self.ordinal)? {
Some(satpoint) => {
println!("{satpoint}");
Ok(())
}
None => Err(anyhow!("Ordinal has not been mined as of index height")),
}
}
}
154 changes: 154 additions & 0 deletions tests/find.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use super::*;

#[test]
fn first_satoshi() -> Result {
Test::new()?
.command("find 0")
.expected_stdout("0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0:0\n")
.block()
.run()
}

#[test]
#[ignore]
fn first_satoshi_slot() -> Result {
Test::new()?
.command("find 0 --slot")
.expected_stdout("0.0.0.0\n")
.block()
.run()
}

#[test]
fn second_satoshi() -> Result {
Test::new()?
.command("find 1")
.expected_stdout("0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0:1\n")
.block()
.run()
}

#[test]
#[ignore]
fn second_satoshi_slot() -> Result {
Test::new()?
.command("find 1 --slot")
.expected_stdout("0.0.0.1\n")
.block()
.run()
}

#[test]
fn first_satoshi_of_second_block() -> Result {
Test::new()?
.command("find 5000000000")
.expected_stdout("9068a11b8769174363376b606af9a4b8b29dd7b13d013f4b0cbbd457db3c3ce5:0:0\n")
.block()
.block()
.run()
}

#[test]
#[ignore]
fn first_satoshi_of_second_block_slot() -> Result {
Test::new()?
.command("find 5000000000 --slot")
.expected_stdout("1.0.0.0\n")
.block()
.block()
.run()
}

#[test]
fn first_satoshi_spent_in_second_block() -> Result {
Test::new()?
.command("find 0")
.expected_stdout("d0a9c70e6c8d890ee5883973a716edc1609eab42a9bc32594bdafc935bb4fad0:0:0\n")
.block()
.block()
.transaction(TransactionOptions {
slots: &[(0, 0, 0)],
output_count: 1,
fee: 0,
})
.run()
}

#[test]
#[ignore]
fn first_satoshi_spent_in_second_block_slot() -> Result {
Test::new()?
.command("find 0 --slot")
.expected_stdout("1.1.0.0\n")
.block()
.block()
.transaction(TransactionOptions {
slots: &[(0, 0, 0)],
output_count: 1,
fee: 0,
})
.run()
}

#[test]
#[ignore]
fn regression_empty_block_crash() -> Result {
Test::new()?
.command("find 0 --slot")
.block()
.block_with_coinbase(CoinbaseOptions {
include_coinbase_transaction: false,
..Default::default()
})
.expected_stdout("0.0.0.0\n")
.run()
}

#[test]
#[ignore]
fn mining_and_spending_transaction_in_same_block() -> Result {
Test::new()?
.command("find 0 --slot")
.block()
.block()
.transaction(TransactionOptions {
slots: &[(0, 0, 0)],
output_count: 1,
fee: 0,
})
.transaction(TransactionOptions {
slots: &[(1, 1, 0)],
output_count: 1,
fee: 0,
})
.expected_stdout("1.2.0.0\n")
.run()
}

#[test]
fn empty_index() -> Result {
Test::new()?
.expected_stderr("error: Ordinal has not been mined as of index height\n")
.expected_status(1)
.command("find 0")
.run()
}

#[test]
fn unmined_satoshi_in_second_block() -> Result {
Test::new()?
.block()
.expected_stderr("error: Ordinal has not been mined as of index height\n")
.expected_status(1)
.command("find 5000000000")
.run()
}

#[test]
fn unmined_satoshi_in_first_block() -> Result {
Test::new()?
.expected_stderr("error: Ordinal has not been mined as of index height\n")
.expected_status(1)
.command("find 0")
.run()
}
14 changes: 6 additions & 8 deletions tests/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ fn incremental_indexing() -> Result {
#[cfg(feature = "redb")]
fn custom_index_size() -> Result {
let tempdir = Test::new()?
.command("--index-size 2097152 list 0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0")
.expected_stdout("[0,5000000000)\n")
.command("--index-size 2097152 find 0")
.expected_stdout("0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0:0\n")
.block()
.output()?
.tempdir;
Expand All @@ -41,10 +41,8 @@ fn custom_index_size() -> Result {
#[cfg(feature = "redb")]
fn human_readable_index_size() -> Result {
let tempdir = Test::new()?
.command(
"--index-size 2mib list 0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0",
)
.expected_stdout("[0,5000000000)\n")
.command("--index-size 2mib find 0")
.expected_stdout("0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0:0\n")
.block()
.output()?
.tempdir;
Expand All @@ -58,8 +56,8 @@ fn human_readable_index_size() -> Result {
#[cfg(feature = "redb")]
fn default_index_size() -> Result {
let tempdir = Test::new()?
.command("list 0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0")
.expected_stdout("[0,5000000000)\n")
.command("find 0")
.expected_stdout("0396bc915f141f7de025f72ae9b6bb8dcdb5f444fc245d8fac486ba67a38eef9:0:0\n")
.block()
.output()?
.tempdir;
Expand Down
1 change: 1 addition & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use {
};

mod epochs;
mod find;
mod index;
mod info;
mod list;
Expand Down

0 comments on commit 45f5729

Please sign in to comment.