Skip to content

Commit

Permalink
Use index for find queries (#149)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Feb 21, 2022
1 parent 22dcde3 commit a5c26bc
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 132 deletions.
189 changes: 121 additions & 68 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ impl Index {
TableDefinition::new("HEIGHT_TO_HASH");
const OUTPOINT_TO_ORDINAL_RANGES: TableDefinition<'static, [u8], [u8]> =
TableDefinition::new("OUTPOINT_TO_ORDINAL_RANGES");
const KEY_TO_SATPOINT: TableDefinition<'static, [u8], [u8]> =
TableDefinition::new("KEY_TO_SATPOINT");

pub(crate) fn open(options: Options) -> Result<Self> {
let client = Client::new(
Expand Down Expand Up @@ -128,6 +130,7 @@ impl Index {
}

let mut outpoint_to_ordinal_ranges = wtx.open_table(&Self::OUTPOINT_TO_ORDINAL_RANGES)?;
let mut key_to_satpoint = wtx.open_table(&Self::KEY_TO_SATPOINT)?;

let mut coinbase_inputs = VecDeque::new();

Expand All @@ -137,7 +140,7 @@ impl Index {
coinbase_inputs.push_front((start.n(), (start + h.subsidy()).n()));
}

for tx in block.txdata.iter().skip(1) {
for (tx_offset, tx) in block.txdata.iter().enumerate().skip(1) {
let mut input_ordinal_ranges = VecDeque::new();

for input in &tx.input {
Expand All @@ -155,85 +158,94 @@ impl Index {
}
}

for (vout, output) in tx.output.iter().enumerate() {
let mut ordinals = Vec::new();
self.index_transaction(
height,
tx_offset as u64,
tx,
&mut input_ordinal_ranges,
&mut outpoint_to_ordinal_ranges,
&mut key_to_satpoint,
)?;

let mut remaining = output.value;
while remaining > 0 {
let range = input_ordinal_ranges
.pop_front()
.ok_or("Found transaction with outputs but no inputs")?;

let count = range.1 - range.0;

let assigned = if count > remaining {
let middle = range.0 + remaining;
input_ordinal_ranges.push_front((middle, range.1));
(range.0, middle)
} else {
range
};

ordinals.extend_from_slice(&assigned.0.to_le_bytes());
ordinals.extend_from_slice(&assigned.1.to_le_bytes());

remaining -= assigned.1 - assigned.0;
}

let outpoint = OutPoint {
txid: tx.txid(),
vout: vout as u32,
};

let mut key = Vec::new();
outpoint.consensus_encode(&mut key)?;

outpoint_to_ordinal_ranges.insert(&key, &ordinals)?;
}

coinbase_inputs.extend(&input_ordinal_ranges);
coinbase_inputs.extend(input_ordinal_ranges);
}

if let Some(tx) = block.txdata.first() {
for (vout, output) in tx.output.iter().enumerate() {
let mut ordinals = Vec::new();

let mut remaining = output.value;
while remaining > 0 {
let range = coinbase_inputs
.pop_front()
.ok_or("Insufficient inputs for coinbase transaction outputs")?;

let count = range.1 - range.0;
self.index_transaction(
height,
0,
tx,
&mut coinbase_inputs,
&mut outpoint_to_ordinal_ranges,
&mut key_to_satpoint,
)?;
}

let assigned = if count > remaining {
let middle = range.0 + remaining;
coinbase_inputs.push_front((middle, range.1));
(range.0, middle)
} else {
range
};
height_to_hash.insert(&height, &block.block_hash())?;
wtx.commit()?;
}

ordinals.extend_from_slice(&assigned.0.to_le_bytes());
ordinals.extend_from_slice(&assigned.1.to_le_bytes());
Ok(())
}

remaining -= assigned.1 - assigned.0;
fn index_transaction(
&self,
block: u64,
tx_offset: u64,
tx: &Transaction,
input_ordinal_ranges: &mut VecDeque<(u64, u64)>,
outpoint_to_ordinal_ranges: &mut Table<[u8], [u8]>,
key_to_satpoint: &mut Table<[u8], [u8]>,
) -> Result {
for (vout, output) in tx.output.iter().enumerate() {
let outpoint = OutPoint {
txid: tx.txid(),
vout: vout as u32,
};
let mut outpoint_encoded = Vec::new();
outpoint.consensus_encode(&mut outpoint_encoded)?;

let mut ordinals = Vec::new();

let mut remaining = output.value;
while remaining > 0 {
let range = input_ordinal_ranges
.pop_front()
.ok_or("Insufficient inputs for transaction outputs")?;

let count = range.1 - range.0;

let assigned = if count > remaining {
let middle = range.0 + remaining;
input_ordinal_ranges.push_front((middle, range.1));
(range.0, middle)
} else {
range
};

let mut satpoint = Vec::new();
SatPoint {
offset: output.value - remaining,
outpoint,
}
.consensus_encode(&mut satpoint)?;
key_to_satpoint.insert(
&Key {
ordinal: assigned.0,
block,
transaction: tx_offset,
}
.encode(),
&satpoint,
)?;

let outpoint = OutPoint {
txid: tx.txid(),
vout: vout as u32,
};

let mut key = Vec::new();
outpoint.consensus_encode(&mut key)?;
ordinals.extend_from_slice(&assigned.0.to_le_bytes());
ordinals.extend_from_slice(&assigned.1.to_le_bytes());

outpoint_to_ordinal_ranges.insert(&key, &ordinals)?;
}
remaining -= assigned.1 - assigned.0;
}

height_to_hash.insert(&height, &block.block_hash())?;
wtx.commit()?;
outpoint_to_ordinal_ranges.insert(&outpoint_encoded, &ordinals)?;
}

Ok(())
Expand All @@ -249,6 +261,47 @@ impl Index {
}
}

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

let height_to_hash = match rtx.open_table(&Self::HEIGHT_TO_HASH) {
Ok(height_to_hash) => height_to_hash,
Err(redb::Error::TableDoesNotExist(_)) => return Ok(None),
Err(err) => return Err(err.into()),
};

if let Some((height, _hash)) = height_to_hash.range_reversed(0..)?.next() {
if height < ordinal.height().0 {
return Ok(None);
}
}

let key_to_satpoint = match rtx.open_table(&Self::KEY_TO_SATPOINT) {
Ok(key_to_satpoint) => key_to_satpoint,
Err(redb::Error::TableDoesNotExist(_)) => return Ok(None),
Err(err) => return Err(err.into()),
};

match key_to_satpoint
.range_reversed([].as_slice()..=Key::new(ordinal).encode().as_slice())?
.next()
{
Some((start_key, start_satpoint)) => {
let start_key = Key::decode(start_key)?;
let start_satpoint = SatPoint::consensus_decode(start_satpoint)?;
Ok(Some((
start_key.block,
start_key.transaction,
SatPoint {
offset: start_satpoint.offset + (ordinal.0 - start_key.ordinal),
outpoint: start_satpoint.outpoint,
},
)))
}
None => Ok(None),
}
}

pub(crate) fn list(&self, outpoint: OutPoint) -> Result<Vec<(u64, u64)>> {
let rtx = self.database.begin_read()?;
let outpoint_to_ordinal_ranges = rtx.open_table(&Self::OUTPOINT_TO_ORDINAL_RANGES)?;
Expand Down
50 changes: 50 additions & 0 deletions src/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use super::*;

pub(crate) struct Key {
pub(crate) ordinal: u64,
pub(crate) block: u64,
pub(crate) transaction: u64,
}

impl Key {
pub(crate) fn new(ordinal: Ordinal) -> Key {
Self {
ordinal: ordinal.0,
block: u64::max_value(),
transaction: u64::max_value(),
}
}

pub(crate) fn encode(self) -> Vec<u8> {
let mut buffer = Vec::new();
buffer.extend(self.ordinal.to_be_bytes());
buffer.extend(self.block.to_be_bytes());
buffer.extend(self.transaction.to_be_bytes());
buffer
}

pub(crate) fn decode(buffer: &[u8]) -> Result<Self> {
if buffer.len() != 24 {
return Err("Buffer too small to decode key from".into());
}

Ok(Key {
ordinal: u64::from_be_bytes(buffer[0..8].try_into().unwrap()),
block: u64::from_be_bytes(buffer[8..16].try_into().unwrap()),
transaction: u64::from_be_bytes(buffer[16..24].try_into().unwrap()),
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn decode_error() {
assert_eq!(
Key::decode(&[]).err().unwrap().to_string(),
"Buffer too small to decode key from"
);
}
}
10 changes: 7 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use {
crate::{
arguments::Arguments, bytes::Bytes, epoch::Epoch, height::Height, index::Index,
arguments::Arguments, bytes::Bytes, epoch::Epoch, height::Height, index::Index, key::Key,
options::Options, ordinal::Ordinal, sat_point::SatPoint, subcommand::Subcommand,
},
bitcoin::{blockdata::constants::COIN_VALUE, consensus::Encodable, Block, OutPoint, Transaction},
bitcoin::{
blockdata::constants::COIN_VALUE, consensus::Decodable, consensus::Encodable, Block, OutPoint,
Transaction,
},
clap::Parser,
derive_more::{Display, FromStr},
integer_cbrt::IntegerCubeRoot,
integer_sqrt::IntegerSquareRoot,
redb::{Database, ReadableTable, TableDefinition},
redb::{Database, ReadableTable, Table, TableDefinition},
std::{
cell::Cell,
cmp::Ordering,
Expand All @@ -28,6 +31,7 @@ mod bytes;
mod epoch;
mod height;
mod index;
mod key;
mod options;
mod ordinal;
mod sat_point;
Expand Down
33 changes: 15 additions & 18 deletions src/sat_point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,24 @@ pub(crate) struct SatPoint {
pub(crate) offset: u64,
}

impl SatPoint {
pub(crate) fn from_transaction_and_offset(tx: &Transaction, mut offset: u64) -> SatPoint {
for (vout, output) in tx.output.iter().enumerate() {
if output.value > offset {
return SatPoint {
outpoint: OutPoint {
txid: tx.txid(),
vout: vout.try_into().unwrap(),
},
offset,
};
}
offset -= output.value;
}
impl Display for SatPoint {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}:{}", self.outpoint, self.offset)
}
}

panic!("Could not find ordinal in transaction!");
impl Encodable for SatPoint {
fn consensus_encode<S: io::Write>(&self, mut s: S) -> Result<usize, io::Error> {
let len = self.outpoint.consensus_encode(&mut s)?;
Ok(len + self.offset.consensus_encode(s)?)
}
}

impl Display for SatPoint {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}:{}", self.outpoint, self.offset)
impl Decodable for SatPoint {
fn consensus_decode<D: io::Read>(mut d: D) -> Result<Self, bitcoin::consensus::encode::Error> {
Ok(SatPoint {
outpoint: Decodable::consensus_decode(&mut d)?,
offset: Decodable::consensus_decode(d)?,
})
}
}
Loading

0 comments on commit a5c26bc

Please sign in to comment.