Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 61 additions & 27 deletions include/bitcoin/database/impl/query/address/address_history.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <atomic>
#include <algorithm>
#include <ranges>
#include <utility>
#include <bitcoin/database/define.hpp>

Expand Down Expand Up @@ -143,37 +144,70 @@ code CLASS::get_history(const stopper& cancel, histories& out,
if (cancel || fail)
return history{};

auto hash = get_tx_key(link);
if (hash == system::null_hash)
{
const auto out = get_tx_history(link);
if (!out.valid())
fail = true;
return history{};
}

uint64_t fee{};
if (!get_tx_fee(fee, link))
fee = history::missing_prevout;
return out;
});
}

auto height = history::unrooted_height;
auto position = history::unconfirmed_position;
if (const auto block = find_strong(link);
is_confirmed_block(block))
{
if (!get_height(height, block) ||
!get_tx_position(position, link, block))
{
fail = true;
return history{};
}
}
else
{
if (is_confirmed_all_prevouts(link))
height = history::rooted_height;
}
// History queries.
// ----------------------------------------------------------------------------

return history{ { std::move(hash), height }, fee, position };
});
TEMPLATE
history CLASS::get_tx_history(const tx_link& link) const NOEXCEPT
{
return get_tx_history(get_tx_key(link), link);
}

TEMPLATE
history CLASS::get_tx_history(const hash_digest& key) const NOEXCEPT
{
const auto link = to_tx(key);
return get_tx_history(hash_digest{ key }, link);
}

// private
TEMPLATE
history CLASS::get_tx_history(hash_digest&& key,
const tx_link& link) const NOEXCEPT
{
if (link.is_terminal())
return {};

uint64_t fee{};
if (!get_tx_fee(fee, link))
fee = history::missing_prevout;

auto height = history::unrooted_height;
auto position = history::unconfirmed_position;
if (const auto block = find_confirmed_block(link); !block.is_terminal())
{
if (!get_height(height, block) ||
!get_tx_position(position, link, block))
return {};
}
else
{
if (is_confirmed_all_prevouts(link))
height = history::rooted_height;
}

return { { std::move(key), height }, fee, position };
}

TEMPLATE
histories CLASS::get_spenders_history(const hash_digest& key,
uint32_t index) const NOEXCEPT
{
const auto ins = to_spenders(key, index);
histories out(ins.size());
for (const auto& in: std::views::reverse(ins))
out.push_back(get_tx_history(to_input_tx(in)));

history::filter_sort_and_dedup(out);
return out;
}

// utilities
Expand Down
12 changes: 12 additions & 0 deletions include/bitcoin/database/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,12 @@ class query
uint64_t& unconfirmed, const hash_digest& key,
bool turbo=false) const NOEXCEPT;

/// History queries.
history get_tx_history(const tx_link& link) const NOEXCEPT;
history get_tx_history(const hash_digest& key) const NOEXCEPT;
histories get_spenders_history(const hash_digest& key,
uint32_t index) const NOEXCEPT;

/// Filters.
/// -----------------------------------------------------------------------

Expand Down Expand Up @@ -814,6 +820,12 @@ class query
code set_code(const tx_link& tx_fk, const transaction& tx,
bool bypass) NOEXCEPT;

/// History.
/// -----------------------------------------------------------------------

history get_tx_history(hash_digest&& key,
const tx_link& link) const NOEXCEPT;

