Skip to content
10 changes: 10 additions & 0 deletions src/kernel/bitcoinkernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,11 @@ const btck_TransactionInput* btck_transaction_get_input_at(const btck_Transactio
return btck_TransactionInput::ref(&btck_Transaction::get(transaction)->vin[input_index]);
}

uint32_t btck_transaction_get_locktime(const btck_Transaction* transaction)
{
return btck_Transaction::get(transaction)->nLockTime;
}

const btck_Txid* btck_transaction_get_txid(const btck_Transaction* transaction)
{
return btck_Txid::ref(&btck_Transaction::get(transaction)->GetHash());
Expand Down Expand Up @@ -692,6 +697,11 @@ const btck_TransactionOutPoint* btck_transaction_input_get_out_point(const btck_
return btck_TransactionOutPoint::ref(&btck_TransactionInput::get(input).prevout);
}

uint32_t btck_transaction_input_get_sequence(const btck_TransactionInput* input)
{
return btck_TransactionInput::get(input).nSequence;
}

void btck_transaction_input_destroy(btck_TransactionInput* input)
{
delete input;
Expand Down
18 changes: 18 additions & 0 deletions src/kernel/bitcoinkernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,15 @@ BITCOINKERNEL_API const btck_TransactionInput* BITCOINKERNEL_WARN_UNUSED_RESULT
BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_count_inputs(
const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1);

/**
* @brief Get a transaction's nLockTime value.
*
* @param[in] transaction Non-null.
* @return The nLockTime value.
*/
BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_get_locktime(
const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1);

/**
* @brief Get the txid of a transaction. The returned txid is not owned and
* depends on the lifetime of the transaction.
Expand Down Expand Up @@ -1505,6 +1514,15 @@ BITCOINKERNEL_API btck_TransactionInput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_t
BITCOINKERNEL_API const btck_TransactionOutPoint* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_input_get_out_point(
const btck_TransactionInput* transaction_input) BITCOINKERNEL_ARG_NONNULL(1);

/**
* @brief Get a transaction input's nSequence value.
*
* @param[in] transaction_input Non-null.
* @return The nSequence value.
*/
BITCOINKERNEL_API uint32_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_input_get_sequence(
const btck_TransactionInput* transaction_input) BITCOINKERNEL_ARG_NONNULL(1);

/**
* Destroy the transaction input.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/kernel/bitcoinkernel_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,11 @@ class TransactionInputApi
{
return OutPointView{btck_transaction_input_get_out_point(impl())};
}

uint32_t GetSequence() const
{
return btck_transaction_input_get_sequence(impl());
}
};

class TransactionInputView : public View<btck_TransactionInput>, public TransactionInputApi<TransactionInputView>
Expand Down Expand Up @@ -596,6 +601,11 @@ class TransactionApi
return TransactionInputView{btck_transaction_get_input_at(impl(), index)};
}

uint32_t GetLocktime() const
{
return btck_transaction_get_locktime(impl());
}

TxidView Txid() const
{
return TxidView{btck_transaction_get_txid(impl())};
Expand Down
5 changes: 4 additions & 1 deletion src/script/miniscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,9 @@ class Node
// Destroy the subexpressions iteratively after moving out their
// subexpressions to avoid a stack-overflow due to recursive calls to
// the subs' destructors.
// We move vectors in order to only update array-pointers inside them
// rather than moving individual Node instances which would involve
// moving/copying each Node field.
std::vector<std::vector<Node>> queue;
queue.push_back(std::move(subs));
do {
Expand Down Expand Up @@ -607,7 +610,7 @@ class Node
// This is kept private as no valid fragment has all of these arguments.
// Only used by Clone()
Node(internal::NoDupCheck, MiniscriptContext script_ctx, enum Fragment nt, std::vector<Node> sub, std::vector<Key> key, std::vector<unsigned char> arg, uint32_t val)
: fragment(nt), k(val), keys(key), data(std::move(arg)), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
: fragment(nt), k(val), keys(std::move(key)), data(std::move(arg)), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}

//! Compute the length of the script for this miniscript (including children).
size_t CalcScriptLen() const
Expand Down
3 changes: 3 additions & 0 deletions src/test/kernel/test_kernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,11 @@ BOOST_AUTO_TEST_CASE(btck_transaction_tests)

BOOST_CHECK_EQUAL(tx.CountOutputs(), 2);
BOOST_CHECK_EQUAL(tx.CountInputs(), 1);
BOOST_CHECK_EQUAL(tx.GetLocktime(), 510826);
auto broken_tx_data{std::span<std::byte>{tx_data.begin(), tx_data.begin() + 10}};
BOOST_CHECK_THROW(Transaction{broken_tx_data}, std::runtime_error);
auto input{tx.GetInput(0)};
BOOST_CHECK_EQUAL(input.GetSequence(), 0xfffffffe);
auto output{tx.GetOutput(tx.CountOutputs() - 1)};
BOOST_CHECK_EQUAL(output.Amount(), 42130042);
auto script_pubkey{output.GetScriptPubkey()};
Expand Down
27 changes: 21 additions & 6 deletions src/test/miniscript_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,24 +727,39 @@ BOOST_AUTO_TEST_CASE(fixed_tests)
}

// Confirm that ~Node(), Node::Clone() and operator=(Node&&) are stack-safe.
BOOST_AUTO_TEST_CASE(node_deep_destruct)
BOOST_AUTO_TEST_CASE(node_stress_stack)
{
using miniscript::internal::NoDupCheck;
using miniscript::Fragment;
using NodeU32 = miniscript::Node<uint32_t>;

constexpr auto ctx{miniscript::MiniscriptContext::P2WSH};
const auto compute_depth{[] (const NodeU32& node) -> size_t {
size_t depth{0};
for (const auto* n{&node}; !n->Subs().empty(); n = &n->Subs().front()) {
++depth;
}
return depth;
}};

constexpr auto ctx{miniscript::MiniscriptContext::TAPSCRIPT};
NodeU32 root{NoDupCheck{}, ctx, Fragment::JUST_1};
for (uint32_t i{0}; i < 200'000; ++i) {
root = NodeU32{NoDupCheck{}, ctx, Fragment::WRAP_S, Vector(std::move(root))};
// Some CI jobs run with CI_LIMIT_STACK_SIZE which reduces the stack size
// via ulimit to 512 kbytes. When tested with ~Node()=default (stack-unsafe)
// implementations the test has been shown to fail for the below depth.
// The test may pass locally despite stack-unsafe implementations unless the
// stack is reduced in a similar way or the depth is temporarily increased.
constexpr size_t depth{200'000};
for (size_t i{0}; i < depth; ++i) {
root = NodeU32{NoDupCheck{}, ctx, Fragment::WRAP_N, Vector(std::move(root))};
}
BOOST_CHECK_EQUAL(root.ScriptSize(), 200'001);
BOOST_CHECK(root.IsValid());
BOOST_CHECK_EQUAL(compute_depth(root), depth);

auto clone{root.Clone()};
BOOST_CHECK_EQUAL(clone.ScriptSize(), root.ScriptSize());
BOOST_CHECK_EQUAL(compute_depth(clone), depth);

clone = std::move(root);
BOOST_CHECK_EQUAL(compute_depth(clone), depth);
}

BOOST_AUTO_TEST_SUITE_END()
Loading