forked from ordinals/ord
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add readme and refactor code (ordinals#22)
- Loading branch information
Showing
9 changed files
with
371 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,64 @@ | ||
# Sat Tracker | ||
|
||
A scheme for numbering satoshis and and tracking them across transactions. | ||
A scheme for assigning serial numbers to satoshis upon creation and tracking | ||
them across subsequent transactions. | ||
|
||
Satoshi serial numbers can be used as an addressing scheme for NFTs. | ||
|
||
|
||
## Numbering | ||
|
||
Satoshis are numbered in the order in which they are mined. | ||
|
||
Satoshi numbers only depend on how many satoshis could have been created in | ||
previous blocks, not how many were *actually* created. | ||
|
||
In particular, this means that block 124724, which underpaid the block subsidy | ||
by one, does not reduce the serial numbers of satoshis in subsequent blocks by | ||
one. | ||
|
||
The `range` command gives the half-open range of satoshis mined in the block at | ||
a given height: | ||
|
||
``` | ||
$ sat-tracker range 0 | ||
0 50000000000 | ||
``` | ||
|
||
See [src/range.rs](src/range.rs) for the numbering algorithm. | ||
|
||
|
||
## Transfer | ||
|
||
Satoshis input to a transaction are transferred to the transaction outputs | ||
according to the order and value of the inputs and outputs. Satoshis paid as | ||
fees are assigned in the same fashion to the outputs of the coinbase | ||
transaction. | ||
|
||
```rust | ||
fn transfer(transaction: Transaction) { | ||
let mut numbers = Vec::new(); | ||
|
||
for input in transaction.inputs { | ||
for number in input.numbers { | ||
numbers.push(number); | ||
} | ||
} | ||
|
||
for output in transaction.outputs { | ||
let rest = numbers.split_off(output.value); | ||
output.numbers = numbers; | ||
numbers = rest; | ||
} | ||
|
||
coinbase.input.numbers.extend_from_slice(&numbers); | ||
} | ||
``` | ||
|
||
The `find` command, unfinished, gives the current outpoint containing a given | ||
satoshi as of a given height: | ||
|
||
``` | ||
$ sat-tracker find --blocksdir ~/.bicoin/blocks 0 0 | ||
4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,156 +1,27 @@ | ||
use super::*; | ||
use std::path::PathBuf; | ||
|
||
const CHILDREN: &str = "children"; | ||
const HEIGHTS: &str = "heights"; | ||
const BLOCK_OFFSETS: &str = "block_offsets"; | ||
const HEIGHTS_TO_HASHES: &str = "height_to_hashes"; | ||
|
||
#[derive(StructOpt)] | ||
pub enum Arguments { | ||
FindSatoshi { | ||
Find { | ||
#[structopt(long)] | ||
blocksdir: PathBuf, | ||
n: u64, | ||
at_height: u64, | ||
height: u64, | ||
}, | ||
Range { | ||
height: u64, | ||
}, | ||
} | ||
|
||
impl Arguments { | ||
pub fn run(self) -> Result<()> { | ||
match self { | ||
Self::FindSatoshi { | ||
Self::Find { | ||
blocksdir, | ||
n, | ||
at_height, | ||
} => { | ||
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; | ||
} | ||
|
||
eprintln!("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()?; | ||
} | ||
|
||
let height = n / (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 position = n % (50 * 100_000_000); | ||
|
||
let mut n = 0; | ||
for (i, output) in block.txdata[0].output.iter().enumerate() { | ||
if n + output.value >= position { | ||
println!("{}:{}", block.txdata[0].txid(), i); | ||
break; | ||
} | ||
n += output.value; | ||
} | ||
|
||
Ok(()) | ||
} | ||
height, | ||
} => crate::find::run(&blocksdir, n, height), | ||
Self::Range { height } => crate::range::run(height), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
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: &Path, n: u64, at_height: u64) -> Result<()> { | ||
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; | ||
} | ||
|
||
eprintln!("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()?; | ||
} | ||
|
||
let height = n / (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 position = n % (50 * 100_000_000); | ||
|
||
let mut n = 0; | ||
for (i, output) in block.txdata[0].output.iter().enumerate() { | ||
if n + output.value >= position { | ||
println!("{}:{}", block.txdata[0].txid(), i); | ||
break; | ||
} | ||
n += output.value; | ||
} | ||
|
||
Ok(()) | ||
} |
Oops, something went wrong.