private:
// This value should never be read, but may be useful in debugging.
static constexpr uint32_t unspecified_timestamp = max_uint32;
Expand Down
229 changes: 206 additions & 23 deletions test/query/address/address_history.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,37 +199,220 @@ BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_block1a_address0__expecte
// NOTE: at(5) is spend exceeds value, which is returned as unrooted_height in get_unconfirmed_history()
// NOTE: but as rooted_height in get_history(). This is a consequence of the optimized processing order
// NOTE: and does not affect valid confirmed/unconfirmed txs.
BOOST_REQUIRE_EQUAL(out.at(0).tx.height(), 1u); // tx1
BOOST_REQUIRE_EQUAL(out.at(1).tx.height(), 2u); // tx2
BOOST_REQUIRE_EQUAL(out.at(2).tx.height(), 2u); // tx3
BOOST_REQUIRE_EQUAL(out.at(3).tx.height(), 3u); // tx6
BOOST_REQUIRE_EQUAL(out.at(4).tx.height(), history::rooted_height); // tx5/tx4
BOOST_REQUIRE_EQUAL(out.at(5).tx.height(), history::rooted_height); // tx4/tx8
BOOST_REQUIRE_EQUAL(out.at(6).tx.height(), history::unrooted_height); // tx8/tx5
BOOST_REQUIRE_EQUAL(out.at(7).tx.height(), history::unrooted_height); // tx7
BOOST_REQUIRE_EQUAL(out.at(0).tx.height(), 1u); // tx1
BOOST_REQUIRE_EQUAL(out.at(1).tx.height(), 2u); // tx2
BOOST_REQUIRE_EQUAL(out.at(2).tx.height(), 2u); // tx3
BOOST_REQUIRE_EQUAL(out.at(3).tx.height(), 3u); // tx6
BOOST_REQUIRE_EQUAL(out.at(4).tx.height(), history::rooted_height); // tx5/tx4
BOOST_REQUIRE_EQUAL(out.at(5).tx.height(), history::rooted_height); // tx4/tx8
BOOST_REQUIRE_EQUAL(out.at(6).tx.height(), history::unrooted_height); // tx8/tx5
BOOST_REQUIRE_EQUAL(out.at(7).tx.height(), history::unrooted_height); // tx7

// Confirmed height by block position.
BOOST_REQUIRE_EQUAL(out.at(0).position, 0u); // tx1
BOOST_REQUIRE_EQUAL(out.at(1).position, 0u); // tx2
BOOST_REQUIRE_EQUAL(out.at(2).position, 1u); // tx3
BOOST_REQUIRE_EQUAL(out.at(3).position, 0u); // tx6
BOOST_REQUIRE_EQUAL(out.at(4).position, history::unconfirmed_position); // tx5/tx4
BOOST_REQUIRE_EQUAL(out.at(5).position, history::unconfirmed_position); // tx4/tx8
BOOST_REQUIRE_EQUAL(out.at(6).position, history::unconfirmed_position); // tx8/tx5
BOOST_REQUIRE_EQUAL(out.at(7).position, history::unconfirmed_position); // tx7
BOOST_REQUIRE_EQUAL(out.at(0).position, 0u); // tx1
BOOST_REQUIRE_EQUAL(out.at(1).position, 0u); // tx2
BOOST_REQUIRE_EQUAL(out.at(2).position, 1u); // tx3
BOOST_REQUIRE_EQUAL(out.at(3).position, 0u); // tx6
BOOST_REQUIRE_EQUAL(out.at(4).position, history::unconfirmed_position); // tx5/tx4
BOOST_REQUIRE_EQUAL(out.at(5).position, history::unconfirmed_position); // tx4/tx8
BOOST_REQUIRE_EQUAL(out.at(6).position, history::unconfirmed_position); // tx8/tx5
BOOST_REQUIRE_EQUAL(out.at(7).position, history::unconfirmed_position); // tx7

// Unconfirmed system::encode_hash(hash) lexically sorted.
BOOST_CHECK(encode_hash(out.at(6).tx.hash()) < encode_hash(out.at(7).tx.hash()));
BOOST_REQUIRE(encode_hash(out.at(6).tx.hash()) < encode_hash(out.at(7).tx.hash()));

