diff --git a/src/index.rs b/src/index.rs index 2248024f9b..ba55b2f9cd 100644 --- a/src/index.rs +++ b/src/index.rs @@ -258,17 +258,21 @@ impl Index { let rtx = self.database.begin_read()?; let outpoint_to_ordinal_ranges: ReadOnlyTable<[u8], [u8]> = rtx.open_table(Self::OUTPOINT_TO_ORDINAL_RANGES)?; + let mut key = Vec::new(); outpoint.consensus_encode(&mut key)?; + let ordinal_ranges = outpoint_to_ordinal_ranges .get(key.as_slice())? .ok_or("Could not find outpoint in index")?; + let mut output = Vec::new(); for chunk in ordinal_ranges.to_value().chunks_exact(16) { let start = u64::from_le_bytes(chunk[0..8].try_into().unwrap()); let end = u64::from_le_bytes(chunk[8..16].try_into().unwrap()); output.push((start, end)); } + Ok(output) } } diff --git a/tests/find.rs b/tests/find.rs index 75f63cb555..a643718794 100644 --- a/tests/find.rs +++ b/tests/find.rs @@ -5,6 +5,7 @@ fn first_satoshi() -> Result { Test::new()? .command("find --blocksdir blocks 0 --as-of-height 0") .expected_stdout("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0\n") + .block() .run() } @@ -13,6 +14,7 @@ fn first_satoshi_slot() -> Result { Test::new()? .command("find --blocksdir blocks 0 --as-of-height 0 --slot") .expected_stdout("0.0.0.0\n") + .block() .run() } @@ -21,6 +23,7 @@ fn second_satoshi() -> Result { Test::new()? .command("find --blocksdir blocks 1 --as-of-height 0") .expected_stdout("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:1\n") + .block() .run() } @@ -29,6 +32,7 @@ fn second_satoshi_slot() -> Result { Test::new()? .command("find --blocksdir blocks 1 --as-of-height 0 --slot") .expected_stdout("0.0.0.1\n") + .block() .run() } @@ -36,7 +40,9 @@ fn second_satoshi_slot() -> Result { fn first_satoshi_of_second_block() -> Result { Test::new()? .command("find --blocksdir blocks 5000000000 --as-of-height 1") - .expected_stdout("e5fb252959bdc7727c80296dbc53e1583121503bb2e266a609ebc49cf2a74c1d:0:0\n") + .expected_stdout("9068a11b8769174363376b606af9a4b8b29dd7b13d013f4b0cbbd457db3c3ce5:0:0\n") + .block() + .block() .run() } @@ -45,6 +51,8 @@ fn first_satoshi_of_second_block_slot() -> Result { Test::new()? .command("find --blocksdir blocks 5000000000 --as-of-height 1 --slot") .expected_stdout("1.0.0.0\n") + .block() + .block() .run() } @@ -52,7 +60,10 @@ fn first_satoshi_of_second_block_slot() -> Result { fn first_satoshi_spent_in_second_block() -> Result { Test::new()? .command("find --blocksdir blocks 0 --as-of-height 1") - .expected_stdout("1e8149c3be0dd66b1cbcb4652d15bea04a9bc8d515c4f544e71bb35a9cba1ed0:0:0\n") + .expected_stdout("72e60639a1dcc6263ed214a1db0dc9545bf65d9327e5a60e84bd3db7fbb4c2fa:0:0\n") + .block() + .block() + .transaction(&[(0, 0, 0)], 1) .run() } @@ -61,5 +72,8 @@ fn first_satoshi_spent_in_second_block_slot() -> Result { Test::new()? .command("find --blocksdir blocks 0 --as-of-height 1 --slot") .expected_stdout("1.1.0.0\n") + .block() + .block() + .transaction(&[(0, 0, 0)], 1) .run() } diff --git a/tests/integration.rs b/tests/integration.rs index b2e8aafa95..93b0f6c7c9 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,6 +1,6 @@ use { bitcoin::{ - blockdata::constants::{genesis_block, COIN_VALUE, MAX_SEQUENCE}, + blockdata::constants::{genesis_block, COIN_VALUE}, blockdata::script, consensus::Encodable, {Block, BlockHeader, Network, OutPoint, Transaction, TxIn, TxOut}, @@ -35,6 +35,7 @@ struct Test { expected_status: i32, ignore_stdout: bool, tempdir: TempDir, + blocks: Vec, } impl Test { @@ -46,6 +47,7 @@ impl Test { expected_status: 0, ignore_stdout: false, tempdir: TempDir::new()?, + blocks: Vec::new(), }) } @@ -124,63 +126,89 @@ impl Test { Ok(stdout.to_owned()) } - fn populate_blocksdir(&self) -> io::Result<()> { - let mut blocks = vec![genesis_block(Network::Bitcoin)]; - - blocks.push(Block { - header: BlockHeader { - version: 0, - prev_blockhash: blocks.last().unwrap().block_hash(), - merkle_root: Default::default(), - time: 0, - bits: 0, - nonce: 0, - }, - txdata: vec![ - Transaction { - version: 1, - lock_time: 0, - input: vec![TxIn { - previous_output: OutPoint::null(), - script_sig: script::Builder::new().push_scriptint(1).into_script(), - sequence: MAX_SEQUENCE, - witness: vec![], - }], - output: vec![TxOut { - value: 50 * COIN_VALUE, - script_pubkey: script::Builder::new().into_script(), - }], + fn block(mut self) -> Self { + if self.blocks.is_empty() { + self.blocks.push(genesis_block(Network::Bitcoin)); + } else { + self.blocks.push(Block { + header: BlockHeader { + version: 0, + prev_blockhash: self.blocks.last().unwrap().block_hash(), + merkle_root: Default::default(), + time: 0, + bits: 0, + nonce: 0, }, - Transaction { - version: 1, + txdata: vec![Transaction { + version: 0, lock_time: 0, input: vec![TxIn { - script_sig: script::Builder::new().into_script(), - sequence: MAX_SEQUENCE, + previous_output: OutPoint::null(), + script_sig: script::Builder::new() + .push_scriptint(self.blocks.len().try_into().unwrap()) + .into_script(), + sequence: 0, witness: vec![], - previous_output: OutPoint { - txid: blocks.last().unwrap().txdata[0].txid(), - vout: 0, - }, }], output: vec![TxOut { value: 50 * COIN_VALUE, script_pubkey: script::Builder::new().into_script(), }], - }, + }], + }); + } + self + } + + fn transaction(mut self, slots: &[(usize, usize, u32)], output_count: u64) -> Self { + let value = slots + .iter() + .map(|slot| self.blocks[slot.0].txdata[slot.1].output[slot.2 as usize].value) + .sum::(); + + let tx = Transaction { + version: 0, + lock_time: 0, + input: slots + .iter() + .map(|slot| TxIn { + previous_output: OutPoint { + txid: self.blocks[slot.0].txdata[slot.1].txid(), + vout: slot.2, + }, + script_sig: script::Builder::new().into_script(), + sequence: 0, + witness: vec![], + }) + .collect(), + output: vec![ + TxOut { + value: value / output_count, + script_pubkey: script::Builder::new().into_script(), + }; + output_count.try_into().unwrap() ], - }); + }; + + self.blocks.last_mut().unwrap().txdata.push(tx); + self + } + + fn populate_blocksdir(&self) -> io::Result<()> { let blocksdir = self.tempdir.path().join("blocks"); fs::create_dir(&blocksdir)?; let mut blockfile = File::create(blocksdir.join("blk00000.dat"))?; - for block in blocks { + for block in &self.blocks { let mut encoded = Vec::new(); block.consensus_encode(&mut encoded)?; blockfile.write_all(&[0xf9, 0xbe, 0xb4, 0xd9])?; blockfile.write_all(&(encoded.len() as u32).to_le_bytes())?; blockfile.write_all(&encoded)?; + for tx in &block.txdata { + eprintln!("{}", tx.txid()); + } } Ok(()) diff --git a/tests/list.rs b/tests/list.rs index ad69ed7657..a2284c89a2 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -6,6 +6,7 @@ fn first_coinbase_transaction() -> Result { .command( "list --blocksdir blocks 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0", ) + .block() .expected_stdout("[0,5000000000)\n") .run() } @@ -14,8 +15,61 @@ fn first_coinbase_transaction() -> Result { fn second_coinbase_transaction() -> Result { Test::new()? .command( - "list --blocksdir blocks e5fb252959bdc7727c80296dbc53e1583121503bb2e266a609ebc49cf2a74c1d:0", + "list --blocksdir blocks 9068a11b8769174363376b606af9a4b8b29dd7b13d013f4b0cbbd457db3c3ce5:0", ) + .block() + .block() .expected_stdout("[5000000000,10000000000)\n") .run() } + +#[test] +fn third_coinbase_transaction_is_not_duplicate() -> Result { + Test::new()? + .command( + "list --blocksdir blocks 8aa5103b13b5b233ac417ee31f21820c9284af2b7a2080a142c2d20e1697b0f4:0", + ) + .block() + .block() + .block() + .expected_stdout("[10000000000,15000000000)\n") + .run() +} + +#[test] +fn split_ranges_are_tracked_correctly() -> Result { + Test::new()? + .command( + "list --blocksdir blocks 7ab338c0e46c95c119ba8ff0a3f6cf64f56f35ba5553a399fdd38169cca00be3:0", + ) + .block() + .block() + .transaction(&[(0, 0, 0)], 2) + .expected_stdout("[0,2500000000)\n") + .run()?; + + Test::new()? + .command( + "list --blocksdir blocks 7ab338c0e46c95c119ba8ff0a3f6cf64f56f35ba5553a399fdd38169cca00be3:1", + ) + .block() + .block() + .transaction(&[(0, 0, 0)], 2) + .expected_stdout("[2500000000,5000000000)\n") + .run() +} + +#[test] +fn merge_ranges_are_tracked_correctly() -> Result { + Test::new()? + .command( + "list --blocksdir blocks fe283c08e46269a7bbe36b629bc2be55d604152419818e94330477c9c3487eec:0", + ) + .block() + .block() + .transaction(&[(0, 0, 0)], 2) + .block() + .transaction(&[(1, 1, 0), (1, 1, 1)], 1) + .expected_stdout("[0,2500000000)\n[2500000000,5000000000)\n") + .run() +}