diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index d4c75a3192bc1..e8851f44fbd63 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -374,6 +376,12 @@ const CBlockIndex* cast_const_block_index(const kernel_BlockIndex* index) return reinterpret_cast(index); } +const CBlockUndo* cast_const_block_undo(const kernel_BlockUndo* undo) +{ + assert(undo); + return reinterpret_cast(undo); +} + } // namespace kernel_Transaction* kernel_transaction_create(const unsigned char* raw_transaction, size_t raw_transaction_len) @@ -400,6 +408,19 @@ kernel_ScriptPubkey* kernel_script_pubkey_create(const unsigned char* script_pub return reinterpret_cast(script_pubkey); } +kernel_ByteArray* kernel_copy_script_pubkey_data(const kernel_ScriptPubkey* script_pubkey_) +{ + auto script_pubkey{cast_script_pubkey(script_pubkey_)}; + + auto byte_array{new kernel_ByteArray{ + .data = new unsigned char[script_pubkey->size()], + .size = script_pubkey->size(), + }}; + + std::memcpy(byte_array->data, script_pubkey->data(), byte_array->size); + return byte_array; +} + void kernel_script_pubkey_destroy(kernel_ScriptPubkey* script_pubkey) { if (script_pubkey) { @@ -982,12 +1003,85 @@ kernel_Block* kernel_read_block_from_disk(const kernel_Context* context_, return reinterpret_cast(block); } +kernel_BlockUndo* kernel_read_block_undo_from_disk(const kernel_Context* context_, + kernel_ChainstateManager* chainman_, + const kernel_BlockIndex* block_index_) +{ + auto chainman{cast_chainstate_manager(chainman_)}; + const auto block_index{cast_const_block_index(block_index_)}; + + if (block_index->nHeight < 1) { + LogDebug(BCLog::KERNEL, "The genesis block does not have undo data."); + return nullptr; + } + auto block_undo{new CBlockUndo{}}; + if (!chainman->m_blockman.UndoReadFromDisk(*block_undo, *block_index)) { + LogError("Failed to read undo data from disk."); + return nullptr; + } + return reinterpret_cast(block_undo); +} + void kernel_block_index_destroy(kernel_BlockIndex* block_index) { // This is just a dummy function. The user does not control block index memory. return; } +uint64_t kernel_block_undo_size(const kernel_BlockUndo* block_undo_) +{ + const auto block_undo{cast_const_block_undo(block_undo_)}; + return block_undo->vtxundo.size(); +} + +void kernel_block_undo_destroy(kernel_BlockUndo* block_undo) +{ + if (block_undo) { + delete cast_const_block_undo(block_undo); + } +} + +uint64_t kernel_get_transaction_undo_size(const kernel_BlockUndo* block_undo_, uint64_t transaction_undo_index) +{ + const auto block_undo{cast_const_block_undo(block_undo_)}; + return block_undo->vtxundo[transaction_undo_index].vprevout.size(); +} + +kernel_TransactionOutput* kernel_get_undo_output_by_index(const kernel_BlockUndo* block_undo_, + uint64_t transaction_undo_index, + uint64_t output_index) +{ + const auto block_undo{cast_const_block_undo(block_undo_)}; + + if (transaction_undo_index >= block_undo->vtxundo.size()) { + LogInfo("transaction undo index is out of bounds."); + return nullptr; + } + + const auto& tx_undo = block_undo->vtxundo[transaction_undo_index]; + + if (output_index >= tx_undo.vprevout.size()) { + LogInfo("previous output index is out of bounds."); + return nullptr; + } + + CTxOut* prevout{new CTxOut{tx_undo.vprevout.at(output_index).out}}; + return reinterpret_cast(prevout); +} + +kernel_ScriptPubkey* kernel_copy_script_pubkey_from_output(kernel_TransactionOutput* output_) +{ + auto output{cast_transaction_output(output_)}; + auto script_pubkey = new CScript{output->scriptPubKey}; + return reinterpret_cast(script_pubkey); +} + +int64_t kernel_get_transaction_output_amount(kernel_TransactionOutput* output_) +{ + auto output{cast_transaction_output(output_)}; + return output->nValue; +} + bool kernel_chainstate_manager_process_block( const kernel_Context* context_, kernel_ChainstateManager* chainman_, diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index f486f03509968..040f3e7bd0a5c 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -230,6 +230,18 @@ typedef struct kernel_BlockValidationState kernel_BlockValidationState; */ typedef struct kernel_ValidationInterface kernel_ValidationInterface; +/** + * Opaque data structure for holding a block undo struct. + * + * It holds all the previous outputs consumed by all transactions in a specific + * block. Internally it holds a nested vector. The top level vector has an entry + * for each transaction in a block (in order of the actual transactions of the + * block and minus the coinbase transaction). Each entry is in turn a vector of + * all the previous outputs of a transaction (in order of their corresponding + * inputs). + */ +typedef struct kernel_BlockUndo kernel_BlockUndo; + /** Current sync state passed to tip changed callbacks. */ typedef enum { kernel_INIT_REINDEX, @@ -454,6 +466,15 @@ kernel_ScriptPubkey* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_script_pubkey_creat const unsigned char* script_pubkey, size_t script_pubkey_len ) BITCOINKERNEL_ARG_NONNULL(1); +/** + * @brief Copies the script pubkey data into the returned byte array. + * @param[in] script_pubkey Non-null. + * @return The serialized script pubkey data. + */ +kernel_ByteArray* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_copy_script_pubkey_data( + const kernel_ScriptPubkey* script_pubkey +) BITCOINKERNEL_ARG_NONNULL(1); + /** * Destroy the script pubkey. */ @@ -477,6 +498,25 @@ kernel_TransactionOutput* kernel_transaction_output_create( int64_t amount ) BITCOINKERNEL_ARG_NONNULL(1); +/** + * @brief Copies the script pubkey of an output in the returned script pubkey + * opaque object. + * + * @param[in] transaction_output Non-null. + * @return The data for the output's script pubkey. + */ +kernel_ScriptPubkey* kernel_copy_script_pubkey_from_output(kernel_TransactionOutput* transaction_output +) BITCOINKERNEL_ARG_NONNULL(1); + +/** + * @brief Gets the amount associated with this transaction output + * + * @param[in] transaction_output Non-null. + * @return The amount. + */ +int64_t kernel_get_transaction_output_amount(kernel_TransactionOutput* transaction_output +) BITCOINKERNEL_ARG_NONNULL(1); + /** * Destroy the transaction output. */ @@ -1027,6 +1067,73 @@ void kernel_block_index_destroy(kernel_BlockIndex* block_index); ///@} +/** @name BlockUndo + * Functions for working with block undo data. + */ +///@{ + +/** + * @brief Reads the block undo data the passed in block index points to from + * disk and returns it. + * + * @param[in] context Non-null. + * @param[in] chainstate_manager Non-null. + * @param[in] block_index Non-null. + * @return The read out block undo data, or null on error. + */ +kernel_BlockUndo* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_read_block_undo_from_disk( + const kernel_Context* context, + kernel_ChainstateManager* chainstate_manager, + const kernel_BlockIndex* block_index +) BITCOINKERNEL_ARG_NONNULL(1, 2, 3); + +/** + * @brief Returns the number of transactions whose undo data is contained in + * block undo. + * + * @param[in] block_undo Non-null. + * @return The number of transaction undo data in the block undo. + */ +uint64_t BITCOINKERNEL_WARN_UNUSED_RESULT kernel_block_undo_size( + const kernel_BlockUndo* block_undo +) BITCOINKERNEL_ARG_NONNULL(1); + +/** + * @brief Returns the number of previous transaction outputs contained in the + * transaction undo data. + * + * @param[in] block_undo Non-null, the block undo data from which tx_undo was retrieved from. + * @param[in] transaction_undo_index The index of the transaction undo data within the block undo data. + * @return The number of previous transaction outputs in the transaction. + */ +uint64_t BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_transaction_undo_size( + const kernel_BlockUndo* block_undo, + uint64_t transaction_undo_index +) BITCOINKERNEL_ARG_NONNULL(1); + +/** + * @brief Return a transaction output contained in the transaction undo data of + * a block undo data at a certain index. + * + * @param[in] block_undo Non-null. + * @param[in] transaction_undo_index The index of the transaction undo data within the block undo data. + * @param[in] output_index The index of the to be retrieved transaction output within the + * transaction undo data. + * @return A transaction output pointer, or null on error. + */ +kernel_TransactionOutput* BITCOINKERNEL_WARN_UNUSED_RESULT kernel_get_undo_output_by_index( + const kernel_BlockUndo* block_undo, + uint64_t transaction_undo_index, + uint64_t output_index +) BITCOINKERNEL_ARG_NONNULL(1); + +/** + * Destroy the block undo data. + */ +void kernel_block_undo_destroy(kernel_BlockUndo* block_undo); + +///@} + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index 586438a7051ad..ac4afbd7c0bef 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -54,6 +54,19 @@ class ScriptPubkey { } + ScriptPubkey(kernel_ScriptPubkey* script_pubkey) noexcept + : m_script_pubkey{script_pubkey} + { + } + + std::vector GetScriptPubkeyData() const noexcept + { + auto serialized_data{kernel_copy_script_pubkey_data(m_script_pubkey.get())}; + std::vector vec{serialized_data->data, serialized_data->data + serialized_data->size}; + kernel_byte_array_destroy(serialized_data); + return vec; + } + /** Check whether this ScriptPubkey object is valid. */ explicit operator bool() const noexcept { return bool{m_script_pubkey}; } }; @@ -75,6 +88,24 @@ class TransactionOutput : m_transaction_output{kernel_transaction_output_create(script_pubkey.m_script_pubkey.get(), amount)} { } + + TransactionOutput(kernel_TransactionOutput* output) noexcept + : m_transaction_output{output} + { + } + + /** Check whether this TransactionOutput object is valid. */ + explicit operator bool() const noexcept { return bool{m_transaction_output}; } + + ScriptPubkey GetScriptPubkey() noexcept + { + return kernel_copy_script_pubkey_from_output(m_transaction_output.get()); + } + + int64_t GetOutputAmount() noexcept + { + return kernel_get_transaction_output_amount(m_transaction_output.get()); + } }; int verify_script(const ScriptPubkey& script_pubkey, @@ -465,6 +496,43 @@ class Block friend class ChainMan; }; +class BlockUndo +{ +private: + struct Deleter { + void operator()(kernel_BlockUndo* ptr) const + { + kernel_block_undo_destroy(ptr); + } + }; + + const std::unique_ptr m_block_undo; + +public: + const uint64_t m_size; + + BlockUndo(kernel_BlockUndo* block_undo) noexcept + : m_block_undo{block_undo}, + m_size{kernel_block_undo_size(block_undo)} + { + } + + BlockUndo(const BlockUndo&) = delete; + BlockUndo& operator=(const BlockUndo&) = delete; + + uint64_t GetTxOutSize(uint64_t index) const noexcept + { + return kernel_get_transaction_undo_size(m_block_undo.get(), index); + } + + TransactionOutput GetTxUndoPrevoutByIndex( + uint64_t tx_undo_index, + uint64_t tx_prevout_index) const noexcept + { + return TransactionOutput{kernel_get_undo_output_by_index(m_block_undo.get(), tx_undo_index, tx_prevout_index)}; + } +}; + class BlockIndex { private: @@ -552,6 +620,13 @@ class ChainMan return block; } + std::optional ReadBlockUndo(const BlockIndex& block_index) const noexcept + { + auto undo{kernel_read_block_undo_from_disk(m_context.m_context.get(), m_chainman, block_index.m_block_index.get())}; + if (!undo) return std::nullopt; + return undo; + } + ~ChainMan() { kernel_chainstate_manager_destroy(m_chainman, m_context.m_context.get()); diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index ed3459ed54e52..d074d29a6e187 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -494,8 +494,6 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory) assert(new_block == false); } -#include - void chainman_regtest_validation_test() { auto test_directory{TestDirectory{"regtest_test_bitcoin_kernel"}}; @@ -536,6 +534,16 @@ void chainman_regtest_validation_test() auto tip_2 = tip.GetPreviousBlockIndex().value(); auto read_block_2 = chainman->ReadBlock(tip_2).value(); assert(read_block_2.GetBlockData() == REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 2]); + + auto block_undo{chainman->ReadBlockUndo(tip)}; + assert(block_undo); + auto tx_undo_size = block_undo->GetTxOutSize(block_undo->m_size - 1); + auto output = block_undo->GetTxUndoPrevoutByIndex(block_undo->m_size - 1, tx_undo_size - 1); + assert(output); + assert(output.GetOutputAmount() == 100000000); + auto script_pubkey = output.GetScriptPubkey(); + assert(script_pubkey); + assert(script_pubkey.GetScriptPubkeyData().size() == 22); } void chainman_reindex_test(TestDirectory& test_directory)