Skip to content

Commit

Permalink
kernel: Add function to read block undo data from disk to C header
Browse files Browse the repository at this point in the history
This adds functions for reading the undo data from disk with a retrieved
block index entry. The undo data of a block contains all the spent
script pubkeys of all the transactions in a block.

In normal operations undo data is used during re-orgs. This data might
also be useful for building external indexes, or to scan for silent
payment transactions.

Internally the block undo data contains a vector of transaction undo
data which contains a vector of the spent outputs. For this reason, the
`kernel_get_block_undo_size(...)` function is added to the header for
retrieving the size of the transaction undo data vector, as well as the
`kernel_get_transaction_undo_size(...) function for retrieving the size
of each spent outputs vector contained within each transaction undo data
entry. With these two sizes the user can iterate through the undo data
by accessing the transaction outputs by their indeces with
`kernel_get_undo_output_by_index`. If an invalid index is passed in, the
`kernel_ERROR_OUT_OF_BOUNDS` error is returned again.

The returned `kernel_TransactionOutput` is entirely owned by the user
and may be destroyed with the `kernel_transaction_output_destroy(...)`
convenience function.
  • Loading branch information
TheCharlatan committed Jan 15, 2025
1 parent 4168163 commit 572e008
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 2 deletions.
94 changes: 94 additions & 0 deletions src/kernel/bitcoinkernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <kernel/bitcoinkernel.h>

#include <chain.h>
#include <coins.h>
#include <consensus/amount.h>
#include <consensus/validation.h>
#include <kernel/chainparams.h>
Expand All @@ -24,6 +25,7 @@
#include <sync.h>
#include <tinyformat.h>
#include <uint256.h>
#include <undo.h>
#include <util/fs.h>
#include <util/result.h>
#include <util/signalinterrupt.h>
Expand Down Expand Up @@ -374,6 +376,12 @@ const CBlockIndex* cast_const_block_index(const kernel_BlockIndex* index)
return reinterpret_cast<const CBlockIndex*>(index);
}

const CBlockUndo* cast_const_block_undo(const kernel_BlockUndo* undo)
{
assert(undo);
return reinterpret_cast<const CBlockUndo*>(undo);
}

} // namespace

kernel_Transaction* kernel_transaction_create(const unsigned char* raw_transaction, size_t raw_transaction_len)
Expand All @@ -400,6 +408,19 @@ kernel_ScriptPubkey* kernel_script_pubkey_create(const unsigned char* script_pub
return reinterpret_cast<kernel_ScriptPubkey*>(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) {
Expand Down Expand Up @@ -982,12 +1003,85 @@ kernel_Block* kernel_read_block_from_disk(const kernel_Context* context_,
return reinterpret_cast<kernel_Block*>(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<kernel_BlockUndo*>(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<kernel_TransactionOutput*>(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<kernel_ScriptPubkey*>(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_,
Expand Down
107 changes: 107 additions & 0 deletions src/kernel/bitcoinkernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions src/kernel/bitcoinkernel_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ class ScriptPubkey
{
}

ScriptPubkey(kernel_ScriptPubkey* script_pubkey) noexcept
: m_script_pubkey{script_pubkey}
{
}

std::vector<unsigned char> GetScriptPubkeyData() const noexcept
{
auto serialized_data{kernel_copy_script_pubkey_data(m_script_pubkey.get())};
std::vector<unsigned char> 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}; }
};
Expand All @@ -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,
Expand Down Expand Up @@ -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<kernel_BlockUndo, Deleter> 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:
Expand Down Expand Up @@ -552,6 +620,13 @@ class ChainMan
return block;
}

std::optional<BlockUndo> 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());
Expand Down
12 changes: 10 additions & 2 deletions src/test/kernel/test_kernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,6 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory)
assert(new_block == false);
}

#include <utility>

void chainman_regtest_validation_test()
{
auto test_directory{TestDirectory{"regtest_test_bitcoin_kernel"}};
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 572e008

Please sign in to comment.