// Fee (not part of sort).
BOOST_REQUIRE_EQUAL(out.at(0).fee, history::missing_prevout); // tx1
BOOST_REQUIRE_EQUAL(out.at(1).fee, history::missing_prevout); // tx2
BOOST_REQUIRE_EQUAL(out.at(2).fee, history::missing_prevout); // tx3
BOOST_REQUIRE_EQUAL(out.at(3).fee, history::missing_prevout); // tx6
BOOST_REQUIRE_EQUAL(out.at(4).fee, history::missing_prevout); // tx5/tx4
BOOST_REQUIRE_EQUAL(out.at(0).fee, history::missing_prevout); // tx1
BOOST_REQUIRE_EQUAL(out.at(1).fee, history::missing_prevout); // tx2
BOOST_REQUIRE_EQUAL(out.at(2).fee, history::missing_prevout); // tx3
BOOST_REQUIRE_EQUAL(out.at(3).fee, history::missing_prevout); // tx6
BOOST_REQUIRE_EQUAL(out.at(4).fee, history::missing_prevout); // tx5/tx4
BOOST_REQUIRE_EQUAL(out.at(5).fee, floored_subtract(0x18u + 0x2au, 0x08u)); // tx4/tx8
BOOST_REQUIRE_EQUAL(out.at(6).fee, floored_subtract(0xb1u + 0xb1u, 0xb2u)); // tx8/tx5
BOOST_REQUIRE_EQUAL(out.at(7).fee, 0u); // tx7
BOOST_REQUIRE_EQUAL(out.at(7).fee, 0u); // tx7
}

// get_tx_history1
// get_tx_history2

BOOST_AUTO_TEST_CASE(query_address__get_tx_history__bogus__invalid)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

const auto history = query.get_tx_history(hash_digest{ 0x42 });
BOOST_REQUIRE(!history.valid());
}

BOOST_AUTO_TEST_CASE(query_address__get_tx_history__genesis__expected)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

const auto hash = test::genesis.transactions_ptr()->at(0)->hash(false);
auto history = query.get_tx_history(0);
BOOST_REQUIRE(history.valid());
BOOST_REQUIRE_EQUAL(history.fee, 0u);
BOOST_REQUIRE_EQUAL(history.position, 0u);
BOOST_REQUIRE_EQUAL(history.tx.height(), 0u);
BOOST_REQUIRE_EQUAL(history.tx.hash(), hash);

history = query.get_tx_history(hash);
BOOST_REQUIRE(history.valid());
BOOST_REQUIRE_EQUAL(history.fee, 0u);
BOOST_REQUIRE_EQUAL(history.position, 0u);
BOOST_REQUIRE_EQUAL(history.tx.height(), 0u);
BOOST_REQUIRE_EQUAL(history.tx.hash(), hash);
}

BOOST_AUTO_TEST_CASE(query_address__get_tx_history__confirmed__expected)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

const auto hash = test::block2a.transactions_ptr()->at(0)->hash(false);
const auto history = query.get_tx_history(hash);
BOOST_REQUIRE(history.valid());
BOOST_REQUIRE_EQUAL(history.fee, history::missing_prevout); // spend > value
BOOST_REQUIRE_EQUAL(history.position, 0u);
BOOST_REQUIRE_EQUAL(history.tx.height(), 2u);
BOOST_REQUIRE_EQUAL(history.tx.hash(), hash);
}

BOOST_AUTO_TEST_CASE(query_address__get_tx_history__confirmed_missing_prevout__expected)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

const auto hash = test::block2a.transactions_ptr()->at(1)->hash(false);
const auto history = query.get_tx_history(hash);
BOOST_REQUIRE(history.valid());
BOOST_REQUIRE_EQUAL(history.fee, history::missing_prevout); // missing prevout
BOOST_REQUIRE_EQUAL(history.position, 1u);
BOOST_REQUIRE_EQUAL(history.tx.height(), 2u);
BOOST_REQUIRE_EQUAL(history.tx.hash(), hash);
}

BOOST_AUTO_TEST_CASE(query_address__get_tx_history__rooted__expected)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

