diff --git a/chronik/CMakeLists.txt b/chronik/CMakeLists.txt --- a/chronik/CMakeLists.txt +++ b/chronik/CMakeLists.txt @@ -108,6 +108,7 @@ ) target_link_libraries(chronik-bridge util + leveldb ) # Chronik library 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 @@ -9,8 +9,27 @@ #[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, + } + + #[allow(missing_debug_implementations)] unsafe extern "C++" { include!("chronik-cpp/chronik_bridge.h"); + include!("node/context.h"); + + /// ::NodeContext from node/context.h + #[namespace = ""] + type NodeContext; + + /// Bridge to bitcoind to access the node + type ChronikBridge; /// Print the message to bitcoind's logs. fn log_print( @@ -28,5 +47,12 @@ source_line: u32, msg: &str, ); + + /// Make the bridge given the NodeContext + fn make_bridge(node: &NodeContext) -> UniquePtr; + + /// 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) -> BlockInfo; } } 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 @@ -5,11 +5,18 @@ #ifndef BITCOIN_CHRONIK_CPP_CHRONIK_BRIDGE_H #define BITCOIN_CHRONIK_CPP_CHRONIK_BRIDGE_H -#include +#include #include +struct NodeContext; +class uint256; + +std::array HashToArray(const uint256 &hash); + namespace chronik_bridge { +struct BlockInfo; + void log_print(const rust::Str logging_function, const rust::Str source_file, const uint32_t source_line, const rust::Str msg); @@ -17,6 +24,20 @@ const rust::Str source_file, const uint32_t source_line, const rust::Str msg); +/** + * Bridge to bitcoind to access the node. + */ +class ChronikBridge { + const NodeContext &m_node; + +public: + ChronikBridge(const NodeContext &node) : m_node(node) {} + + BlockInfo get_chain_tip() const; +}; + +std::unique_ptr make_bridge(const NodeContext &node); + } // namespace chronik_bridge #endif // BITCOIN_CHRONIK_CPP_CHRONIK_BRIDGE_H 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 @@ -2,8 +2,18 @@ // 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 + +std::array HashToArray(const uint256 &hash) { + std::array array; + std::copy_n(hash.begin(), 32, array.begin()); + return array; +} namespace chronik_bridge { @@ -21,4 +31,23 @@ } } +BlockInfo ChronikBridge::get_chain_tip() const { + const CBlockIndex *tip = + WITH_LOCK(cs_main, return m_node.chainman->ActiveTip()); + if (tip == nullptr) { + return { + .hash = {}, + .height = -1, + }; + } + return { + .hash = HashToArray(tip->GetBlockHash()), + .height = tip->nHeight, + }; +} + +std::unique_ptr make_bridge(const NodeContext &node) { + return std::make_unique(node); +} + } // namespace chronik_bridge diff --git a/chronik/test/chronikbridge_tests.cpp b/chronik/test/chronikbridge_tests.cpp new file mode 100644 --- /dev/null +++ b/chronik/test/chronikbridge_tests.cpp @@ -0,0 +1,54 @@ +// 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 + +BOOST_AUTO_TEST_SUITE(chronikbridge_tests) + +BOOST_FIXTURE_TEST_CASE(test_get_chain_tip_empty, ChainTestingSetup) { + // Setup chainstate + { + LOCK(::cs_main); + m_node.chainman->InitializeChainstate(*m_node.mempool); + } + // Chain has no blocks yet: + // get_chain_tip returns hash=000...000, height=-1 + const chronik_bridge::ChronikBridge bridge(m_node); + chronik_bridge::BlockInfo block = bridge.get_chain_tip(); + chronik_bridge::BlockInfo expected_block{.hash = {}, .height = -1}; + BOOST_CHECK(block == expected_block); +} + +BOOST_FIXTURE_TEST_CASE(test_get_chain_tip_genesis, TestingSetup) { + const chronik_bridge::ChronikBridge bridge(m_node); + // Check for genesis block + chronik_bridge::BlockInfo block = bridge.get_chain_tip(); + chronik_bridge::BlockInfo expected_block{ + .hash = + HashToArray(GetConfig().GetChainParams().GenesisBlock().GetHash()), + .height = 0}; + BOOST_CHECK(block == expected_block); +} + +BOOST_FIXTURE_TEST_CASE(test_get_chain_tip_100, TestChain100Setup) { + // Generate new block (at height 101) + CBlock tip_block = CreateAndProcessBlock( + {}, CScript() << std::vector(33) << OP_CHECKSIG); + const chronik_bridge::ChronikBridge bridge(m_node); + // Check if block is 101th + chronik_bridge::BlockInfo block = bridge.get_chain_tip(); + chronik_bridge::BlockInfo expected_block{ + .hash = HashToArray(tip_block.GetHash()), .height = 101}; + BOOST_CHECK(block == expected_block); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -101,6 +101,12 @@ data/asmap.raw ) +if(BUILD_BITCOIN_CHRONIK) + set(BITCOIN_CHRONIK_TESTS + ../../chronik/test/chronikbridge_tests.cpp + ) +endif() + add_boost_unit_tests_to_suite(bitcoin test_bitcoin fixture.cpp jsonutil.cpp @@ -235,6 +241,9 @@ # Wallet tests ${BITCOIN_WALLET_TESTS} + + # Chronik tests + ${BITCOIN_CHRONIK_TESTS} ) function(add_boost_test_runners_with_upgrade_activated SUITE EXECUTABLE)