Skip to content

Commit

Permalink
Add Index struct (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Jan 18, 2022
1 parent 2ea7028 commit 9e5cc10
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 132 deletions.
2 changes: 1 addition & 1 deletion src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl Arguments {
blocksdir,
ordinal,
height,
} => crate::find::run(blocksdir, ordinal, height),
} => crate::find::run(blocksdir.as_deref(), ordinal, height),
Self::Name { name } => crate::name::run(&name),
Self::Range { height, name } => crate::range::run(height, name),
Self::Supply => crate::supply::run(),
Expand Down
133 changes: 3 additions & 130 deletions src/find.rs
Original file line number Diff line number Diff line change
@@ -1,140 +1,13 @@
use super::*;

const CHILDREN: &str = "children";
const HEIGHTS: &str = "heights";
const BLOCK_OFFSETS: &str = "block_offsets";
const HEIGHTS_TO_HASHES: &str = "height_to_hashes";

pub(crate) fn run(blocksdir: Option<PathBuf>, ordinal: u64, at_height: u64) -> Result<()> {
let blocksdir = if let Some(blocksdir) = blocksdir {
blocksdir
} else if cfg!(target_os = "macos") {
dirs::home_dir()
.ok_or("Unable to retrieve home directory")?
.join("Library/Application Support/Bitcoin/blocks")
} else if cfg!(target_os = "windows") {
dirs::data_dir()
.ok_or("Unable to retrieve home directory")?
.join("Bitcoin/blocks")
} else {
dirs::home_dir()
.ok_or("Unable to retrieve home directory")?
.join(".bitcoin/blocks")
};

let tempdir = tempfile::tempdir()?;
let blockfile = blocksdir.join("blk00000.dat");

let db = unsafe {
Database::open(tempdir.path().join("bitcoin.redb"), 4096 * 1024 * 1024 * 10).unwrap()
};

{
let tx = db.begin_write()?;

let mut children: MultimapTable<[u8], [u8]> = tx.open_multimap_table(CHILDREN)?;

let mut block_offsets: Table<[u8], u64> = tx.open_table(BLOCK_OFFSETS)?;

let blocks = fs::read(&blockfile)?;

let mut i = 0;

let mut count = 0;

loop {
if i == blocks.len() {
break;
}

let offset = i;

assert_eq!(&blocks[i..i + 4], &[0xf9, 0xbe, 0xb4, 0xd9]);
i += 4;

let len = u32::from_le_bytes(blocks[i..i + 4].try_into()?) as usize;
i += 4;

let bytes = &blocks[i..i + len];
i += len;

let block = Block::consensus_decode(bytes)?;

children.insert(&block.header.prev_blockhash, &block.block_hash())?;

block_offsets.insert(&block.block_hash(), &(offset as u64))?;

count += 1;
}

log::info!("Inserted {} blocks…", count);

tx.commit()?;
}

{
let write = db.begin_write()?;

let mut heights: Table<[u8], u64> = write.open_table(HEIGHTS)?;
let mut heights_to_hashes: Table<u64, [u8]> = write.open_table(HEIGHTS_TO_HASHES)?;

heights.insert(genesis_block(Network::Bitcoin).block_hash().deref(), &0)?;
heights_to_hashes.insert(&0, genesis_block(Network::Bitcoin).block_hash().deref())?;

let read = db.begin_read()?;

let children: ReadOnlyMultimapTable<[u8], [u8]> = read.open_multimap_table(CHILDREN)?;

let mut queue = vec![(
genesis_block(Network::Bitcoin)
.block_hash()
.deref()
.to_vec(),
0,
)];

while let Some((block, height)) = queue.pop() {
heights.insert(block.as_ref(), &height)?;
heights_to_hashes.insert(&height, block.as_ref())?;

let mut iter = children.get(&block)?;

while let Some(child) = iter.next() {
queue.push((child.to_vec(), height + 1));
}
}

write.commit()?;
}
pub(crate) fn run(blocksdir: Option<&Path>, ordinal: u64, at_height: u64) -> Result<()> {
let index = Index::new(blocksdir)?;

let height = ordinal / (50 * 100_000_000);
assert!(height < 100);
assert!(at_height == height);

let tx = db.begin_read()?;

let heights_to_hashes: ReadOnlyTable<u64, [u8]> = tx.open_table(HEIGHTS_TO_HASHES)?;
let guard = heights_to_hashes.get(&height)?.unwrap();
let hash = guard.to_value();

let offsets: ReadOnlyTable<[u8], u64> = tx.open_table(BLOCK_OFFSETS)?;
let mut i = offsets.get(hash)?.unwrap().to_value() as usize;

if i == 1 {
i = 0;
}

let blocks = fs::read(&blockfile)?;

assert_eq!(&blocks[i..i + 4], &[0xf9, 0xbe, 0xb4, 0xd9]);
i += 4;

let len = u32::from_le_bytes(blocks[i..i + 4].try_into()?) as usize;
i += 4;

let bytes = &blocks[i..i + len];

let block = Block::consensus_decode(bytes)?;
let block = index.block(height)?;

let position = ordinal % (50 * 100_000_000);

Expand Down
146 changes: 146 additions & 0 deletions src/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use super::*;

const CHILDREN: &str = "children";
const HEIGHTS: &str = "heights";
const BLOCK_OFFSETS: &str = "block_offsets";
const HEIGHTS_TO_HASHES: &str = "height_to_hashes";

pub(crate) struct Index {
blocksdir: PathBuf,
database: Database,
}

impl Index {
pub(crate) fn new(blocksdir: Option<&Path>) -> Result<Self> {
let blocksdir = if let Some(blocksdir) = blocksdir {
blocksdir.to_owned()
} else if cfg!(target_os = "macos") {
dirs::home_dir()
.ok_or("Unable to retrieve home directory")?
.join("Library/Application Support/Bitcoin/blocks")
} else if cfg!(target_os = "windows") {
dirs::data_dir()
.ok_or("Unable to retrieve home directory")?
.join("Bitcoin/blocks")
} else {
dirs::home_dir()
.ok_or("Unable to retrieve home directory")?
.join(".bitcoin/blocks")
};

let index = Self {
database: unsafe { Database::open("bitcoin.redb", 4096 * 1024 * 1024 * 10)? },
blocksdir,
};

index.index_blockfile()?;

Ok(index)
}

fn index_blockfile(&self) -> Result {
{
let tx = self.database.begin_write()?;

let mut children: MultimapTable<[u8], [u8]> = tx.open_multimap_table(CHILDREN)?;

let mut block_offsets: Table<[u8], u64> = tx.open_table(BLOCK_OFFSETS)?;

let blocks = fs::read(self.blocksdir.join("blk00000.dat"))?;

let mut offset = 0;

let mut count = 0;

loop {
if offset == blocks.len() {
break;
}

let range = Self::block_range_at(&blocks, offset)?;

let block = Block::consensus_decode(&blocks[range.clone()])?;

children.insert(&block.header.prev_blockhash, &block.block_hash())?;

block_offsets.insert(&block.block_hash(), &(offset as u64))?;

offset = range.end;

count += 1;
}

log::info!("Inserted {} blocks…", count);

tx.commit()?;
}

{
let write = self.database.begin_write()?;

let mut heights: Table<[u8], u64> = write.open_table(HEIGHTS)?;
let mut heights_to_hashes: Table<u64, [u8]> = write.open_table(HEIGHTS_TO_HASHES)?;

heights.insert(genesis_block(Network::Bitcoin).block_hash().deref(), &0)?;
heights_to_hashes.insert(&0, genesis_block(Network::Bitcoin).block_hash().deref())?;

let read = self.database.begin_read()?;

let children: ReadOnlyMultimapTable<[u8], [u8]> = read.open_multimap_table(CHILDREN)?;

let mut queue = vec![(
genesis_block(Network::Bitcoin)
.block_hash()
.deref()
.to_vec(),
0,
)];

while let Some((block, height)) = queue.pop() {
heights.insert(block.as_ref(), &height)?;
heights_to_hashes.insert(&height, block.as_ref())?;

let mut iter = children.get(&block)?;

while let Some(child) = iter.next() {
queue.push((child.to_vec(), height + 1));
}
}

write.commit()?;
}

Ok(())
}

pub(crate) fn block(&self, height: u64) -> Result<Block> {
let tx = self.database.begin_read()?;

let heights_to_hashes: ReadOnlyTable<u64, [u8]> = tx.open_table(HEIGHTS_TO_HASHES)?;
let guard = heights_to_hashes.get(&height)?.unwrap();
let hash = guard.to_value();

let offsets: ReadOnlyTable<[u8], u64> = tx.open_table(BLOCK_OFFSETS)?;
let offset = offsets.get(hash)?.unwrap().to_value() as usize;

let blocks = fs::read(self.blocksdir.join("blk00000.dat"))?;

Self::decode_block_at(&blocks, offset)
}

fn block_range_at(blocks: &[u8], offset: usize) -> Result<Range<usize>> {
assert_eq!(&blocks[offset..offset + 4], &[0xf9, 0xbe, 0xb4, 0xd9]);
let offset = offset + 4;

let len = u32::from_le_bytes(blocks[offset..offset + 4].try_into()?) as usize;
let offset = offset + 4;

Ok(offset..offset + len)
}

fn decode_block_at(blocks: &[u8], offset: usize) -> Result<Block> {
Ok(Block::consensus_decode(
&blocks[Self::block_range_at(blocks, offset)?],
)?)
}
}
11 changes: 10 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use {
crate::index::Index,
arguments::Arguments,
bitcoin::{
blockdata::constants::{genesis_block, COIN_VALUE},
Expand All @@ -11,12 +12,20 @@ use {
Database, MultimapTable, ReadOnlyMultimapTable, ReadOnlyTable, ReadableMultimapTable,
ReadableTable, Table,
},
std::{cmp::Ordering, fs, ops::Deref, path::PathBuf, process},
std::{
cmp::Ordering,
fs,
ops::Deref,
ops::Range,
path::{Path, PathBuf},
process,
},
structopt::StructOpt,
};

mod arguments;
mod find;
mod index;
mod name;
mod range;
mod supply;
Expand Down

0 comments on commit 9e5cc10

Please sign in to comment.