using namespace system;
const auto hash = test::tx4.hash(false);
const auto history = query.get_tx_history(hash);
BOOST_REQUIRE(history.valid());
BOOST_REQUIRE_EQUAL(history.fee, floored_subtract(0x18u + 0x2au, 0x08u));
BOOST_REQUIRE_EQUAL(history.position, history::unconfirmed_position);
BOOST_REQUIRE_EQUAL(history.tx.height(), history::rooted_height);
BOOST_REQUIRE_EQUAL(history.tx.hash(), hash);
}

BOOST_AUTO_TEST_CASE(query_address__get_tx_history__unrooted__expected)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

using namespace system;
const auto hash = test::block2b.transactions_ptr()->at(0)->hash(false);
const auto history = query.get_tx_history(hash);
BOOST_REQUIRE(history.valid());
BOOST_REQUIRE_EQUAL(history.fee, floored_subtract(0xb1u + 0xb1u, 0xb2u));
BOOST_REQUIRE_EQUAL(history.position, history::unconfirmed_position);
BOOST_REQUIRE_EQUAL(history.tx.height(), history::unrooted_height);
BOOST_REQUIRE_EQUAL(history.tx.hash(), hash);
}

// get_spenders_history

BOOST_AUTO_TEST_CASE(query_address__get_spenders_history__bogus__empty)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

const auto histories = query.get_spenders_history({ 0x42 }, 0);
BOOST_REQUIRE(histories.empty());
}

BOOST_AUTO_TEST_CASE(query_address__get_spenders_history__genesis__expected)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

const auto hash = test::genesis.transactions_ptr()->at(0)->hash(false);
const auto histories = query.get_spenders_history(hash, 0);
BOOST_REQUIRE_EQUAL(histories.size(), 0u);
}

BOOST_AUTO_TEST_CASE(query_address__get_spenders_history__confirmed__expected)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE(!store.create(test::events_handler));
BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query));

using namespace system;
const auto hash = test::block1a.transactions_ptr()->at(0)->hash(false);
const auto histories = query.get_spenders_history(hash, 0);
BOOST_REQUIRE_EQUAL(histories.size(), 4u);

BOOST_REQUIRE(histories.at(0).valid());
BOOST_REQUIRE_EQUAL(histories.at(0).fee, history::missing_prevout); // spend > value
BOOST_REQUIRE_EQUAL(histories.at(0).position, 0u);
BOOST_REQUIRE_EQUAL(histories.at(0).tx.height(), 2u);
BOOST_REQUIRE_EQUAL(histories.at(0).tx.hash(), test::block2a.transactions_ptr()->at(0)->hash(false));

BOOST_REQUIRE(histories.at(1).valid());
BOOST_REQUIRE_EQUAL(histories.at(1).fee, history::missing_prevout); // spend > value
BOOST_REQUIRE_EQUAL(histories.at(1).position, 0u);
BOOST_REQUIRE_EQUAL(histories.at(1).tx.height(), 3u);
BOOST_REQUIRE_EQUAL(histories.at(1).tx.hash(), test::block3a.transactions_ptr()->at(0)->hash(false));

BOOST_REQUIRE(histories.at(2).valid());
BOOST_REQUIRE_EQUAL(histories.at(2).fee, history::missing_prevout); // spend > value
BOOST_REQUIRE_EQUAL(histories.at(2).position, history::unconfirmed_position);
BOOST_REQUIRE_EQUAL(histories.at(2).tx.height(), history::rooted_height);
BOOST_REQUIRE_EQUAL(histories.at(2).tx.hash(), test::tx5.hash(false));

BOOST_REQUIRE(histories.at(3).valid());
BOOST_REQUIRE_EQUAL(histories.at(3).fee, floored_subtract(0x18u + 0x2au, 0x08u));
BOOST_REQUIRE_EQUAL(histories.at(3).position, history::unconfirmed_position);
BOOST_REQUIRE_EQUAL(histories.at(3).tx.height(), history::rooted_height);
BOOST_REQUIRE_EQUAL(histories.at(3).tx.hash(), test::tx4.hash(false));
}

BOOST_AUTO_TEST_SUITE_END()