Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14864471
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
41 KB
Subscribers
None
View Options
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 ¶ms = 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
Details
Attached
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)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment