diff --git a/Cargo.lock b/Cargo.lock index 4b9befde68..fb24737494 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2949,11 +2949,13 @@ name = "test-bitcoincore-rpc" version = "0.0.0" dependencies = [ "bitcoin", + "bitcoincore-rpc-json", "hex", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-http-server", + "serde_json", ] [[package]] diff --git a/justfile b/justfile index d68b5fc82a..a958923009 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,6 @@ ci: clippy forbid cargo fmt -- --check - cargo test -- --test-threads=1 + cargo test --all forbid: ./bin/forbid diff --git a/src/index.rs b/src/index.rs index 59c0795ce7..9da2316e59 100644 --- a/src/index.rs +++ b/src/index.rs @@ -13,8 +13,6 @@ mod rtx; const HEIGHT_TO_HASH: TableDefinition = TableDefinition::new("HEIGHT_TO_HASH"); const OUTPOINT_TO_ORDINAL_RANGES: TableDefinition<[u8; 36], [u8]> = TableDefinition::new("OUTPOINT_TO_ORDINAL_RANGES"); -const OUTPOINT_TO_TXID: TableDefinition<[u8; 36], [u8; 32]> = - TableDefinition::new("OUTPOINT_TO_TXID"); const STATISTICS: TableDefinition = TableDefinition::new("STATISTICS"); pub(crate) struct Index { @@ -27,7 +25,7 @@ pub(crate) struct Index { #[derive(Debug, PartialEq)] pub(crate) enum List { - Spent(Txid), + Spent, Unspent(Vec<(u64, u64)>), } @@ -101,7 +99,6 @@ impl Index { tx.open_table(HEIGHT_TO_HASH)?; tx.open_table(OUTPOINT_TO_ORDINAL_RANGES)?; - tx.open_table(OUTPOINT_TO_TXID)?; tx.open_table(STATISTICS)?; tx.commit()?; @@ -209,7 +206,6 @@ impl Index { pub(crate) fn index_block(&self, wtx: &mut WriteTransaction, height: u64) -> Result { let mut height_to_hash = wtx.open_table(HEIGHT_TO_HASH)?; let mut outpoint_to_ordinal_ranges = wtx.open_table(OUTPOINT_TO_ORDINAL_RANGES)?; - let mut outpoint_to_txid = wtx.open_table(OUTPOINT_TO_TXID)?; let mut statistics = wtx.open_table(STATISTICS)?; let start = Instant::now(); @@ -299,7 +295,6 @@ impl Index { *txid, tx, &mut outpoint_to_ordinal_ranges, - &mut outpoint_to_txid, &mut input_ordinal_ranges, &mut ordinal_ranges_written, &mut outputs_in_block, @@ -313,7 +308,6 @@ impl Index { *txid, tx, &mut outpoint_to_ordinal_ranges, - &mut outpoint_to_txid, &mut coinbase_inputs, &mut ordinal_ranges_written, &mut outputs_in_block, @@ -371,7 +365,6 @@ impl Index { txid: Txid, tx: &Transaction, outpoint_to_ordinal_ranges: &mut Table<[u8; 36], [u8]>, - #[allow(unused)] outpoint_to_txid: &mut Table<[u8; 36], [u8; 32]>, input_ordinal_ranges: &mut VecDeque<(u64, u64)>, ordinal_ranges_written: &mut u64, outputs_traversed: &mut u64, @@ -416,11 +409,6 @@ impl Index { outpoint_to_ordinal_ranges.insert(&serialize(&outpoint).try_into().unwrap(), &ordinals)?; } - #[cfg(any())] - for input in &tx.input { - outpoint_to_txid.insert(&serialize(&input.previous_output), &txid)?; - } - Ok(()) } @@ -447,6 +435,21 @@ impl Index { self.client.get_raw_transaction(&txid, None).into_option() } + pub(crate) fn is_transaction_in_active_chain(&self, txid: Txid) -> Result { + Ok( + self + .client + .get_raw_transaction_info(&txid, None) + .into_option()? + .and_then(|transaction_info| { + transaction_info + .confirmations + .map(|confirmations| confirmations > 0) + }) + .unwrap_or(false), + ) + } + pub(crate) fn find(&self, ordinal: u64) -> Result> { if self.height()? < Ordinal(ordinal).height() { return Ok(None); @@ -499,16 +502,13 @@ impl Index { .map(|chunk| Self::decode_ordinal_range(chunk.try_into().unwrap())) .collect(), ))), - None => Ok( - self - .database - .begin_read()? - .open_table(OUTPOINT_TO_TXID)? - .get(&outpoint_encoded.try_into().unwrap())? - .map(|txid| deserialize(txid.as_slice())) - .transpose()? - .map(List::Spent), - ), + None => { + if self.is_transaction_in_active_chain(outpoint.txid)? { + Ok(Some(List::Spent)) + } else { + Ok(None) + } + } } } @@ -640,7 +640,7 @@ mod tests { let context = Context::new(); context.rpc_server.mine_blocks(1); - let split_coinbase_output = test_bitcoincore_rpc::TransactionTemplate { + let split_coinbase_output = TransactionTemplate { input_slots: &[(1, 0, 0)], output_count: 2, fee: 0, @@ -666,7 +666,7 @@ mod tests { let context = Context::new(); context.rpc_server.mine_blocks(2); - let merge_coinbase_outputs = test_bitcoincore_rpc::TransactionTemplate { + let merge_coinbase_outputs = TransactionTemplate { input_slots: &[(1, 0, 0), (2, 0, 0)], output_count: 1, fee: 0, @@ -690,7 +690,7 @@ mod tests { let context = Context::new(); context.rpc_server.mine_blocks(1); - let fee_paying_tx = test_bitcoincore_rpc::TransactionTemplate { + let fee_paying_tx = TransactionTemplate { input_slots: &[(1, 0, 0)], output_count: 2, fee: 10, @@ -724,12 +724,12 @@ mod tests { let context = Context::new(); context.rpc_server.mine_blocks(2); - let first_fee_paying_tx = test_bitcoincore_rpc::TransactionTemplate { + let first_fee_paying_tx = TransactionTemplate { input_slots: &[(1, 0, 0)], output_count: 1, fee: 10, }; - let second_fee_paying_tx = test_bitcoincore_rpc::TransactionTemplate { + let second_fee_paying_tx = TransactionTemplate { input_slots: &[(2, 0, 0)], output_count: 1, fee: 10, @@ -759,7 +759,7 @@ mod tests { let context = Context::new(); context.rpc_server.mine_blocks(1); - let no_value_output = test_bitcoincore_rpc::TransactionTemplate { + let no_value_output = TransactionTemplate { input_slots: &[(1, 0, 0)], output_count: 1, fee: 50 * COIN_VALUE, @@ -779,7 +779,7 @@ mod tests { let context = Context::new(); context.rpc_server.mine_blocks(1); - let no_value_output = test_bitcoincore_rpc::TransactionTemplate { + let no_value_output = TransactionTemplate { input_slots: &[(1, 0, 0)], output_count: 1, fee: 50 * COIN_VALUE, @@ -787,7 +787,7 @@ mod tests { context.rpc_server.broadcast_tx(no_value_output); context.rpc_server.mine_blocks(1); - let no_value_input = test_bitcoincore_rpc::TransactionTemplate { + let no_value_input = TransactionTemplate { input_slots: &[(2, 1, 0)], output_count: 1, fee: 0, @@ -802,6 +802,24 @@ mod tests { ); } + #[test] + fn list_spent_output() { + let context = Context::new(); + context.rpc_server.mine_blocks(1); + context.rpc_server.broadcast_tx(TransactionTemplate { + input_slots: &[(1, 0, 0)], + output_count: 1, + fee: 0, + }); + context.rpc_server.mine_blocks(1); + context.index.index().unwrap(); + let txid = context.rpc_server.tx(1, 0).txid(); + assert_eq!( + context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(), + List::Spent, + ); + } + #[test] fn list_unknown_output() { let context = Context::new(); @@ -873,13 +891,11 @@ mod tests { fn find_first_satoshi_spent_in_second_block() { let context = Context::new(); context.rpc_server.mine_blocks(1); - let spend_txid = context - .rpc_server - .broadcast_tx(test_bitcoincore_rpc::TransactionTemplate { - input_slots: &[(1, 0, 0)], - output_count: 1, - fee: 0, - }); + let spend_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + input_slots: &[(1, 0, 0)], + output_count: 1, + fee: 0, + }); context.rpc_server.mine_blocks(1); context.index.index().unwrap(); assert_eq!( diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index 4fc580f095..73840abdac 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -18,7 +18,7 @@ impl List { } Ok(()) } - Some(crate::index::List::Spent(txid)) => Err(anyhow!("Output spent in transaction {txid}")), + Some(crate::index::List::Spent) => Err(anyhow!("Output spent.")), None => Err(anyhow!("Output not found")), } } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 25535f1a42..ac376f4453 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1096,7 +1096,7 @@ mod tests { let test_server = TestServer::new(); test_server.bitcoin_rpc_server.mine_blocks(1); - let transaction = test_bitcoincore_rpc::TransactionTemplate { + let transaction = TransactionTemplate { input_slots: &[(1, 0, 0)], output_count: 1, fee: 0, diff --git a/src/subcommand/server/templates/output.rs b/src/subcommand/server/templates/output.rs index 474e7ea632..592bb5d303 100644 --- a/src/subcommand/server/templates/output.rs +++ b/src/subcommand/server/templates/output.rs @@ -45,12 +45,12 @@ mod tests { outpoint: "0000000000000000000000000000000000000000000000000000000000000000:0" .parse() .unwrap(), - list: List::Spent("1111111111111111111111111111111111111111111111111111111111111111".parse().unwrap()) + list: List::Spent, } .to_string(), "

Output 0000000000000000000000000000000000000000000000000000000000000000:0

-

Spent by transaction 1111111111111111111111111111111111111111111111111111111111111111.

+

Output has been spent.

" .unindent() ); diff --git a/src/test.rs b/src/test.rs index d0bcb02b39..d3b7061cbc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,4 +1,4 @@ -pub(crate) use tempfile::TempDir; +pub(crate) use {tempfile::TempDir, test_bitcoincore_rpc::TransactionTemplate}; macro_rules! assert_regex_match { ($string:expr, $pattern:expr $(,)?) => { diff --git a/templates/output.html b/templates/output.html index 6de52b783f..e4e8576e3b 100644 --- a/templates/output.html +++ b/templates/output.html @@ -8,7 +8,7 @@

Ordinal Ranges

%% } %% } -%% List::Spent(txid) => { -

Spent by transaction {{ txid }}.

+%% List::Spent => { +

Output has been spent.

%% } %% } diff --git a/test-bitcoincore-rpc/Cargo.toml b/test-bitcoincore-rpc/Cargo.toml index 798d8ff119..832c5d1802 100644 --- a/test-bitcoincore-rpc/Cargo.toml +++ b/test-bitcoincore-rpc/Cargo.toml @@ -9,8 +9,10 @@ repository = "https://github.com/casey/ord" [dependencies] bitcoin = { version = "0.29.1", features = ["serde"] } +bitcoincore-rpc-json = "0.16.0" hex = "0.4.3" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" jsonrpc-http-server = "18.0.0" +serde_json = "1.0.0" diff --git a/test-bitcoincore-rpc/src/lib.rs b/test-bitcoincore-rpc/src/lib.rs index c3121c0ad2..d27e22ff51 100644 --- a/test-bitcoincore-rpc/src/lib.rs +++ b/test-bitcoincore-rpc/src/lib.rs @@ -2,9 +2,10 @@ use { bitcoin::{ blockdata::constants::COIN_VALUE, blockdata::script, consensus::encode::serialize, hash_types::BlockHash, hashes::Hash, Block, BlockHeader, Network, OutPoint, PackedLockTime, - Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, + Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, Wtxid, }, - jsonrpc_core::IoHandler, + bitcoincore_rpc_json::GetRawTransactionResult, + jsonrpc_core::{IoHandler, Value}, jsonrpc_http_server::{CloseHandle, ServerBuilder}, std::collections::BTreeMap, std::{ @@ -203,7 +204,7 @@ pub trait Api { txid: Txid, verbose: bool, blockhash: Option, - ) -> Result; + ) -> Result; } impl Api for Server { @@ -245,14 +246,40 @@ impl Api for Server { txid: Txid, verbose: bool, blockhash: Option, - ) -> Result { - assert!(!verbose, "Verbose param is unsupported"); + ) -> Result { assert_eq!(blockhash, None, "Blockhash param is unsupported"); - match self.state.lock().unwrap().transactions.get(&txid) { - Some(tx) => Ok(hex::encode(serialize(tx))), - None => Err(jsonrpc_core::Error::new( - jsonrpc_core::types::error::ErrorCode::ServerError(-8), - )), + if verbose { + match self.state.lock().unwrap().transactions.get(&txid) { + Some(_) => Ok( + serde_json::to_value(GetRawTransactionResult { + in_active_chain: None, + hex: Vec::new(), + txid: Txid::all_zeros(), + hash: Wtxid::all_zeros(), + size: 0, + vsize: 0, + version: 0, + locktime: 0, + vin: Vec::new(), + vout: Vec::new(), + blockhash: None, + confirmations: Some(1), + time: None, + blocktime: None, + }) + .unwrap(), + ), + None => Err(jsonrpc_core::Error::new( + jsonrpc_core::types::error::ErrorCode::ServerError(-8), + )), + } + } else { + match self.state.lock().unwrap().transactions.get(&txid) { + Some(tx) => Ok(Value::String(hex::encode(serialize(tx)))), + None => Err(jsonrpc_core::Error::new( + jsonrpc_core::types::error::ErrorCode::ServerError(-8), + )), + } } } } @@ -280,6 +307,11 @@ impl Handle { pub fn invalidate_tip(&self) -> BlockHash { self.state.lock().unwrap().pop_block() } + + pub fn tx(&self, bi: usize, ti: usize) -> Transaction { + let state = self.state.lock().unwrap(); + state.blocks[&state.hashes[bi]].txdata[ti].clone() + } } impl Drop for Handle {