diff --git a/chronik/chronik-cpp/chronik_bridge.cpp b/chronik/chronik-cpp/chronik_bridge.cpp index 09d033b8f..bd5b873be 100644 --- a/chronik/chronik-cpp/chronik_bridge.cpp +++ b/chronik/chronik-cpp/chronik_bridge.cpp @@ -1,394 +1,395 @@ // Copyright (c) 2022 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include chronik_bridge::OutPoint BridgeOutPoint(const COutPoint &outpoint) { return { .txid = chronik::util::HashToArray(outpoint.GetTxId()), .out_idx = outpoint.GetN(), }; } chronik_bridge::TxOutput BridgeTxOutput(const CTxOut &output) { return { .value = output.nValue / Amount::satoshi(), .script = chronik::util::ToRustVec(output.scriptPubKey), }; } chronik_bridge::Coin BridgeCoin(const Coin &coin) { const int32_t nHeight = coin.GetHeight() == 0x7fff'ffff ? -1 : coin.GetHeight(); return { .output = BridgeTxOutput(coin.GetTxOut()), .height = nHeight, .is_coinbase = coin.IsCoinBase(), }; } rust::Vec BridgeTxInputs(bool isCoinbase, const std::vector &inputs, const std::vector &spent_coins) { rust::Vec bridged_inputs; bridged_inputs.reserve(inputs.size()); for (size_t idx = 0; idx < inputs.size(); ++idx) { const CTxIn &input = inputs[idx]; chronik_bridge::Coin bridge_coin{}; // empty coin if (!isCoinbase) { if (idx >= spent_coins.size()) { throw std::runtime_error("Missing coin for input"); } bridge_coin = BridgeCoin(spent_coins[idx]); } bridged_inputs.push_back({ .prev_out = BridgeOutPoint(input.prevout), .script = chronik::util::ToRustVec(input.scriptSig), .sequence = input.nSequence, .coin = std::move(bridge_coin), }); } return bridged_inputs; } rust::Vec BridgeTxOutputs(const std::vector &outputs) { rust::Vec bridged_outputs; bridged_outputs.reserve(outputs.size()); for (const CTxOut &output : outputs) { bridged_outputs.push_back(BridgeTxOutput(output)); } return bridged_outputs; } chronik_bridge::Tx BridgeTx(bool isCoinbase, const CTransaction &tx, const std::vector &spent_coins) { return { .txid = chronik::util::HashToArray(tx.GetId()), .version = tx.nVersion, .inputs = BridgeTxInputs(isCoinbase, tx.vin, spent_coins), .outputs = BridgeTxOutputs(tx.vout), .locktime = tx.nLockTime, }; } chronik_bridge::BlockTx BridgeBlockTx(bool isCoinbase, const CTransaction &tx, const std::vector &spent_coins, size_t data_pos, size_t undo_pos) { return {.tx = BridgeTx(isCoinbase, tx, spent_coins), .data_pos = uint32_t(data_pos), .undo_pos = uint32_t(isCoinbase ? 0 : undo_pos)}; } size_t GetFirstBlockTxOffset(const CBlock &block, const CBlockIndex &bindex) { return bindex.nDataPos + ::GetSerializeSize(CBlockHeader()) + GetSizeOfCompactSize(block.vtx.size()); } size_t GetFirstUndoOffset(const CBlock &block, const CBlockIndex &bindex) { // We have to -1 here, because coinbase txs don't have undo data. return bindex.nUndoPos + GetSizeOfCompactSize(block.vtx.size() - 1); } 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; // Set undo offset; for the genesis block leave it at 0 if (bindex.nHeight > 0) { undo_pos = GetFirstUndoOffset(block, bindex); } rust::Vec bridged_txs; for (size_t tx_idx = 0; tx_idx < block.vtx.size(); ++tx_idx) { const bool isCoinbase = tx_idx == 0; const CTransaction &tx = *block.vtx[tx_idx]; if (!isCoinbase && tx_idx - 1 >= block_undo.vtxundo.size()) { throw std::runtime_error("Missing undo data for tx"); } const std::vector &spent_coins = isCoinbase ? std::vector() : block_undo.vtxundo[tx_idx - 1].vprevout; bridged_txs.push_back( BridgeBlockTx(isCoinbase, tx, spent_coins, data_pos, undo_pos)); // advance data_pos and undo_pos positions data_pos += ::GetSerializeSize(tx); if (!isCoinbase) { undo_pos += ::GetSerializeSize(block_undo.vtxundo[tx_idx - 1]); } } return {.hash = chronik::util::HashToArray(block.GetHash()), .prev_hash = chronik::util::HashToArray(block.hashPrevBlock), .n_bits = block.nBits, .timestamp = block.GetBlockTime(), .height = bindex.nHeight, .file_num = uint32_t(bindex.nFile), .data_pos = bindex.nDataPos, .undo_pos = bindex.nUndoPos, .size = ::GetSerializeSize(block), .txs = bridged_txs}; } namespace chronik_bridge { void log_print(const rust::Str logging_function, const rust::Str source_file, const uint32_t source_line, const rust::Str msg) { LogInstance().LogPrintStr(std::string(msg), std::string(logging_function), std::string(source_file), source_line); } void log_print_chronik(const rust::Str logging_function, const rust::Str source_file, const uint32_t source_line, const rust::Str msg) { if (LogInstance().WillLogCategory(BCLog::CHRONIK)) { log_print(logging_function, source_file, source_line, msg); } } const CBlockIndex &ChronikBridge::get_chain_tip() const { const CBlockIndex *tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveTip()); if (tip == nullptr) { throw block_index_not_found(); } return *tip; } const CBlockIndex & ChronikBridge::lookup_block_index(std::array hash) const { BlockHash block_hash{chronik::util::ArrayToHash(hash)}; const CBlockIndex *pindex = WITH_LOCK( cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(block_hash)); if (!pindex) { throw block_index_not_found(); } return *pindex; } std::unique_ptr ChronikBridge::load_block(const CBlockIndex &bindex) const { CBlock block; if (!node::ReadBlockFromDisk(block, &bindex, m_consensus)) { throw std::runtime_error("Reading block data failed"); } 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 ChronikBridge::load_tx(uint32_t file_num, uint32_t data_pos, uint32_t undo_pos) const { CMutableTransaction tx; CTxUndo txundo{}; const bool isCoinbase = undo_pos == 0; if (!node::ReadTxFromDisk(tx, FlatFilePos(file_num, data_pos))) { throw std::runtime_error("Reading tx data from disk failed"); } if (!isCoinbase) { if (!node::ReadTxUndoFromDisk(txundo, FlatFilePos(file_num, undo_pos))) { throw std::runtime_error("Reading tx undo data from disk failed"); } } return BridgeTx(isCoinbase, CTransaction(std::move(tx)), txundo.vprevout); } rust::Vec ChronikBridge::load_raw_tx(uint32_t file_num, uint32_t data_pos) const { CMutableTransaction tx; if (!node::ReadTxFromDisk(tx, FlatFilePos(file_num, data_pos))) { throw std::runtime_error("Reading tx data from disk failed"); } CDataStream raw_tx{SER_NETWORK, PROTOCOL_VERSION}; raw_tx << tx; - return chronik::util::ToRustVec(raw_tx); + return chronik::util::ToRustVec(MakeUCharSpan(raw_tx)); } Tx bridge_tx(const CTransaction &tx, const std::vector<::Coin> &spent_coins) { return BridgeTx(false, tx, spent_coins); } const CBlockIndex &ChronikBridge::find_fork(const CBlockIndex &index) const { const CBlockIndex *fork = WITH_LOCK( cs_main, return m_node.chainman->ActiveChainstate().m_chain.FindFork(&index)); if (!fork) { throw block_index_not_found(); } return *fork; } void ChronikBridge::lookup_spent_coins( Tx &tx, rust::Vec ¬_found, rust::Vec &coins_to_uncache) const { not_found.clear(); coins_to_uncache.clear(); LOCK(cs_main); CCoinsViewCache &coins_cache = m_node.chainman->ActiveChainstate().CoinsTip(); CCoinsViewMemPool coin_view(&coins_cache, *m_node.mempool); for (TxInput &input : tx.inputs) { TxId txid = TxId(chronik::util::ArrayToHash(input.prev_out.txid)); COutPoint outpoint = COutPoint(txid, input.prev_out.out_idx); // Remember if coin was already cached const bool had_cached = coins_cache.HaveCoinInCache(outpoint); ::Coin coin; if (!coin_view.GetCoin(outpoint, coin)) { not_found.push_back(input.prev_out); continue; } if (!had_cached) { // Only add if previously uncached. // We don't check if the prev_out is now cached (which wouldn't be // the case for a mempool UTXO), as uncaching an outpoint is cheap, // so we save one extra cache lookup here. coins_to_uncache.push_back(input.prev_out); } input.coin = BridgeCoin(coin); } } void ChronikBridge::uncache_coins( rust::Slice coins_to_uncache) const { LOCK(cs_main); CCoinsViewCache &coins_cache = m_node.chainman->ActiveChainstate().CoinsTip(); for (const OutPoint &outpoint : coins_to_uncache) { TxId txid = TxId(chronik::util::ArrayToHash(outpoint.txid)); coins_cache.Uncache(COutPoint(txid, outpoint.out_idx)); } } std::array ChronikBridge::broadcast_tx(rust::Slice raw_tx, int64_t max_fee) const { std::vector vec = chronik::util::FromRustSlice(raw_tx); CDataStream stream{vec, SER_NETWORK, PROTOCOL_VERSION}; CMutableTransaction tx; stream >> tx; CTransactionRef tx_ref = MakeTransactionRef(tx); std::string err_str; TransactionError error = node::BroadcastTransaction( m_node, tx_ref, err_str, max_fee * Amount::satoshi(), /*relay=*/true, /*wait_callback=*/false); if (error != TransactionError::OK) { bilingual_str txErrorMsg = TransactionErrorString(error); if (err_str.empty()) { throw std::runtime_error(txErrorMsg.original.c_str()); } else { std::string msg = strprintf("%s: %s", txErrorMsg.original, err_str); throw std::runtime_error(msg.c_str()); } } return chronik::util::HashToArray(tx_ref->GetId()); } void ChronikBridge::abort_node(const rust::Str msg, const rust::Str user_msg) const { AbortNode(std::string(msg), Untranslated(std::string(user_msg))); } bool ChronikBridge::shutdown_requested() const { return ShutdownRequested(); } std::unique_ptr make_bridge(const Config &config, const node::NodeContext &node) { return std::make_unique( config.GetChainParams().GetConsensus(), node); } chronik_bridge::Block bridge_block(const CBlock &block, const CBlockUndo &block_undo, const CBlockIndex &bindex) { return BridgeBlock(block, block_undo, bindex); } BlockInfo get_block_info(const CBlockIndex &bindex) { return { .hash = chronik::util::HashToArray(bindex.GetBlockHash()), .height = bindex.nHeight, }; } const CBlockIndex &get_block_ancestor(const CBlockIndex &index, int32_t height) { const CBlockIndex *pindex = index.GetAncestor(height); if (!pindex) { throw block_index_not_found(); } return *pindex; } rust::Vec compress_script(rust::Slice bytecode) { std::vector vec = chronik::util::FromRustSlice(bytecode); CScript script{vec.begin(), vec.end()}; CDataStream compressed{SER_NETWORK, PROTOCOL_VERSION}; compressed << Using(script); - return chronik::util::ToRustVec(compressed); + return chronik::util::ToRustVec(MakeUCharSpan(compressed)); } rust::Vec decompress_script(rust::Slice compressed) { std::vector vec = chronik::util::FromRustSlice(compressed); CDataStream stream{vec, SER_NETWORK, PROTOCOL_VERSION}; CScript script; stream >> Using(script); return chronik::util::ToRustVec(script); } int64_t calc_fee(size_t num_bytes, int64_t sats_fee_per_kb) { return CFeeRate(sats_fee_per_kb * SATOSHI).GetFee(num_bytes) / SATOSHI; } int64_t default_max_raw_tx_fee_rate_per_kb() { return node::DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK() / SATOSHI; } void sync_with_validation_interface_queue() { SyncWithValidationInterfaceQueue(); } bool init_error(const rust::Str msg) { return InitError(Untranslated(std::string(msg))); } } // namespace chronik_bridge