diff --git a/chronik/chronik-bridge/src/ffi.rs b/chronik/chronik-bridge/src/ffi.rs --- a/chronik/chronik-bridge/src/ffi.rs +++ b/chronik/chronik-bridge/src/ffi.rs @@ -134,6 +134,7 @@ include!("node/context.h"); include!("primitives/block.h"); include!("primitives/transaction.h"); + include!("undo.h"); /// node::NodeContext from node/context.h #[namespace = "node"] @@ -147,6 +148,10 @@ #[namespace = ""] type CBlock; + /// ::CBlockUndo from undo.h + #[namespace = ""] + type CBlockUndo; + /// ::Coin from coins.h (renamed to CCoin to prevent a name clash) #[namespace = ""] #[cxx_name = "Coin"] @@ -203,6 +208,12 @@ block_index: &CBlockIndex, ) -> Result>; + /// Load the CBlockUndo data of this CBlockIndex from the disk undo data + fn load_block_undo( + self: &ChronikBridge, + block_index: &CBlockIndex, + ) -> Result>; + /// Find at which block the given block_index forks off from the node. fn find_fork( self: &ChronikBridge, @@ -252,6 +263,7 @@ /// Bridge bitcoind's classes to the shared struct [`Block`]. fn bridge_block( block: &CBlock, + block_undo: &CBlockUndo, block_index: &CBlockIndex, ) -> Result; diff --git a/chronik/chronik-cpp/chronik_bridge.h b/chronik/chronik-cpp/chronik_bridge.h --- a/chronik/chronik-cpp/chronik_bridge.h +++ b/chronik/chronik-cpp/chronik_bridge.h @@ -11,6 +11,7 @@ class CBlock; class CBlockIndex; +class CBlockUndo; class Coin; class Config; class CTransaction; @@ -63,6 +64,9 @@ std::unique_ptr load_block(const CBlockIndex &bindex) const; + std::unique_ptr + load_block_undo(const CBlockIndex &bindex) const; + const CBlockIndex &find_fork(const CBlockIndex &index) const; void lookup_spent_coins(Tx &, rust::Vec ¬_found, @@ -78,7 +82,8 @@ Tx bridge_tx(const CTransaction &tx, const std::vector &spent_coins); -Block bridge_block(const CBlock &block, const CBlockIndex &bindex); +Block bridge_block(const CBlock &block, const CBlockUndo &block_undo, + const CBlockIndex &bindex); Tx load_tx(uint32_t file_num, uint32_t data_pos, uint32_t undo_pos); rust::Vec load_raw_tx(uint32_t file_num, uint32_t data_pos); diff --git a/chronik/chronik-cpp/chronik_bridge.cpp b/chronik/chronik-cpp/chronik_bridge.cpp --- a/chronik/chronik-cpp/chronik_bridge.cpp +++ b/chronik/chronik-cpp/chronik_bridge.cpp @@ -112,17 +112,14 @@ } chronik_bridge::Block BridgeBlock(const CBlock &block, + const CBlockUndo &block_undo, const CBlockIndex &bindex) { size_t data_pos = GetFirstBlockTxOffset(block, bindex); size_t undo_pos = 0; - CBlockUndo block_undo; - // Read undo data (genesis block doesn't have undo data) + // Set undo offset; for the genesis block leave it at 0 if (bindex.nHeight > 0) { undo_pos = GetFirstUndoOffset(block, bindex); - if (!node::UndoReadFromDisk(block_undo, &bindex)) { - throw std::runtime_error("Reading block undo data failed"); - } } rust::Vec bridged_txs; @@ -203,6 +200,18 @@ return std::make_unique(std::move(block)); } +std::unique_ptr +ChronikBridge::load_block_undo(const CBlockIndex &bindex) const { + CBlockUndo block_undo; + // Read undo data (genesis block doesn't have undo data) + if (bindex.nHeight > 0) { + if (!node::UndoReadFromDisk(block_undo, &bindex)) { + throw std::runtime_error("Reading block undo data failed"); + } + } + return std::make_unique(std::move(block_undo)); +} + Tx bridge_tx(const CTransaction &tx, const std::vector<::Coin> &spent_coins) { return BridgeTx(false, tx, spent_coins); } @@ -292,8 +301,9 @@ } chronik_bridge::Block bridge_block(const CBlock &block, + const CBlockUndo &block_undo, const CBlockIndex &bindex) { - return BridgeBlock(block, bindex); + return BridgeBlock(block, block_undo, bindex); } Tx load_tx(uint32_t file_num, uint32_t data_pos, uint32_t undo_pos) { diff --git a/chronik/chronik-indexer/src/indexer.rs b/chronik/chronik-indexer/src/indexer.rs --- a/chronik/chronik-indexer/src/indexer.rs +++ b/chronik/chronik-indexer/src/indexer.rs @@ -240,9 +240,7 @@ return Ok(()); } let block_index = ffi::get_block_ancestor(node_tip_index, height)?; - let ffi_block = bridge.load_block(block_index)?; - let ffi_block = expect_unique_ptr("load_block", &ffi_block); - let block = self.make_chronik_block(ffi_block, block_index)?; + let block = self.load_chronik_block(bridge, block_index)?; let hash = block.db_block.hash.clone(); self.handle_block_connected(block)?; log_chronik!( @@ -301,9 +299,7 @@ let block_index = bridge .lookup_block_index(db_block.hash.to_bytes()) .map_err(|_| CannotRewindChronik(db_block.hash))?; - let ffi_block = bridge.load_block(block_index)?; - let ffi_block = expect_unique_ptr("load_block", &ffi_block); - let block = self.make_chronik_block(ffi_block, block_index)?; + let block = self.load_chronik_block(bridge, block_index)?; self.handle_block_disconnected(block)?; } Ok(fork_info.height) @@ -614,13 +610,8 @@ &self.subs } - /// Build the ChronikBlock from the CBlockIndex - pub fn make_chronik_block( - &self, - block: &ffi::CBlock, - bindex: &ffi::CBlockIndex, - ) -> Result { - let block = ffi::bridge_block(block, bindex)?; + /// Build a ChronikBlock from a ffi::Block. + pub fn make_chronik_block(&self, block: ffi::Block) -> ChronikBlock { let db_block = DbBlock { hash: BlockHash::from(block.hash), prev_hash: BlockHash::from(block.prev_hash), @@ -655,12 +646,27 @@ .into_iter() .map(|block_tx| Tx::from(block_tx.tx)) .collect::>(); - Ok(ChronikBlock { + ChronikBlock { db_block, block_txs, size: block.size, txs, - }) + } + } + + /// Load a ChronikBlock from the node given the CBlockIndex. + pub fn load_chronik_block( + &self, + bridge: &ffi::ChronikBridge, + block_index: &ffi::CBlockIndex, + ) -> Result { + let ffi_block = bridge.load_block(block_index)?; + let ffi_block = expect_unique_ptr("load_block", &ffi_block); + let ffi_block_undo = bridge.load_block_undo(block_index)?; + let ffi_block_undo = + expect_unique_ptr("load_block_undo", &ffi_block_undo); + let block = ffi::bridge_block(ffi_block, ffi_block_undo, block_index)?; + Ok(self.make_chronik_block(block)) } } diff --git a/chronik/chronik-lib/src/bridge.rs b/chronik/chronik-lib/src/bridge.rs --- a/chronik/chronik-lib/src/bridge.rs +++ b/chronik/chronik-lib/src/bridge.rs @@ -226,8 +226,11 @@ block: &ffi::CBlock, bindex: &ffi::CBlockIndex, ) -> Result<()> { + let block_undo = self.node.bridge.load_block_undo(bindex)?; + let block = + chronik_bridge::ffi::bridge_block(block, &block_undo, bindex)?; let mut indexer = self.indexer.blocking_write(); - let block = indexer.make_chronik_block(block, bindex)?; + let block = indexer.make_chronik_block(block); let block_hash = block.db_block.hash.clone(); let num_txs = block.block_txs.txs.len(); indexer.handle_block_connected(block)?; @@ -244,8 +247,11 @@ block: &ffi::CBlock, bindex: &ffi::CBlockIndex, ) -> Result<()> { + let block_undo = self.node.bridge.load_block_undo(bindex)?; + let block = + chronik_bridge::ffi::bridge_block(block, &block_undo, bindex)?; let mut indexer = self.indexer.blocking_write(); - let block = indexer.make_chronik_block(block, bindex)?; + let block = indexer.make_chronik_block(block); let block_hash = block.db_block.hash.clone(); let num_txs = block.block_txs.txs.len(); indexer.handle_block_disconnected(block)?; @@ -258,10 +264,8 @@ } fn finalize_block(&self, bindex: &ffi::CBlockIndex) -> Result<()> { - let block = self.node.bridge.load_block(bindex)?; - let block_ref = expect_unique_ptr("load_block", &block); let mut indexer = self.indexer.blocking_write(); - let block = indexer.make_chronik_block(block_ref, bindex)?; + let block = indexer.load_chronik_block(&self.node.bridge, bindex)?; let block_hash = block.db_block.hash.clone(); let num_txs = block.block_txs.txs.len(); indexer.handle_block_finalized(block)?; diff --git a/chronik/test/bridgeprimitives_tests.cpp b/chronik/test/bridgeprimitives_tests.cpp --- a/chronik/test/bridgeprimitives_tests.cpp +++ b/chronik/test/bridgeprimitives_tests.cpp @@ -109,14 +109,22 @@ } BOOST_FIXTURE_TEST_CASE(test_bridge_genesis, TestChain100Setup) { + LOCK(cs_main); + const CChainParams ¶ms = GetConfig().GetChainParams(); + const chronik_bridge::ChronikBridge bridge(params.GetConsensus(), m_node); ChainstateManager &chainman = *Assert(m_node.chainman); CBlockIndex *pgenesis = chainman.ActiveTip()->GetAncestor(0); const CBlock &genesisBlock = params.GenesisBlock(); - chronik_bridge::Block bridgedGenesisBlock = - chronik_bridge::bridge_block(genesisBlock, *pgenesis); + // Loading genesis unblock data returns an empty undo + std::unique_ptr genesisBlockUndo = + bridge.load_block_undo(*pgenesis); + BOOST_CHECK(genesisBlockUndo->vtxundo.empty()); + + chronik_bridge::Block bridgedGenesisBlock = chronik_bridge::bridge_block( + genesisBlock, *genesisBlockUndo, *pgenesis); chronik_bridge::Tx expectedGenesisTx = { .txid = HashToArray(genesisBlock.vtx[0]->GetId()), .version = 1, @@ -174,6 +182,10 @@ } BOOST_FIXTURE_TEST_CASE(test_bridge_detailled, TestChain100Setup) { + LOCK(cs_main); + + const CChainParams ¶ms = GetConfig().GetChainParams(); + const chronik_bridge::ChronikBridge bridge(params.GetConsensus(), m_node); ChainstateManager &chainman = *Assert(m_node.chainman); CBlock coinsBlock = CreateAndProcessBlock({}, CScript() << OP_1, @@ -203,11 +215,13 @@ CBlock testBlock = CreateAndProcessBlock({tx1, tx2}, CScript() << OP_2, &chainman.ActiveChainstate()); + std::unique_ptr testBlockUndo = + bridge.load_block_undo(*chainman.ActiveTip()); BOOST_CHECK_EQUAL(chainman.ActiveTip()->GetBlockHash(), testBlock.GetHash()); - chronik_bridge::Block bridgedTestBlock = - chronik_bridge::bridge_block(testBlock, *chainman.ActiveTip()); + chronik_bridge::Block bridgedTestBlock = chronik_bridge::bridge_block( + testBlock, *testBlockUndo, *chainman.ActiveTip()); chronik_bridge::Tx expectedTestTx0 = { .txid = HashToArray(testBlock.vtx[0]->GetId()), @@ -326,18 +340,24 @@ } BOOST_FIXTURE_TEST_CASE(test_bridge_bad, TestChain100Setup) { + LOCK(cs_main); + + const CChainParams ¶ms = GetConfig().GetChainParams(); + const chronik_bridge::ChronikBridge bridge(params.GetConsensus(), m_node); ChainstateManager &chainman = *Assert(m_node.chainman); - // Incompatible CBlock and CBlockIndex: - // CBlock has a tx that the disk doesn't have. + // Incompatible CBlock and CBlockUndo: + // CBlock has a tx that the CBlockUndo doesn't have. CBlock badBlock1 = CreateBlock({CMutableTransaction()}, CScript() << OP_1, chainman.ActiveChainstate()); - BOOST_CHECK_EXCEPTION( - chronik_bridge::bridge_block(badBlock1, *chainman.ActiveTip()), - std::runtime_error, [](const std::runtime_error &ex) { - BOOST_CHECK_EQUAL(ex.what(), "Missing undo data for tx"); - return true; - }); + CBlockUndo badBlockUndo1; + BOOST_CHECK_EXCEPTION(chronik_bridge::bridge_block(badBlock1, badBlockUndo1, + *chainman.ActiveTip()), + std::runtime_error, [](const std::runtime_error &ex) { + BOOST_CHECK_EQUAL(ex.what(), + "Missing undo data for tx"); + return true; + }); CBlock coinsBlock = CreateAndProcessBlock({}, CScript() << OP_1, &chainman.ActiveChainstate()); @@ -351,24 +371,32 @@ CScript() << OP_RETURN << std::vector(100))}; CreateAndProcessBlock({tx}, CScript() << OP_1, &chainman.ActiveChainstate()); + std::unique_ptr blockUndo = + bridge.load_block_undo(*chainman.ActiveTip()); // This time, bad CBlock has two inputs whereas the disk has only one. CMutableTransaction badTx; badTx.vin.resize(2); CBlock badBlock2 = CreateBlock({badTx}, CScript() << OP_1, chainman.ActiveChainstate()); - BOOST_CHECK_EXCEPTION( - chronik_bridge::bridge_block(badBlock2, *chainman.ActiveTip()), - std::runtime_error, [](const std::runtime_error &ex) { - BOOST_CHECK_EQUAL(ex.what(), "Missing coin for input"); - return true; - }); + BOOST_CHECK_EXCEPTION(chronik_bridge::bridge_block(badBlock2, *blockUndo, + *chainman.ActiveTip()), + std::runtime_error, [](const std::runtime_error &ex) { + BOOST_CHECK_EQUAL(ex.what(), + "Missing coin for input"); + return true; + }); } // It's easy to make a hard to detect off-by-one error when using // GetSizeOfCompactSize, therefore we test blocks with "dangerous" number of // txs, which cover the cases where GetSizeOfCompactSize goes from 1 -> 3 -> 5. BOOST_FIXTURE_TEST_CASE(test_bridge_big, TestChain100Setup) { + LOCK(cs_main); + + const CChainParams ¶ms = GetConfig().GetChainParams(); + const chronik_bridge::ChronikBridge bridge(params.GetConsensus(), m_node); + ChainstateManager &chainman = *Assert(m_node.chainman); std::vector testNumTxsCases = { @@ -411,9 +439,12 @@ BOOST_CHECK_EQUAL(chainman.ActiveTip()->GetBlockHash(), testBlock.GetHash()); + std::unique_ptr testBlockUndo = + bridge.load_block_undo(*chainman.ActiveTip()); + // test matches disk - chronik_bridge::Block bridgedBlock = - chronik_bridge::bridge_block(testBlock, *chainman.ActiveTip()); + chronik_bridge::Block bridgedBlock = chronik_bridge::bridge_block( + testBlock, *testBlockUndo, *chainman.ActiveTip()); CheckMatchesDisk(testBlock, bridgedBlock); for (const chronik_bridge::BlockTx &bridgedTx : bridgedBlock.txs) {