Kernel: Add functions for working with outpoints

This introduces the transaction outpoint, input and id types. This now
allows a user to retrieve a transaction output from a prior transaction
that a transaction outpoint is pointing to.

This is exercised in the tests by verifying the script of every
transaction in the test chain.
This commit is contained in:
TheCharlatan 2025-09-19 22:14:54 +02:00
parent 00000cb55a
commit 7918a4d17d
No known key found for this signature in database
GPG Key ID: 9B79B45691DB4173
4 changed files with 415 additions and 0 deletions

View File

@ -498,6 +498,9 @@ struct btck_BlockSpentOutputs : Handle<btck_BlockSpentOutputs, std::shared_ptr<C
struct btck_TransactionSpentOutputs : Handle<btck_TransactionSpentOutputs, CTxUndo> {}; struct btck_TransactionSpentOutputs : Handle<btck_TransactionSpentOutputs, CTxUndo> {};
struct btck_Coin : Handle<btck_Coin, Coin> {}; struct btck_Coin : Handle<btck_Coin, Coin> {};
struct btck_BlockHash : Handle<btck_BlockHash, uint256> {}; struct btck_BlockHash : Handle<btck_BlockHash, uint256> {};
struct btck_TransactionInput : Handle<btck_TransactionInput, CTxIn> {};
struct btck_TransactionOutPoint: Handle<btck_TransactionOutPoint, COutPoint> {};
struct btck_Txid: Handle<btck_Txid, Txid> {};
btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len) btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len)
{ {
@ -526,6 +529,17 @@ size_t btck_transaction_count_inputs(const btck_Transaction* transaction)
return btck_Transaction::get(transaction)->vin.size(); return btck_Transaction::get(transaction)->vin.size();
} }
const btck_TransactionInput* btck_transaction_get_input_at(const btck_Transaction* transaction, size_t input_index)
{
assert(input_index < btck_Transaction::get(transaction)->vin.size());
return btck_TransactionInput::ref(&btck_Transaction::get(transaction)->vin[input_index]);
}
const btck_Txid* btck_transaction_get_txid(const btck_Transaction* transaction)
{
return btck_Txid::ref(&btck_Transaction::get(transaction)->GetHash());
}
btck_Transaction* btck_transaction_copy(const btck_Transaction* transaction) btck_Transaction* btck_transaction_copy(const btck_Transaction* transaction)
{ {
return btck_Transaction::copy(transaction); return btck_Transaction::copy(transaction);
@ -642,6 +656,61 @@ int btck_script_pubkey_verify(const btck_ScriptPubkey* script_pubkey,
return result ? 1 : 0; return result ? 1 : 0;
} }
btck_TransactionInput* btck_transaction_input_copy(const btck_TransactionInput* input)
{
return btck_TransactionInput::copy(input);
}
const btck_TransactionOutPoint* btck_transaction_input_get_out_point(const btck_TransactionInput* input)
{
return btck_TransactionOutPoint::ref(&btck_TransactionInput::get(input).prevout);
}
void btck_transaction_input_destroy(btck_TransactionInput* input)
{
delete input;
}
btck_TransactionOutPoint* btck_transaction_out_point_copy(const btck_TransactionOutPoint* out_point)
{
return btck_TransactionOutPoint::copy(out_point);
}
uint32_t btck_transaction_out_point_get_index(const btck_TransactionOutPoint* out_point)
{
return btck_TransactionOutPoint::get(out_point).n;
}
const btck_Txid* btck_transaction_out_point_get_txid(const btck_TransactionOutPoint* out_point)
{
return btck_Txid::ref(&btck_TransactionOutPoint::get(out_point).hash);
}
void btck_transaction_out_point_destroy(btck_TransactionOutPoint* out_point)
{
delete out_point;
}
btck_Txid* btck_txid_copy(const btck_Txid* txid)
{
return btck_Txid::copy(txid);
}
void btck_txid_to_bytes(const btck_Txid* txid, unsigned char output[32])
{
std::memcpy(output, btck_Txid::get(txid).begin(), 32);
}
int btck_txid_equals(const btck_Txid* txid1, const btck_Txid* txid2)
{
return btck_Txid::get(txid1) == btck_Txid::get(txid2);
}
void btck_txid_destroy(btck_Txid* txid)
{
delete txid;
}
void btck_logging_set_level_category(btck_LogCategory category, btck_LogLevel level) void btck_logging_set_level_category(btck_LogCategory category, btck_LogLevel level)
{ {
LOCK(cs_main); LOCK(cs_main);

View File

@ -248,6 +248,22 @@ typedef struct btck_Coin btck_Coin;
*/ */
typedef struct btck_BlockHash btck_BlockHash; typedef struct btck_BlockHash btck_BlockHash;
/**
* Opaque data structure for holding a transaction input.
*
* Holds information on the @ref btck_TransactionOutPoint held within.
*/
typedef struct btck_TransactionInput btck_TransactionInput;
/**
* Opaque data structure for holding a transaction out point.
*
* Holds the transaction id and output index it is pointing to.
*/
typedef struct btck_TransactionOutPoint btck_TransactionOutPoint;
typedef struct btck_Txid btck_Txid;
/** Current sync state passed to tip changed callbacks. */ /** Current sync state passed to tip changed callbacks. */
typedef uint8_t btck_SynchronizationState; typedef uint8_t btck_SynchronizationState;
#define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0)) #define btck_SynchronizationState_INIT_REINDEX ((btck_SynchronizationState)(0))
@ -493,6 +509,18 @@ BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_count
BITCOINKERNEL_API const btck_TransactionOutput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_get_output_at( BITCOINKERNEL_API const btck_TransactionOutput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_get_output_at(
const btck_Transaction* transaction, size_t output_index) BITCOINKERNEL_ARG_NONNULL(1); const btck_Transaction* transaction, size_t output_index) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the transaction input at the provided index. The returned
* transaction input is not owned and depends on the lifetime of the
* transaction.
*
* @param[in] transaction Non-null.
* @param[in] input_index The index of the transaction input to be retrieved.
* @return The transaction input
*/
BITCOINKERNEL_API const btck_TransactionInput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_get_input_at(
const btck_Transaction* transaction, size_t input_index) BITCOINKERNEL_ARG_NONNULL(1);
/** /**
* @brief Get the number of inputs of a transaction. * @brief Get the number of inputs of a transaction.
* *
@ -502,6 +530,15 @@ BITCOINKERNEL_API const btck_TransactionOutput* BITCOINKERNEL_WARN_UNUSED_RESULT
BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_count_inputs( BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_count_inputs(
const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1); const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the txid of a transaction.
*
* @param[in] transaction Non-null.
* @return The txid.
*/
BITCOINKERNEL_API const btck_Txid* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_get_txid(
const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1);
/** /**
* Destroy the transaction. * Destroy the transaction.
*/ */
@ -1292,6 +1329,118 @@ BITCOINKERNEL_API void btck_transaction_spent_outputs_destroy(btck_TransactionSp
///@} ///@}
/** @name Transaction Input
* Functions for working with transaction inputs.
*/
///@{
/**
* @brief Copy a transaction input.
*
* @param[in] transaction_input Non-null.
* @return The copied transaction input.
*/
BITCOINKERNEL_API btck_TransactionInput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_input_copy(
const btck_TransactionInput* transaction_input) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the transaction out point. The returned transaction out point is
* not owned and depends on the lifetime of the transaction.
*
* @param[in] transaction_input Non-null.
* @return The transaction out point.
*/
BITCOINKERNEL_API const btck_TransactionOutPoint* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_input_get_out_point(
const btck_TransactionInput* transaction_input) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Destroy the transaction input.
*/
BITCOINKERNEL_API void btck_transaction_input_destroy(btck_TransactionInput* transaction_input);
///@}
/** @name Transaction Out Point
* Functions for working with transaction out points.
*/
///@{
/**
* @brief Copy a transaction out point.
*
* @param[in] transaction_out_point Non-null.
* @return The copied transaction out point.
*/
BITCOINKERNEL_API btck_TransactionOutPoint* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_out_point_copy(
const btck_TransactionOutPoint* transaction_out_point) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the output position from the out point.
*
* @param[in] transaction_out_point Non-null.
* @return The output index.
*/
BITCOINKERNEL_API uint32_t btck_transaction_out_point_get_index(
const btck_TransactionOutPoint* transaction_out_point) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Get the txid from the out point.
*
* @param[in] transaction_out_point Non-null.
* @return The txid.
*/
BITCOINKERNEL_API const btck_Txid* btck_transaction_out_point_get_txid(
const btck_TransactionOutPoint* transaction_out_point) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Destroy the transaction out point.
*/
BITCOINKERNEL_API void btck_transaction_out_point_destroy(btck_TransactionOutPoint* transaction_out_point);
///@}
/** @name Txid
* Functions for working with txids.
*/
///@{
/**
* @brief Copy a txid.
*
* @param[in] txid Non-null.
* @return The copied txid.
*/
BITCOINKERNEL_API btck_Txid* BITCOINKERNEL_WARN_UNUSED_RESULT btck_txid_copy(
const btck_Txid* txid) BITCOINKERNEL_ARG_NONNULL(1);
/**
* @brief Check if two txids are equal.
*
* @param[in] txid1 Non-null.
* @param[in] txid2 Non-null.
* @return 0 if the txid is not equal.
*/
BITCOINKERNEL_API int btck_txid_equals(
const btck_Txid* txid1, const btck_Txid* txid2) BITCOINKERNEL_ARG_NONNULL(1, 2);
/**
* @brief Serializes the txid to bytes.
*
* @param[in] txid Non-null.
* @param[out] output The serialized txid.
*/
BITCOINKERNEL_API void btck_txid_to_bytes(
const btck_Txid* txid, unsigned char output[32]) BITCOINKERNEL_ARG_NONNULL(1, 2);
/**
* Destroy the txid.
*/
BITCOINKERNEL_API void btck_txid_destroy(btck_Txid* txid);
///@}
///@}
/** @name Coin /** @name Coin
* Functions for working with coins. * Functions for working with coins.
*/ */

View File

@ -447,6 +447,119 @@ public:
: Handle(view) {} : Handle(view) {}
}; };
template <typename Derived>
class TxidApi
{
private:
auto impl() const
{
return static_cast<const Derived*>(this)->get();
}
friend Derived;
TxidApi() = default;
public:
bool operator==(const TxidApi& other) const
{
return btck_txid_equals(impl(), other.impl()) != 0;
}
bool operator!=(const TxidApi& other) const
{
return btck_txid_equals(impl(), other.impl()) == 0;
}
std::array<std::byte, 32> ToBytes() const
{
std::array<std::byte, 32> hash;
btck_txid_to_bytes(impl(), reinterpret_cast<unsigned char*>(hash.data()));
return hash;
}
};
class TxidView : public View<btck_Txid>, public TxidApi<TxidView>
{
public:
explicit TxidView(const btck_Txid* ptr) : View{ptr} {}
};
class Txid : public Handle<btck_Txid, btck_txid_copy, btck_txid_destroy>, public TxidApi<Txid>
{
public:
Txid(const TxidView& view)
: Handle(view) {}
};
template <typename Derived>
class OutPointApi
{
private:
auto impl() const
{
return static_cast<const Derived*>(this)->get();
}
friend Derived;
OutPointApi() = default;
public:
uint32_t index() const
{
return btck_transaction_out_point_get_index(impl());
}
TxidView Txid() const
{
return TxidView{btck_transaction_out_point_get_txid(impl())};
}
};
class OutPointView : public View<btck_TransactionOutPoint>, public OutPointApi<OutPointView>
{
public:
explicit OutPointView(const btck_TransactionOutPoint* ptr) : View{ptr} {}
};
class OutPoint : public Handle<btck_TransactionOutPoint, btck_transaction_out_point_copy, btck_transaction_out_point_destroy>, public OutPointApi<OutPoint>
{
public:
OutPoint(const OutPointView& view)
: Handle(view) {}
};
template <typename Derived>
class TransactionInputApi
{
private:
auto impl() const
{
return static_cast<const Derived*>(this)->get();
}
friend Derived;
TransactionInputApi() = default;
public:
OutPointView OutPoint() const
{
return OutPointView{btck_transaction_input_get_out_point(impl())};
}
};
class TransactionInputView : public View<btck_TransactionInput>, public TransactionInputApi<TransactionInputView>
{
public:
explicit TransactionInputView(const btck_TransactionInput* ptr) : View{ptr} {}
};
class TransactionInput : public Handle<btck_TransactionInput, btck_transaction_input_copy, btck_transaction_input_destroy>, public TransactionInputApi<TransactionInput>
{
public:
TransactionInput(const TransactionInputView& view)
: Handle(view) {}
};
template <typename Derived> template <typename Derived>
class TransactionApi class TransactionApi
{ {
@ -472,11 +585,26 @@ public:
return TransactionOutputView{btck_transaction_get_output_at(impl(), index)}; return TransactionOutputView{btck_transaction_get_output_at(impl(), index)};
} }
TransactionInputView GetInput(size_t index) const
{
return TransactionInputView{btck_transaction_get_input_at(impl(), index)};
}
TxidView Txid() const
{
return TxidView{btck_transaction_get_txid(impl())};
}
auto Outputs() const auto Outputs() const
{ {
return Range<Derived, &TransactionApi<Derived>::CountOutputs, &TransactionApi<Derived>::GetOutput>{*static_cast<const Derived*>(this)}; return Range<Derived, &TransactionApi<Derived>::CountOutputs, &TransactionApi<Derived>::GetOutput>{*static_cast<const Derived*>(this)};
} }
auto Inputs() const
{
return Range<Derived, &TransactionApi<Derived>::CountInputs, &TransactionApi<Derived>::GetInput>{*static_cast<const Derived*>(this)};
}
std::vector<std::byte> ToBytes() const std::vector<std::byte> ToBytes() const
{ {
return write_bytes(impl(), btck_transaction_to_bytes); return write_bytes(impl(), btck_transaction_to_bytes);

View File

@ -14,6 +14,7 @@
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
#include <filesystem> #include <filesystem>
#include <format>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <optional> #include <optional>
@ -61,6 +62,19 @@ std::vector<std::byte> hex_string_to_byte_vec(std::string_view hex)
return bytes; return bytes;
} }
std::string byte_span_to_hex_string_reversed(std::span<const std::byte> bytes)
{
std::ostringstream oss;
// Iterate in reverse order
for (auto it = bytes.rbegin(); it != bytes.rend(); ++it) {
oss << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<unsigned int>(static_cast<uint8_t>(*it));
}
return oss.str();
}
constexpr auto VERIFY_ALL_PRE_SEGWIT{ScriptVerificationFlags::P2SH | ScriptVerificationFlags::DERSIG | constexpr auto VERIFY_ALL_PRE_SEGWIT{ScriptVerificationFlags::P2SH | ScriptVerificationFlags::DERSIG |
ScriptVerificationFlags::NULLDUMMY | ScriptVerificationFlags::CHECKLOCKTIMEVERIFY | ScriptVerificationFlags::NULLDUMMY | ScriptVerificationFlags::CHECKLOCKTIMEVERIFY |
ScriptVerificationFlags::CHECKSEQUENCEVERIFY}; ScriptVerificationFlags::CHECKSEQUENCEVERIFY};
@ -476,6 +490,18 @@ BOOST_AUTO_TEST_CASE(btck_transaction_output)
CheckHandle(output, output2); CheckHandle(output, output2);
} }
BOOST_AUTO_TEST_CASE(btck_transaction_input)
{
Transaction tx{hex_string_to_byte_vec("020000000248c03e66fd371c7033196ce24298628e59ebefa00363026044e0f35e0325a65d000000006a473044022004893432347f39beaa280e99da595681ddb20fc45010176897e6e055d716dbfa022040a9e46648a5d10c33ef7cee5e6cf4b56bd513eae3ae044f0039824b02d0f44c012102982331a52822fd9b62e9b5d120da1d248558fac3da3a3c51cd7d9c8ad3da760efeffffffb856678c6e4c3c84e39e2ca818807049d6fba274b42af3c6d3f9d4b6513212d2000000006a473044022068bcedc7fe39c9f21ad318df2c2da62c2dc9522a89c28c8420ff9d03d2e6bf7b0220132afd752754e5cb1ea2fd0ed6a38ec666781e34b0e93dc9a08f2457842cf5660121033aeb9c079ea3e08ea03556182ab520ce5c22e6b0cb95cee6435ee17144d860cdfeffffff0260d50b00000000001976a914363cc8d55ea8d0500de728ef6d63804ddddbdc9888ac67040f00000000001976a914c303bdc5064bf9c9a8b507b5496bd0987285707988ac6acb0700")};
TransactionInput input_0 = tx.GetInput(0);
TransactionInput input_1 = tx.GetInput(1);
CheckHandle(input_0, input_1);
CheckRange(tx.Inputs(), tx.CountInputs());
OutPoint point_0 = input_0.OutPoint();
OutPoint point_1 = input_1.OutPoint();
CheckHandle(point_0, point_1);
}
BOOST_AUTO_TEST_CASE(btck_script_verify_tests) BOOST_AUTO_TEST_CASE(btck_script_verify_tests)
{ {
// Legacy transaction aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d // Legacy transaction aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d
@ -731,6 +757,7 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory)
auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"); auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000");
Block block{raw_block}; Block block{raw_block};
TransactionView tx{block.GetTransaction(block.CountTransactions() - 1)}; TransactionView tx{block.GetTransaction(block.CountTransactions() - 1)};
BOOST_CHECK_EQUAL(byte_span_to_hex_string_reversed(tx.Txid().ToBytes()), "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
BOOST_CHECK_EQUAL(tx.CountInputs(), 1); BOOST_CHECK_EQUAL(tx.CountInputs(), 1);
Transaction tx2 = tx; Transaction tx2 = tx;
BOOST_CHECK_EQUAL(tx2.CountInputs(), 1); BOOST_CHECK_EQUAL(tx2.CountInputs(), 1);
@ -869,6 +896,48 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
auto read_block_2 = chainman->ReadBlock(tip_2).value(); auto read_block_2 = chainman->ReadBlock(tip_2).value();
check_equal(read_block_2.ToBytes(), as_bytes(REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 2])); check_equal(read_block_2.ToBytes(), as_bytes(REGTEST_BLOCK_DATA[REGTEST_BLOCK_DATA.size() - 2]));
Txid txid = read_block.Transactions()[0].Txid();
Txid txid_2 = read_block_2.Transactions()[0].Txid();
BOOST_CHECK(txid != txid_2);
BOOST_CHECK(txid == txid);
CheckHandle(txid, txid_2);
auto find_transaction = [&chainman](const TxidView& target_txid) -> std::optional<Transaction> {
for (const auto block_tree_entry : chainman->GetChain().Entries()) {
auto block{chainman->ReadBlock(block_tree_entry)};
for (const auto transaction : block->Transactions()) {
if (transaction.Txid() == target_txid) {
return transaction;
}
}
}
return std::nullopt;
};
for (const auto block_tree_entry : chainman->GetChain().Entries()) {
auto block{chainman->ReadBlock(block_tree_entry)};
for (const auto transaction : block->Transactions()) {
std::vector<TransactionInput> inputs;
std::vector<TransactionOutput> spent_outputs;
for (const auto input : transaction.Inputs()) {
OutPointView point = input.OutPoint();
if (point.index() == std::numeric_limits<uint32_t>::max()) {
continue;
}
inputs.emplace_back(input);
BOOST_CHECK(point.Txid() != transaction.Txid());
Transaction tx = *find_transaction(point.Txid());
BOOST_CHECK(point.Txid() == tx.Txid());
spent_outputs.emplace_back(tx.GetOutput(point.index()));
}
BOOST_CHECK(inputs.size() == spent_outputs.size());
ScriptVerifyStatus status = ScriptVerifyStatus::OK;
for (size_t i{0}; i < inputs.size(); ++i) {
BOOST_CHECK(spent_outputs[i].GetScriptPubkey().Verify(spent_outputs[i].Amount(), transaction, spent_outputs, i, ScriptVerificationFlags::ALL, status));
}
}
}
BlockSpentOutputs block_spent_outputs{chainman->ReadBlockSpentOutputs(tip)}; BlockSpentOutputs block_spent_outputs{chainman->ReadBlockSpentOutputs(tip)};
BlockSpentOutputs block_spent_outputs_prev{chainman->ReadBlockSpentOutputs(*tip.GetPrevious())}; BlockSpentOutputs block_spent_outputs_prev{chainman->ReadBlockSpentOutputs(*tip.GetPrevious())};
CheckHandle(block_spent_outputs, block_spent_outputs_prev); CheckHandle(block_spent_outputs, block_spent_outputs_prev);