Page MenuHomePhabricator

No OneTemporary

diff --git a/chronik/chronik-bridge/src/ffi.rs b/chronik/chronik-bridge/src/ffi.rs
index 8532ee8a6..0b3c5d498 100644
--- a/chronik/chronik-bridge/src/ffi.rs
+++ b/chronik/chronik-bridge/src/ffi.rs
@@ -1,241 +1,244 @@
// 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.
//! Module containing the cxx definitions for the bridge from C++ to Rust.
pub use self::ffi_inner::*;
#[allow(unsafe_code)]
#[cxx::bridge(namespace = "chronik_bridge")]
mod ffi_inner {
/// Info about a block
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BlockInfo {
/// Hash of the block (or 000...000 if no block)
pub hash: [u8; 32],
/// Height of the block (or -1 if no block)
pub height: i32,
}
/// Block coming from bitcoind to Chronik.
///
/// We don't index all fields (e.g. hashMerkleRoot), only those that are
/// needed when querying a range of blocks.
///
/// Instead of storing all the block data for Chronik again, we only store
/// file_num, data_pos and undo_pos of the block data of the node.
///
/// This makes the index relatively small, as it's mostly just pointing to
/// the data the node already stores.
///
/// Note that this prohibits us from using Chronik in pruned mode.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Block {
/// Block hash
pub hash: [u8; 32],
/// hashPrevBlock, hash of the previous block in the chain
pub prev_hash: [u8; 32],
/// nBits, difficulty of the header
pub n_bits: u32,
/// Timestamp of the block
pub timestamp: i64,
/// Height of the block in the chain.
pub height: i32,
/// File number of the block file this block is stored in.
/// This can be used to later slice out transactions, so we don't have
/// to index txs twice.
pub file_num: u32,
/// Position of the block within the block file, starting at the block
/// header.
pub data_pos: u32,
/// Position of the undo data within the undo file.
pub undo_pos: u32,
/// Txs of this block, including positions within the block/undo files.
pub txs: Vec<BlockTx>,
}
/// Tx in a block
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct BlockTx {
/// Tx (without disk data)
pub tx: Tx,
/// Where the tx is stored within the block file.
pub data_pos: u32,
/// Where the tx's undo data is stored within the block's undo file.
pub undo_pos: u32,
}
/// CTransaction, in a block or in the mempool.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Tx {
/// TxId of the tx.
pub txid: [u8; 32],
/// nVersion of the tx.
pub version: i32,
/// Tx inputs.
pub inputs: Vec<TxInput>,
/// Tx outputs.
pub outputs: Vec<TxOutput>,
/// Locktime of the tx.
pub locktime: u32,
}
/// COutPoint, pointing to a coin being spent.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct OutPoint {
/// TxId of the output of the coin.
pub txid: [u8; 32],
/// Index in the outputs of the tx of the coin.
pub out_idx: u32,
}
/// CTxIn, spending an unspent output.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TxInput {
/// Points to an output being spent.
pub prev_out: OutPoint,
/// scriptSig unlocking the output.
pub script: Vec<u8>,
/// nSequence.
pub sequence: u32,
/// Coin being spent by this tx.
pub coin: Coin,
}
/// CTxOut, creating a new output.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TxOutput {
/// Value of the output.
pub value: i64,
/// Script locking the output.
pub script: Vec<u8>,
}
/// Coin, can be spent by providing a valid unlocking script.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Coin {
/// Output, locking the coins.
pub output: TxOutput,
/// Height of the coin in the chain.
pub height: i32,
/// Whether the coin is a coinbase.
pub is_coinbase: bool,
}
#[allow(missing_debug_implementations)]
unsafe extern "C++" {
include!("blockindex.h");
include!("chronik-cpp/chronik_bridge.h");
include!("node/context.h");
include!("primitives/block.h");
include!("primitives/transaction.h");
/// node::NodeContext from node/context.h
#[namespace = "node"]
type NodeContext;
/// ::CBlockIndex from blockindex.h
#[namespace = ""]
type CBlockIndex;
/// ::CBlock from primitives/block.h
#[namespace = ""]
type CBlock;
/// ::Config from config.h
#[namespace = ""]
type Config;
/// ::CTransaction from primitives/transaction.h
#[namespace = ""]
type CTransaction;
/// Bridge to bitcoind to access the node
type ChronikBridge;
/// Print the message to bitcoind's logs.
fn log_print(
logging_function: &str,
source_file: &str,
source_line: u32,
msg: &str,
);
/// Print the message to bitcoind's logs under the BCLog::Chronik
/// category.
fn log_print_chronik(
logging_function: &str,
source_file: &str,
source_line: u32,
msg: &str,
);
/// Make the bridge given the NodeContext
fn make_bridge(
config: &Config,
node: &NodeContext,
) -> UniquePtr<ChronikBridge>;
/// Return the tip of the chain of the node.
/// Returns hash=000...000, height=-1 if there's no block on the chain.
fn get_chain_tip(self: &ChronikBridge) -> Result<&CBlockIndex>;
/// Lookup the block index with the given hash, or throw an error
/// if it couldn't be found.
fn lookup_block_index(
self: &ChronikBridge,
hash: [u8; 32],
) -> Result<&CBlockIndex>;
/// Load the CBlock data of this CBlockIndex from the disk
fn load_block(
self: &ChronikBridge,
block_index: &CBlockIndex,
) -> Result<UniquePtr<CBlock>>;
/// Bridge CTransaction -> ffi::Tx, including finding the spent coins.
/// `tx` can be a mempool tx.
fn bridge_tx(self: &ChronikBridge, tx: &CTransaction) -> Result<Tx>;
/// Find at which block the given block_index forks off from the node.
fn find_fork(
self: &ChronikBridge,
block_index: &CBlockIndex,
) -> Result<&CBlockIndex>;
/// Bridge bitcoind's classes to the shared struct [`Block`].
fn bridge_block(
block: &CBlock,
block_index: &CBlockIndex,
) -> Result<Block>;
/// Load the CTransaction and CTxUndo data from disk and turn it into a
/// bridged Tx, containing spent coins etc.
fn load_tx(file_num: u32, data_pos: u32, undo_pos: u32) -> Result<Tx>;
+ /// Load the CTransaction from disk and serialize it.
+ fn load_raw_tx(file_num: u32, data_pos: u32) -> Result<Vec<u8>>;
+
/// Get a BlockInfo for this CBlockIndex.
fn get_block_info(block_index: &CBlockIndex) -> BlockInfo;
/// CBlockIndex::GetAncestor
fn get_block_ancestor(
block_index: &CBlockIndex,
height: i32,
) -> Result<&CBlockIndex>;
/// Compress the given script using `ScriptCompression`.
fn compress_script(script: &[u8]) -> Vec<u8>;
/// Decompress the given script using `ScriptCompression`.
fn decompress_script(compressed: &[u8]) -> Result<Vec<u8>>;
/// Calls `InitError` from `node/ui_interface.h` to report an error to
/// the user and then gracefully shut down the node.
fn init_error(msg: &str) -> bool;
/// Calls `AbortNode` from shutdown.h to gracefully shut down the node
/// when an unrecoverable error occured.
fn abort_node(msg: &str, user_msg: &str);
}
}
diff --git a/chronik/chronik-cpp/chronik_bridge.cpp b/chronik/chronik-cpp/chronik_bridge.cpp
index d644e0a08..3110c3840 100644
--- a/chronik/chronik-cpp/chronik_bridge.cpp
+++ b/chronik/chronik-cpp/chronik_bridge.cpp
@@ -1,296 +1,306 @@
// 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 <blockindex.h>
#include <chainparams.h>
#include <chronik-bridge/src/ffi.rs.h>
#include <chronik-cpp/chronik_bridge.h>
#include <chronik-cpp/util/collection.h>
#include <chronik-cpp/util/hash.h>
#include <compressor.h>
#include <config.h>
#include <logging.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/ui_interface.h>
#include <shutdown.h>
#include <streams.h>
#include <undo.h>
#include <validation.h>
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<uint8_t>(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<chronik_bridge::TxInput>
BridgeTxInputs(bool isCoinbase, const std::vector<CTxIn> &inputs,
const std::vector<Coin> &spent_coins) {
rust::Vec<chronik_bridge::TxInput> 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<uint8_t>(input.scriptSig),
.sequence = input.nSequence,
.coin = std::move(bridge_coin),
});
}
return bridged_inputs;
}
rust::Vec<chronik_bridge::TxOutput>
BridgeTxOutputs(const std::vector<CTxOut> &outputs) {
rust::Vec<chronik_bridge::TxOutput> 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<Coin> &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<Coin> &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 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)
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<chronik_bridge::BlockTx> 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<Coin> &spent_coins =
isCoinbase ? std::vector<Coin>()
: 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,
.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<uint8_t, 32> 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<CBlock>
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<CBlock>(std::move(block));
}
Tx ChronikBridge::bridge_tx(const CTransaction &tx) const {
std::map<COutPoint, ::Coin> coins;
for (const CTxIn &input : tx.vin) {
coins[input.prevout];
}
FindCoins(m_node, coins);
std::vector<::Coin> spent_coins;
spent_coins.reserve(tx.vin.size());
for (const CTxIn &input : tx.vin) {
const ::Coin &coin = coins[input.prevout];
if (coin.GetTxOut().IsNull()) {
throw std::runtime_error("Couldn't find coin for input");
}
spent_coins.push_back(coin);
}
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;
}
std::unique_ptr<ChronikBridge> make_bridge(const Config &config,
const node::NodeContext &node) {
return std::make_unique<ChronikBridge>(
config.GetChainParams().GetConsensus(), node);
}
chronik_bridge::Block bridge_block(const CBlock &block,
const CBlockIndex &bindex) {
return BridgeBlock(block, bindex);
}
Tx load_tx(uint32_t file_num, uint32_t data_pos, uint32_t undo_pos) {
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<uint8_t> load_raw_tx(uint32_t file_num, uint32_t data_pos) {
+ 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<uint8_t>(raw_tx);
+}
+
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<uint8_t> compress_script(rust::Slice<const uint8_t> bytecode) {
std::vector<uint8_t> vec = chronik::util::FromRustSlice(bytecode);
CScript script{vec.begin(), vec.end()};
CDataStream compressed{SER_NETWORK, PROTOCOL_VERSION};
compressed << Using<ScriptCompression>(script);
return chronik::util::ToRustVec<uint8_t>(compressed);
}
rust::Vec<uint8_t> decompress_script(rust::Slice<const uint8_t> compressed) {
std::vector<uint8_t> vec = chronik::util::FromRustSlice(compressed);
CDataStream stream{vec, SER_NETWORK, PROTOCOL_VERSION};
CScript script;
stream >> Using<ScriptCompression>(script);
return chronik::util::ToRustVec<uint8_t>(script);
}
bool init_error(const rust::Str msg) {
return InitError(Untranslated(std::string(msg)));
}
void abort_node(const rust::Str msg, const rust::Str user_msg) {
AbortNode(std::string(msg), Untranslated(std::string(user_msg)));
}
} // namespace chronik_bridge
diff --git a/chronik/chronik-cpp/chronik_bridge.h b/chronik/chronik-cpp/chronik_bridge.h
index 3e1ce420c..0801fa6fd 100644
--- a/chronik/chronik-cpp/chronik_bridge.h
+++ b/chronik/chronik-cpp/chronik_bridge.h
@@ -1,89 +1,90 @@
// 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.
#ifndef BITCOIN_CHRONIK_CPP_CHRONIK_BRIDGE_H
#define BITCOIN_CHRONIK_CPP_CHRONIK_BRIDGE_H
#include <memory>
#include <rust/cxx.h>
class CBlock;
class CBlockIndex;
class Config;
class CTransaction;
namespace Consensus {
struct Params;
} // namespace Consensus
namespace node {
struct NodeContext;
} // namespace node
class uint256;
namespace chronik_bridge {
struct BlockInfo;
struct Block;
struct Tx;
class block_index_not_found : public std::exception {
public:
const char *what() const noexcept override {
return "CBlockIndex not found";
}
};
void log_print(const rust::Str logging_function, const rust::Str source_file,
const uint32_t source_line, const rust::Str msg);
void log_print_chronik(const rust::Str logging_function,
const rust::Str source_file, const uint32_t source_line,
const rust::Str msg);
/**
* Bridge to bitcoind to access the node.
*/
class ChronikBridge {
const Consensus::Params &m_consensus;
const node::NodeContext &m_node;
public:
ChronikBridge(const Consensus::Params &consensus,
const node::NodeContext &node)
: m_consensus(consensus), m_node(node) {}
const CBlockIndex &get_chain_tip() const;
const CBlockIndex &lookup_block_index(std::array<uint8_t, 32> hash) const;
std::unique_ptr<CBlock> load_block(const CBlockIndex &bindex) const;
Tx bridge_tx(const CTransaction &tx) const;
const CBlockIndex &find_fork(const CBlockIndex &index) const;
};
std::unique_ptr<ChronikBridge> make_bridge(const Config &config,
const node::NodeContext &node);
Block bridge_block(const CBlock &block, const CBlockIndex &bindex);
Tx load_tx(uint32_t file_num, uint32_t data_pos, uint32_t undo_pos);
+rust::Vec<uint8_t> load_raw_tx(uint32_t file_num, uint32_t data_pos);
BlockInfo get_block_info(const CBlockIndex &index);
const CBlockIndex &get_block_ancestor(const CBlockIndex &index, int32_t height);
rust::Vec<uint8_t> compress_script(rust::Slice<const uint8_t> script);
rust::Vec<uint8_t> decompress_script(rust::Slice<const uint8_t> compressed);
bool init_error(const rust::Str msg);
void abort_node(const rust::Str msg, const rust::Str user_msg);
} // namespace chronik_bridge
#endif // BITCOIN_CHRONIK_CPP_CHRONIK_BRIDGE_H
diff --git a/chronik/test/bridgeprimitives_tests.cpp b/chronik/test/bridgeprimitives_tests.cpp
index 8d247ef52..1d16db128 100644
--- a/chronik/test/bridgeprimitives_tests.cpp
+++ b/chronik/test/bridgeprimitives_tests.cpp
@@ -1,435 +1,450 @@
// Copyright (c) 2023 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// Test to verify all the Bridge* functions in chronik_bridge.cpp are working
// correctly.
#include <chainparams.h>
#include <chronik-bridge/src/ffi.rs.h>
#include <chronik-cpp/chronik_bridge.h>
#include <chronik-cpp/util/collection.h>
#include <chronik-cpp/util/hash.h>
#include <config.h>
#include <node/blockstorage.h>
+#include <streams.h>
#include <undo.h>
#include <util/strencodings.h>
#include <validation.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
using namespace chronik::util;
BOOST_AUTO_TEST_SUITE(bridgeprimitives_tests)
void CheckTxsEqual(const chronik_bridge::Tx &left,
const chronik_bridge::Tx &right) {
BOOST_CHECK_EQUAL(HexStr(left.txid), HexStr(right.txid));
BOOST_CHECK_EQUAL(left.version, right.version);
BOOST_CHECK_EQUAL(left.locktime, right.locktime);
BOOST_CHECK_EQUAL(left.inputs.size(), right.inputs.size());
for (size_t inputIdx = 0; inputIdx < left.inputs.size(); ++inputIdx) {
const chronik_bridge::TxInput &inLeft = left.inputs[inputIdx];
const chronik_bridge::TxInput &inRight = right.inputs.at(inputIdx);
BOOST_CHECK_EQUAL(HexStr(inLeft.prev_out.txid),
HexStr(inRight.prev_out.txid));
BOOST_CHECK_EQUAL(inLeft.prev_out.out_idx, inRight.prev_out.out_idx);
BOOST_CHECK_EQUAL(HexStr(inLeft.script), HexStr(inRight.script));
BOOST_CHECK_EQUAL(inLeft.sequence, inRight.sequence);
BOOST_CHECK_EQUAL(inLeft.coin.output.value, inRight.coin.output.value);
BOOST_CHECK_EQUAL(HexStr(inLeft.coin.output.script),
HexStr(inRight.coin.output.script));
BOOST_CHECK_EQUAL(inLeft.coin.height, inRight.coin.height);
BOOST_CHECK_EQUAL(inLeft.coin.is_coinbase, inRight.coin.is_coinbase);
}
BOOST_CHECK_EQUAL(left.outputs.size(), right.outputs.size());
for (size_t outputIdx = 0; outputIdx < left.outputs.size(); ++outputIdx) {
const chronik_bridge::TxOutput &outLeft = left.outputs[outputIdx];
const chronik_bridge::TxOutput &outRight = right.outputs.at(outputIdx);
BOOST_CHECK_EQUAL(outLeft.value, outRight.value);
BOOST_CHECK_EQUAL(HexStr(outLeft.script), HexStr(outRight.script));
}
}
void CheckBlocksEqual(const chronik_bridge::Block &left,
const chronik_bridge::Block &right) {
BOOST_CHECK_EQUAL(HexStr(left.hash), HexStr(right.hash));
BOOST_CHECK_EQUAL(HexStr(left.prev_hash), HexStr(right.prev_hash));
BOOST_CHECK_EQUAL(left.n_bits, right.n_bits);
BOOST_CHECK_EQUAL(left.timestamp, right.timestamp);
BOOST_CHECK_EQUAL(left.height, right.height);
BOOST_CHECK_EQUAL(left.file_num, right.file_num);
BOOST_CHECK_EQUAL(left.data_pos, right.data_pos);
BOOST_CHECK_EQUAL(left.undo_pos, right.undo_pos);
BOOST_CHECK_EQUAL(left.txs.size(), right.txs.size());
for (size_t txIdx = 0; txIdx < left.txs.size(); ++txIdx) {
const chronik_bridge::BlockTx &txLeft = left.txs[txIdx];
const chronik_bridge::BlockTx &txRight = right.txs.at(txIdx);
BOOST_CHECK_EQUAL(txLeft.data_pos, txRight.data_pos);
BOOST_CHECK_EQUAL(txLeft.undo_pos, txRight.undo_pos);
CheckTxsEqual(txLeft.tx, txRight.tx);
}
}
void CheckMatchesDisk(const CBlock &block,
const chronik_bridge::Block &bridgedBlock) {
for (size_t idx = 0; idx < block.vtx.size(); ++idx) {
const chronik_bridge::BlockTx blockTx = bridgedBlock.txs[idx];
CMutableTransaction txFromDisk;
BOOST_CHECK(node::ReadTxFromDisk(
txFromDisk, FlatFilePos(bridgedBlock.file_num, blockTx.data_pos)));
BOOST_CHECK(txFromDisk.GetHash() == block.vtx[idx]->GetHash());
if (idx == 0) {
continue;
}
CTxUndo txundo;
BOOST_CHECK(node::ReadTxUndoFromDisk(
txundo, FlatFilePos(bridgedBlock.file_num, blockTx.undo_pos)));
BOOST_CHECK_EQUAL(txundo.vprevout.size(), txFromDisk.vin.size());
for (size_t inputIdx = 0; inputIdx < blockTx.tx.inputs.size();
++inputIdx) {
const Coin &coin = txundo.vprevout[inputIdx];
const chronik_bridge::Coin &bridgeCoin =
blockTx.tx.inputs[inputIdx].coin;
BOOST_CHECK_EQUAL(coin.GetTxOut().nValue / SATOSHI,
bridgeCoin.output.value);
BOOST_CHECK_EQUAL(HexStr(coin.GetTxOut().scriptPubKey),
HexStr(bridgeCoin.output.script));
BOOST_CHECK_EQUAL(coin.GetHeight(), bridgeCoin.height);
BOOST_CHECK_EQUAL(coin.IsCoinBase(), bridgeCoin.is_coinbase);
}
}
}
BOOST_FIXTURE_TEST_CASE(test_bridge_genesis, TestChain100Setup) {
const CChainParams &params = GetConfig().GetChainParams();
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);
chronik_bridge::Tx expectedGenesisTx = {
.txid = HashToArray(genesisBlock.vtx[0]->GetId()),
.version = 1,
.inputs = {{
.prev_out = chronik_bridge::OutPoint({
.txid = {},
.out_idx = 0xffff'ffff,
}),
.script = ToRustVec<uint8_t>(genesisBlock.vtx[0]->vin[0].scriptSig),
.sequence = 0xffff'ffff,
.coin = {}, // null coin
}},
.outputs = {{
.value = 5000000000,
.script =
ToRustVec<uint8_t>(genesisBlock.vtx[0]->vout[0].scriptPubKey),
}},
.locktime = 0,
};
chronik_bridge::Block expectedBridgedGenesisBlock = {
.hash = HashToArray(genesisBlock.GetHash()),
.prev_hash = {},
.n_bits = 0x207fffff,
.timestamp = 1296688602,
.height = 0,
.file_num = 0,
.data_pos = 8, // 8 magic bytes in block file
.undo_pos = 0, // genesis has no undo data
.txs = {{
.tx = expectedGenesisTx,
.data_pos = 89, // +80 header +1 compact size
.undo_pos = 0 // coinbase has no undo data
}}};
CheckBlocksEqual(bridgedGenesisBlock, expectedBridgedGenesisBlock);
chronik_bridge::BlockTx &bridgedGenesisTx = bridgedGenesisBlock.txs[0];
CMutableTransaction genesisTxFromDisk;
BOOST_CHECK(node::ReadTxFromDisk(
genesisTxFromDisk,
FlatFilePos(bridgedGenesisBlock.file_num, bridgedGenesisTx.data_pos)));
BOOST_CHECK(genesisTxFromDisk.GetHash() == genesisBlock.vtx[0]->GetHash());
CheckTxsEqual(chronik_bridge::load_tx(bridgedGenesisBlock.file_num,
bridgedGenesisTx.data_pos,
bridgedGenesisTx.undo_pos),
bridgedGenesisTx.tx);
+
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << genesisBlock.vtx[0];
+ BOOST_CHECK_EQUAL(HexStr(ss), HexStr(chronik_bridge::load_raw_tx(
+ bridgedGenesisBlock.file_num,
+ bridgedGenesisTx.data_pos)));
}
BOOST_FIXTURE_TEST_CASE(test_bridge_detailled, TestChain100Setup) {
ChainstateManager &chainman = *Assert(m_node.chainman);
CBlock coinsBlock = CreateAndProcessBlock({}, CScript() << OP_1,
&chainman.ActiveChainstate());
mineBlocks(100);
CScript scriptPad = CScript() << OP_RETURN << std::vector<uint8_t>(100);
CMutableTransaction tx1;
tx1.nVersion = 1;
tx1.vin = {CTxIn(coinsBlock.vtx[0]->GetId(), 0)};
tx1.vout = {
CTxOut(50 * COIN - 10000 * SATOSHI, CScript() << OP_3),
CTxOut(1000 * SATOSHI, CScript() << OP_4),
CTxOut(Amount::zero(), scriptPad),
};
tx1.nLockTime = 123;
CMutableTransaction tx2;
tx2.nVersion = 1;
tx2.vin = {CTxIn(tx1.GetId(), 0), CTxIn(tx1.GetId(), 1)};
tx2.vout = {
CTxOut(50 * COIN - 30000 * SATOSHI, CScript() << OP_5),
CTxOut(Amount::zero(), scriptPad),
};
BOOST_CHECK(tx1.GetId() < tx2.GetId());
CBlock testBlock = CreateAndProcessBlock({tx1, tx2}, CScript() << OP_2,
&chainman.ActiveChainstate());
BOOST_CHECK_EQUAL(chainman.ActiveTip()->GetBlockHash(),
testBlock.GetHash());
chronik_bridge::Block bridgedTestBlock =
chronik_bridge::bridge_block(testBlock, *chainman.ActiveTip());
chronik_bridge::Tx expectedTestTx0 = {
.txid = HashToArray(testBlock.vtx[0]->GetId()),
.version = 2,
.inputs = {{
.prev_out = chronik_bridge::OutPoint({
.txid = {},
.out_idx = 0xffff'ffff,
}),
.script = ToRustVec<uint8_t>(testBlock.vtx[0]->vin[0].scriptSig),
.sequence = 0xffff'ffff,
.coin = {}, // null coin
}},
.outputs = {{
.value = 2500000000,
.script = {0x52},
}},
.locktime = 0,
};
chronik_bridge::Tx expectedTestTx1 = {
.txid = HashToArray(tx1.GetId()),
.version = 1,
.inputs = {{
.prev_out = chronik_bridge::OutPoint({
.txid = HashToArray(coinsBlock.vtx[0]->GetId()),
.out_idx = 0,
}),
.script = {},
.sequence = 0xffff'ffff,
.coin = chronik_bridge::Coin({
.output = {5000000000, {0x51}},
.height = 101,
.is_coinbase = true,
}),
}},
.outputs = {{4999990000, {0x53}},
{1000, {0x54}},
{0, ToRustVec<uint8_t>(scriptPad)}},
.locktime = 123,
};
chronik_bridge::Tx expectedTestTx2 = {
.txid = HashToArray(tx2.GetId()),
.version = 1,
.inputs = {chronik_bridge::TxInput({
.prev_out = chronik_bridge::OutPoint({
.txid = HashToArray(tx1.GetId()),
.out_idx = 0,
}),
.script = {},
.sequence = 0xffff'ffff,
.coin =
{
.output = {4999990000, {0x53}},
.height = 202,
.is_coinbase = false,
},
}),
chronik_bridge::TxInput({
.prev_out = chronik_bridge::OutPoint({
.txid = HashToArray(tx1.GetId()),
.out_idx = 1,
}),
.script = {},
.sequence = 0xffff'ffff,
.coin =
{
.output = {1000, {0x54}},
.height = 202,
.is_coinbase = false,
},
})},
.outputs = {{
.value = 4999970000,
.script = {0x55},
},
{
.value = 0,
.script = ToRustVec<uint8_t>(scriptPad),
}},
.locktime = 0,
};
chronik_bridge::Block expectedBridgedTestBlock = {
.hash = HashToArray(testBlock.GetHash()),
.prev_hash = HashToArray(testBlock.hashPrevBlock),
.n_bits = 0x207fffff,
.timestamp = 1598888152,
.height = 202,
.file_num = 0,
.data_pos = 39948,
.undo_pos = 8249,
.txs = {
{.tx = expectedTestTx0, .data_pos = 40029, .undo_pos = 0},
{.tx = expectedTestTx1, .data_pos = 40129, .undo_pos = 8250},
{.tx = expectedTestTx2, .data_pos = 40312, .undo_pos = 8257},
}};
CheckBlocksEqual(bridgedTestBlock, expectedBridgedTestBlock);
CheckMatchesDisk(testBlock, bridgedTestBlock);
for (const chronik_bridge::BlockTx &bridgedTx : bridgedTestBlock.txs) {
CheckTxsEqual(chronik_bridge::load_tx(bridgedTestBlock.file_num,
bridgedTx.data_pos,
bridgedTx.undo_pos),
bridgedTx.tx);
}
+
+ for (size_t i = 0; i < testBlock.vtx.size(); ++i) {
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << testBlock.vtx[i];
+ BOOST_CHECK_EQUAL(HexStr(ss), HexStr(chronik_bridge::load_raw_tx(
+ bridgedTestBlock.file_num,
+ bridgedTestBlock.txs[i].data_pos)));
+ }
}
BOOST_FIXTURE_TEST_CASE(test_bridge_bad, TestChain100Setup) {
ChainstateManager &chainman = *Assert(m_node.chainman);
// Incompatible CBlock and CBlockIndex:
// CBlock has a tx that the disk 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;
});
CBlock coinsBlock = CreateAndProcessBlock({}, CScript() << OP_1,
&chainman.ActiveChainstate());
mineBlocks(100);
// Create block with some undo data
CMutableTransaction tx;
tx.nVersion = 1;
tx.vin = {CTxIn(coinsBlock.vtx[0]->GetId(), 0)};
tx.vout = {CTxOut(50 * COIN - 10000 * SATOSHI,
CScript() << OP_RETURN << std::vector<uint8_t>(100))};
CreateAndProcessBlock({tx}, CScript() << OP_1,
&chainman.ActiveChainstate());
// 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;
});
}
// 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) {
ChainstateManager &chainman = *Assert(m_node.chainman);
std::vector<size_t> testNumTxsCases = {
0, 1, 2, 3, 10, 62, 63, 64, 65, 126, 127, 128,
129, 130, 250, 251, 252, 253, 254, 255, 256, 257, 258, 65536};
std::vector<CBlock> coinblocks;
for (size_t idx = 0; idx < testNumTxsCases.size(); ++idx) {
CBlock coinblock = CreateAndProcessBlock({}, CScript() << OP_1,
&chainman.ActiveChainstate());
coinblocks.push_back(coinblock);
BOOST_CHECK_EQUAL(chainman.ActiveTip()->GetBlockHash(),
coinblock.GetHash());
}
mineBlocks(100);
CScript scriptPad = CScript() << OP_RETURN << std::vector<uint8_t>(100);
for (size_t idx = 0; idx < testNumTxsCases.size(); ++idx) {
size_t numTxs = testNumTxsCases[idx];
std::vector<CMutableTransaction> txs;
txs.reserve(numTxs);
const CTransactionRef &coinbase = coinblocks[idx].vtx[0];
Amount available = coinbase->vout[0].nValue;
TxId coinTxId = coinbase->GetId();
for (size_t txIdx = 0; txIdx < numTxs; ++txIdx) {
CMutableTransaction tx;
tx.nVersion = 1;
tx.vin = {CTxIn(coinTxId, 0)};
available -= 10000 * SATOSHI;
tx.vout = {
CTxOut(available, CScript() << OP_2),
CTxOut(Amount::zero(), scriptPad),
};
coinTxId = tx.GetId();
txs.push_back(tx);
}
CBlock testBlock = CreateAndProcessBlock(txs, CScript() << OP_1,
&chainman.ActiveChainstate());
BOOST_CHECK_EQUAL(chainman.ActiveTip()->GetBlockHash(),
testBlock.GetHash());
// test matches disk
chronik_bridge::Block bridgedBlock =
chronik_bridge::bridge_block(testBlock, *chainman.ActiveTip());
CheckMatchesDisk(testBlock, bridgedBlock);
for (const chronik_bridge::BlockTx &bridgedTx : bridgedBlock.txs) {
CheckTxsEqual(chronik_bridge::load_tx(bridgedBlock.file_num,
bridgedTx.data_pos,
bridgedTx.undo_pos),
bridgedTx.tx);
}
}
}
BOOST_FIXTURE_TEST_CASE(test_load_tx_bad, TestChain100Setup) {
BOOST_CHECK_EXCEPTION(
chronik_bridge::load_tx(0x7fffffff, 0, 0), std::runtime_error,
[](const std::runtime_error &ex) {
BOOST_CHECK_EQUAL(ex.what(), "Reading tx data from disk failed");
return true;
});
BOOST_CHECK_EXCEPTION(
chronik_bridge::load_tx(0, 0x7fffffff, 0), std::runtime_error,
[](const std::runtime_error &ex) {
BOOST_CHECK_EQUAL(ex.what(), "Reading tx data from disk failed");
return true;
});
uint32_t genesisCbDataPos = 89;
BOOST_CHECK_EXCEPTION(
chronik_bridge::load_tx(0, genesisCbDataPos, 0x7fffffff),
std::runtime_error, [](const std::runtime_error &ex) {
BOOST_CHECK_EQUAL(ex.what(),
"Reading tx undo data from disk failed");
return true;
});
// sanity check
chronik_bridge::load_tx(0, genesisCbDataPos, 0);
}
BOOST_AUTO_TEST_SUITE_END()

File Metadata

Mime Type
text/x-diff
Expires
Wed, May 21, 19:57 (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865825
Default Alt Text
(41 KB)

Event Timeline