diff --git a/include/bitcoin/database/impl/query/address/address_history.ipp b/include/bitcoin/database/impl/query/address/address_history.ipp index 8907d42f..3d798304 100644 --- a/include/bitcoin/database/impl/query/address/address_history.ipp +++ b/include/bitcoin/database/impl/query/address/address_history.ipp @@ -52,29 +52,30 @@ code CLASS::get_unconfirmed_history(const stopper& cancel, histories& out, return parallel_history_transform(cancel, turbo, out, txs, [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> history { - if (cancel) + if (cancel || fail) return {}; - if (const auto block = find_strong(link); is_confirmed_block(block)) + // chain::checkpoint invalid in default construction (filter). + const auto block = find_strong(link); + if (is_confirmed_block(block)) + return {}; + + auto hash = get_tx_key(link); + if (hash == system::null_hash) { - constexpr auto excluded = history::excluded_position; - return { system::chain::checkpoint{}, {}, excluded }; + fail = true; + return {}; } - else - { - uint64_t fee{}; - auto hash = get_tx_key(link); - if (hash == system::null_hash || !get_tx_fee(fee, link)) - { - fail = true; - } - const auto height = is_confirmed_all_prevouts(link) ? - history::rooted_height : history::unrooted_height; - - constexpr auto unconfirmed = history::unconfirmed_position; - return { { std::move(hash), height }, fee, unconfirmed }; - } + uint64_t fee{}; + auto height = history::unrooted_height; + if (!get_tx_fee(fee, link)) + fee = history::missing_prevout; + else if (is_confirmed_all_prevouts(link)) + height = history::rooted_height; + + return { { std::move(hash), height }, fee, + history::unconfirmed_position }; }); } @@ -96,28 +97,29 @@ code CLASS::get_confirmed_history(const stopper& cancel, histories& out, return parallel_history_transform(cancel, turbo, out, txs, [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> history { - if (cancel) + if (cancel || fail) return {}; - if (const auto block = find_strong(link); is_confirmed_block(block)) - { - uint64_t fee{}; - size_t height{}, position{}; - auto hash = get_tx_key(link); - if (hash == system::null_hash || !get_tx_fee(fee, link) || - !get_height(height, block) || - !get_tx_position(position, link, block)) - { - fail = true; - } + // chain::checkpoint invalid in default construction (filter). + const auto block = find_strong(link); + if (!is_confirmed_block(block)) + return {}; - return { { std::move(hash), height }, fee, position }; - } - else + size_t height{}, position{}; + auto hash = get_tx_key(link); + if (hash == system::null_hash || + !get_height(height, block) || + !get_tx_position(position, link, block)) { - constexpr auto exclude = history::excluded_position; - return { system::chain::checkpoint{}, {}, exclude }; + fail = true; + return {}; } + + uint64_t fee{}; + if (!get_tx_fee(fee, link)) + fee = history::missing_prevout; + + return { { std::move(hash), height }, fee, position }; }); } @@ -139,25 +141,36 @@ code CLASS::get_history(const stopper& cancel, histories& out, return parallel_history_transform(cancel, turbo, out, links, [this](const auto& link, auto& cancel, auto& fail) NOEXCEPT -> history { - if (cancel) + if (cancel || fail) return {}; - uint64_t fee{}; auto hash = get_tx_key(link); - if (hash == system::null_hash || !get_tx_fee(fee, link)) + if (hash == system::null_hash) + { fail = true; + return {}; + } - size_t height{}, position{}; - if (const auto block = find_strong(link); is_confirmed_block(block)) + 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_strong(link); + is_confirmed_block(block)) { if (!get_height(height, block) || !get_tx_position(position, link, block)) + { fail = true; + return {}; + } } else { - height = is_confirmed_all_prevouts(link) ? - history::rooted_height : history::unrooted_height; + if (is_confirmed_all_prevouts(link)) + height = history::rooted_height; } return { { std::move(hash), height }, fee, position }; diff --git a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp index 3bfaf0c7..f8cd0939 100644 --- a/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp +++ b/include/bitcoin/database/impl/query/navigate/navigate_reverse.ipp @@ -52,26 +52,8 @@ TEMPLATE code CLASS::to_touched_txs(tx_links& out, const output_links& outputs) const NOEXCEPT { - // Reserve one for each output tx and one for spending input tx (estimate). - out.clear(); - out.reserve(two * outputs.size()); - - // Orders are reversed due to expected tx_links reversal, for faster sort. - for (const auto& output: std::views::reverse(outputs)) - { - out.push_back(to_output_tx(output)); - if (out.back() == tx_link::terminal) - return error::integrity; - - for (const auto& input: std::views::reverse(to_spenders(output))) - { - out.push_back(to_input_tx(input)); - if (out.back() == tx_link::terminal) - return error::integrity; - } - } - - return {}; + const stopper cancel{}; + return to_touched_txs(cancel, out, outputs); } TEMPLATE @@ -88,37 +70,29 @@ code CLASS::to_touched_txs(const stopper& cancel, tx_links& out, if (cancel) return error::canceled; - out.push_back(to_output_tx(output)); - if (out.back() == tx_link::terminal) + if (const auto tx = to_output_tx(output); tx.is_terminal()) return error::integrity; + else + out.push_back(tx); for (const auto& input: std::views::reverse(to_spenders(output))) { - out.push_back(to_input_tx(input)); - if (out.back() == tx_link::terminal) + if (const auto tx = to_input_tx(input); tx.is_terminal()) return error::integrity; + else + out.push_back(tx); } } - return {}; + return error::success; } TEMPLATE code CLASS::to_address_outputs(output_links& out, const hash_digest& key) const NOEXCEPT { - // Pushing into the vector is more efficient than precomputation of size. - out.clear(); - for (auto it = store_.address.it(key); it; ++it) - { - table::address::record address{}; - if (!store_.address.get(it, address)) - return error::integrity; - - out.push_back(address.output_fk); - } - - return error::success; + const stopper cancel{}; + return to_address_outputs(cancel, out, key); } TEMPLATE diff --git a/include/bitcoin/database/types.hpp b/include/bitcoin/database/types.hpp index 7e571bc4..a0f043cc 100644 --- a/include/bitcoin/database/types.hpp +++ b/include/bitcoin/database/types.hpp @@ -137,7 +137,7 @@ struct BCD_API history { static constexpr size_t rooted_height = zero; static constexpr size_t unrooted_height = max_size_t; - static constexpr size_t excluded_position = max_size_t; + static constexpr size_t missing_prevout = max_uint64; static constexpr size_t unconfirmed_position = zero; struct less_than diff --git a/src/types.cpp b/src/types.cpp index 78507917..c472bbc9 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -86,7 +86,7 @@ bool history::equal_to::operator()(const history& a, const history& b) const NOE bool history::exclude::operator()(const history& element) const NOEXCEPT { - return element.position == max_size_t; + return !element.tx.is_valid(); } void history::sort_and_dedup(std::vector& out) NOEXCEPT @@ -140,7 +140,7 @@ bool unspent::equal_to::operator()(const unspent& a, bool unspent::exclude::operator()(const unspent& element) const NOEXCEPT { - return element.position == max_size_t; + return !element.tx.is_valid(); } void unspent::sort_and_dedup(std::vector& out) NOEXCEPT diff --git a/test/mocks/blocks.cpp b/test/mocks/blocks.cpp index 374289af..72d1ad4f 100644 --- a/test/mocks/blocks.cpp +++ b/test/mocks/blocks.cpp @@ -25,7 +25,7 @@ using namespace system; constexpr hash_digest two_hash = from_uintx(uint256_t(two)); constexpr database::context context{ 0x01020304, 0x11121314, 0x21222324 }; -constexpr hash_digest genesis_address = base16_hash("740485f380ff6379d11ef6fe7d7cdd68aea7f8bd0d953d9fdf3531fb7d531833"); +constexpr hash_digest genesis_address0 = base16_hash("740485f380ff6379d11ef6fe7d7cdd68aea7f8bd0d953d9fdf3531fb7d531833"); constexpr hash_digest block1a_address0 = base16_hash("fab04811b1d0379bf78c2a41902368c200d675788e4bff8c88ff543836e4fca1"); constexpr hash_digest block1a_address1 = base16_hash("067bd624c5840ed0d5b65597bafcde68f07fa9d87d3b43292b3199e49a514e59"); @@ -111,9 +111,11 @@ bool setup_three_block_confirmed_address_store(query_t& query) NOEXCEPT query.set(test::tx4) && query.set(test::tx5) && query.set(block3a, database::context{ 0, 3, 0 }, false, false) && + query.set(block1b, database::context{ 0, 1, 0 }, false, false) && + query.set(block2b, database::context{ 0, 2, 0 }, false, false) && query.push_confirmed(query.to_header(block1a.hash()), true) && query.push_confirmed(query.to_header(block2a.hash()), true) && - query.push_confirmed(query.to_header(block2a.hash()), true); + query.push_confirmed(query.to_header(block3a.hash()), true); } bool setup_three_block_unconfirmed_address_store(query_t& query) NOEXCEPT @@ -136,7 +138,7 @@ const block bogus_block { 0x31323334, null_hash, - one_hash, + hash_digest{ 0xbb }, 0x41424344, 0x51525354, 0x61626364 @@ -241,7 +243,7 @@ const block block1a { 0x31323334, // version block0_hash, // previous_block_hash - null_hash, // merkle_root + hash_digest{ 0x1a },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -299,7 +301,7 @@ const block block2a { 0x31323334, // version block1a.hash(), // previous_block_hash - one_hash, // merkle_root + hash_digest{ 0x2a },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -371,6 +373,54 @@ const block block2a } } }; +const block block3a +{ + header + { + 0x31323334, // version + block2a.hash(), // previous_block_hash + hash_digest{ 0x3a },// merkle_root + 0x41424344, // timestamp + 0x51525354, // bits + 0x61626364 // nonce + }, + transactions + { + // This first transaction is *not* a coinbase. + transaction + { + 0xa3, // version + inputs + { + input + { + // existing prevout + point{ block1a.transactions_ptr()->front()->hash(false), 0x01 }, + script{ { { opcode::checkmultisig }, { opcode::size } } }, + witness{ "[949494]" }, + 0xa3 // sequence + }, + input + { + // existing prevout + point{ block1a.transactions_ptr()->front()->hash(false), 0x00 }, + script{ { { opcode::checkmultisig }, { opcode::size } } }, + witness{ "[919191]" }, + 0x83 // sequence + } + }, + outputs + { + output + { + 0x83, // value + script{ { { opcode::pick } } } + } + }, + 0x83 // locktime + } + } +}; const transaction tx4 { 0xa5, // version @@ -395,7 +445,7 @@ const transaction tx4 { output { - 0x85, // value + 0x08, // value script{ { { opcode::pick } } } } }, @@ -430,7 +480,7 @@ const block block_spend_1a { 0x31323334, // version block1a.hash(), // previous_block_hash - two_hash, // merkle_root (two_hash allows double spend) + hash_digest{ 0x2c },// merkle_root (???? allows double spend) 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -470,7 +520,7 @@ const block block_spend_genesis { 0x31323334, // version block0_hash, // previous_block_hash - null_hash, // merkle_root + hash_digest{ 0x1c },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -480,61 +530,13 @@ const block block_spend_genesis tx_spend_genesis } }; -const block block3a -{ - header - { - 0x31323334, // version - block2a.hash(), // previous_block_hash - two_hash, // merkle_root - 0x41424344, // timestamp - 0x51525354, // bits - 0x61626364 // nonce - }, - transactions - { - // This first transaction is *not* a coinbase. - transaction - { - 0xa3, // version - inputs - { - input - { - // existing prevout - point{ block1a.transactions_ptr()->front()->hash(false), 0x01 }, - script{ { { opcode::checkmultisig }, { opcode::size } } }, - witness{ "[949494]" }, - 0xa3 // sequence - }, - input - { - // existing prevout - point{ block1a.transactions_ptr()->front()->hash(false), 0x00 }, - script{ { { opcode::checkmultisig }, { opcode::size } } }, - witness{ "[919191]" }, - 0x83 // sequence - } - }, - outputs - { - output - { - 0x83, // value - script{ { { opcode::pick } } } - } - }, - 0x83 // locktime - } - } -}; const block block1b { header { 0x31323334, // version block0_hash, // previous_block_hash - null_hash, // merkle_root + hash_digest{ 0x1b },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -578,7 +580,7 @@ const block block2b { 0x31323334, // version block1b.hash(), // previous_block_hash - one_hash, // merkle_root + hash_digest{ 0x2b },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -651,7 +653,7 @@ const block block_spend_internal_2b { 0x31323334, // version block1b.hash(), // previous_block_hash - one_hash, // merkle_root + hash_digest{ 0x3c },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -691,7 +693,7 @@ const block block_missing_prevout_2b { 0x31323334, // version block1b.hash(), // previous_block_hash - one_hash, // merkle_root + hash_digest{ 0x3d },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce @@ -731,7 +733,7 @@ const block block_valid_spend_internal_2b { 0x31323334, // version block1b.hash(), // previous_block_hash - one_hash, // merkle_root + hash_digest{ 0x3e },// merkle_root 0x41424344, // timestamp 0x51525354, // bits 0x61626364 // nonce diff --git a/test/mocks/blocks.hpp b/test/mocks/blocks.hpp index 6fcd39d1..807875d0 100644 --- a/test/mocks/blocks.hpp +++ b/test/mocks/blocks.hpp @@ -31,7 +31,8 @@ using header_data = system::data_array<80>; const auto events_handler = [](auto, auto) {}; extern const database::context context; -extern const system::hash_digest genesis_address; +// block1a_address0 is the same address as: bk2a0/bk2a1/tx4/tx5/bk3a0. +extern const system::hash_digest genesis_address0; extern const system::hash_digest block1a_address0; extern const system::hash_digest block1a_address1; @@ -93,12 +94,12 @@ extern const system::chain::block block9; extern const system::chain::block bogus_block; extern const system::chain::block block1a; extern const system::chain::block block2a; +extern const system::chain::block block3a; extern const system::chain::transaction tx4; extern const system::chain::transaction tx5; extern const system::chain::block block_spend_1a; extern const system::chain::transaction tx_spend_genesis; extern const system::chain::block block_spend_genesis; -extern const system::chain::block block3a; extern const system::chain::block block1b; extern const system::chain::block block2b; extern const system::chain::transaction tx2b; diff --git a/test/query/address/address_balance.cpp b/test/query/address/address_balance.cpp index fcdf744b..74346058 100644 --- a/test/query/address/address_balance.cpp +++ b/test/query/address/address_balance.cpp @@ -20,57 +20,95 @@ #include "../../mocks/blocks.hpp" #include "../../mocks/chunk_store.hpp" +// Unconfirmed balance is always zero in the current implementation. + BOOST_FIXTURE_TEST_SUITE(query_address_tests, test::directory_setup_fixture) // get_unconfirmed_balance // get_confirmed_balance // get_balance -BOOST_AUTO_TEST_CASE(query_address__get_balance__turbo_genesis__expected) +BOOST_AUTO_TEST_CASE(query_address__get_balance__turbo_block1a_address0_confirmed_blocks__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(query.initialize(test::genesis)); + + // block1a_address0 has 9 instances `script{ { { opcode::pick } } }`. + // block1a_address0 has 4 confirmed instances (blocks 1a/2a/3a). + // block1a (value: 0x18) is confirmed spent by block2a0. + // block2a (value: 0x81) is confirmed unspent. + // block2a (value: 0x81) is confirmed unspent. + // block3a (value: 0x83) is confirmed unspent. + BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query)); uint64_t unconfirmed{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_unconfirmed_balance(cancel, unconfirmed, test::genesis_address, true)); + BOOST_REQUIRE(!query.get_unconfirmed_balance(cancel, unconfirmed, test::block1a_address0, true)); BOOST_REQUIRE_EQUAL(unconfirmed, 0u); uint64_t confirmed{}; - BOOST_REQUIRE(!query.get_confirmed_balance(cancel, confirmed, test::genesis_address, true)); - BOOST_REQUIRE_EQUAL(confirmed, 5000000000u); + BOOST_REQUIRE(!query.get_confirmed_balance(cancel, confirmed, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(confirmed, 389u); confirmed = unconfirmed = 42; - BOOST_REQUIRE(!query.get_balance(cancel, confirmed, unconfirmed, test::genesis_address, true)); - BOOST_REQUIRE_EQUAL(confirmed, 5000000000u); + BOOST_REQUIRE(!query.get_balance(cancel, confirmed, unconfirmed, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(confirmed, 389u); BOOST_REQUIRE_EQUAL(unconfirmed, 0u); } -BOOST_AUTO_TEST_CASE(query_address__get_balance__genesis__expected) +BOOST_AUTO_TEST_CASE(query_address__get_balance__turbo_block1a_address0_unconfirmed_blocks__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(query.initialize(test::genesis)); + + // block1a_address0 has 6 instances `script{ { { opcode::pick } } }`. + BOOST_REQUIRE(test::setup_three_block_unconfirmed_address_store(query)); + + uint64_t unconfirmed{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_unconfirmed_balance(cancel, unconfirmed, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(unconfirmed, 0u); + + uint64_t confirmed{}; + BOOST_REQUIRE(!query.get_confirmed_balance(cancel, confirmed, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(confirmed, 0u); + + confirmed = unconfirmed = 42; + BOOST_REQUIRE(!query.get_balance(cancel, confirmed, unconfirmed, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(confirmed, 0u); + BOOST_REQUIRE_EQUAL(unconfirmed, 0u); +} + +BOOST_AUTO_TEST_CASE(query_address__get_balance__block1a_address1_confirmed_blocks__expected) +{ + settings settings{}; + settings.path = TEST_DIRECTORY; + test::chunk_store store{ settings }; + test::query_accessor query{ store }; + BOOST_REQUIRE(!store.create(test::events_handler)); + + // block1a_address1 is unique to that output instance `script{ { { opcode::roll } } }`. + // block1a_address1 is spent by block2a1, tx4-1, and (confirmed double spent by) block3a1. + BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query)); uint64_t unconfirmed{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_unconfirmed_balance(cancel, unconfirmed, test::genesis_address)); + BOOST_REQUIRE(!query.get_unconfirmed_balance(cancel, unconfirmed, test::block1a_address1)); BOOST_REQUIRE_EQUAL(unconfirmed, 0u); uint64_t confirmed{}; - BOOST_REQUIRE(!query.get_confirmed_balance(cancel, confirmed, test::genesis_address)); - BOOST_REQUIRE_EQUAL(confirmed, 5000000000u); + BOOST_REQUIRE(!query.get_confirmed_balance(cancel, confirmed, test::block1a_address1)); + BOOST_REQUIRE_EQUAL(confirmed, 0u); confirmed = unconfirmed = 42; - BOOST_REQUIRE(!query.get_balance(cancel, confirmed, unconfirmed, test::genesis_address)); - BOOST_REQUIRE_EQUAL(confirmed, 5000000000u); + BOOST_REQUIRE(!query.get_balance(cancel, confirmed, unconfirmed, test::block1a_address1)); + BOOST_REQUIRE_EQUAL(confirmed, 0u); BOOST_REQUIRE_EQUAL(unconfirmed, 0u); } diff --git a/test/query/address/address_history.cpp b/test/query/address/address_history.cpp index 55d81044..b95f234d 100644 --- a/test/query/address/address_history.cpp +++ b/test/query/address/address_history.cpp @@ -26,50 +26,205 @@ BOOST_FIXTURE_TEST_SUITE(query_address_tests, test::directory_setup_fixture) // get_confirmed_history // get_history -BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_genesis__expected) +BOOST_AUTO_TEST_CASE(query_address__get_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(query.initialize(test::genesis)); + BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query)); histories out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::genesis_address, true)); - BOOST_REQUIRE(out.empty()); + BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::genesis_address0)); + BOOST_REQUIRE_EQUAL(out.size(), 0u); out.clear(); - BOOST_REQUIRE(!query.get_confirmed_history(cancel, out, test::genesis_address, true)); - BOOST_CHECK_EQUAL(out.size(), 1u); + BOOST_REQUIRE(!query.get_confirmed_history(cancel, out, test::genesis_address0)); + BOOST_REQUIRE_EQUAL(out.size(), 1u); out.clear(); - BOOST_REQUIRE(!query.get_history(cancel, out, test::genesis_address, true)); - BOOST_CHECK_EQUAL(out.size(), 1u); + BOOST_REQUIRE(!query.get_history(cancel, out, test::genesis_address0)); + BOOST_REQUIRE_EQUAL(out.size(), 1u); } -BOOST_AUTO_TEST_CASE(query_address__get_history__genesis__expected) +// block1a_address0 has 9 instances `script{ { { opcode::pick } } }`. +// 7 outputs are in individual txs, 2 are in tx bk1b0. +// 4 owning txs are confirmed (bk1a/bk2a/bk3a). +// 4 owning txs are unconfirmed (bk1b/bk2b/tx4/tx5). +// 6 outputs are spent, (bk2a/bk3a/tx4/tx5/bk2b00/bk2b01). +// 2 spend txs are confirmed, (bk2a/bk3a). +// 3 spend txs are unconfirmed, (tx4/tx5/bk2b0(2)). +// +// One owner owns two (one owner removed by deduplication). +// All spenders are also owners (all spends removed by deduplication). +// 4 owns + 2 spend txs are confirmed. +// 4 owns + 2 spend txs are confirmed. +// +// 4 unconfirmed: +// tx4 spends 1u + owns 1u (+1u) [unconfirmed rooted]. +// tx5 spends 1u + owns 1u (+1u) [unconfirmed rooted]. +// bk1btx0 owns 2u (+1u) [unconfirmed unrooted]. +// bk2btx0 spends 2u + owns 1u (+1u) [unconfirmed unrooted]. +// +// 4 confirmed: +// bk1atx0 owns 1c (+1c) [confirmed]. +// bk2atx0 spends 1c + owns 1c (+1c) [confirmed]. +// bk2atx1 owns 1c (+1c) [confirmed]. +// bk3atx0 spends 1c + owns 1c (+1c) [confirmed]. + +BOOST_AUTO_TEST_CASE(query_address__get_unconfirmed_history__turbo_block1a_address0__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(query.initialize(test::genesis)); + BOOST_REQUIRE(test::setup_three_block_confirmed_address_store(query)); + using namespace system; histories out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::genesis_address)); - BOOST_CHECK_EQUAL(out.size(), 0u); + BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(out.size(), 4u); - out.clear(); - BOOST_REQUIRE(!query.get_unconfirmed_history(cancel, out, test::genesis_address)); - BOOST_CHECK_EQUAL(out.size(), 0u); + // Identities (not part of sort). + BOOST_REQUIRE_EQUAL(out.at(0).tx.hash(), test::tx4.hash(false)); // tx4 + BOOST_REQUIRE_EQUAL(out.at(1).tx.hash(), test::block2b.transactions_ptr()->at(0)->hash(false)); // tx8 + BOOST_REQUIRE_EQUAL(out.at(2).tx.hash(), test::tx5.hash(false)); // tx5 + BOOST_REQUIRE_EQUAL(out.at(3).tx.hash(), test::block1b.transactions_ptr()->at(0)->hash(false)); // tx7 - out.clear(); - BOOST_REQUIRE(!query.get_history(cancel, out, test::genesis_address)); - BOOST_CHECK_EQUAL(out.size(), 1u); + // Confirmed by height ascending (not part of sort). + + // Unconfirmed rooted before unrooted. + BOOST_REQUIRE_EQUAL(out.at(0).tx.height(), history::rooted_height); // spends block1a (tx0 both outputs). + BOOST_REQUIRE_EQUAL(out.at(1).tx.height(), history::unrooted_height); // bk2btx0 unrooted (spends bk1). + BOOST_REQUIRE_EQUAL(out.at(2).tx.height(), history::unrooted_height); // spend exceeds value (treated as missing prevout). + BOOST_REQUIRE_EQUAL(out.at(3).tx.height(), history::unrooted_height); // bk1btx0 unrooted (missing prevouts). + + // Confirmed height by block position (not part of sort). + BOOST_REQUIRE_EQUAL(out.at(0).position, history::unconfirmed_position); + BOOST_REQUIRE_EQUAL(out.at(1).position, history::unconfirmed_position); + BOOST_REQUIRE_EQUAL(out.at(2).position, history::unconfirmed_position); + BOOST_REQUIRE_EQUAL(out.at(3).position, history::unconfirmed_position); + + // Unconfirmed system::encode_hash(hash) lexically sorted. + BOOST_REQUIRE(encode_hash(out.at(1).tx.hash()) < encode_hash(out.at(2).tx.hash())); + BOOST_REQUIRE(encode_hash(out.at(2).tx.hash()) < encode_hash(out.at(3).tx.hash())); + + // Fee (not part of sort). + BOOST_REQUIRE_EQUAL(out.at(0).fee, system::floored_subtract(0x18u + 0x2au, 0x08u)); + BOOST_REQUIRE_EQUAL(out.at(1).fee, system::floored_subtract(0xb1u + 0xb1u, 0xb2u)); + BOOST_REQUIRE_EQUAL(out.at(2).fee, history::missing_prevout); // spend exceeds value (treated as missing prevout). + BOOST_REQUIRE_EQUAL(out.at(3).fee, 0u); // coinbase (archived with null single point). +} + +BOOST_AUTO_TEST_CASE(query_address__get_confirmed_history__turbo_block1a_address0__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; + histories out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_confirmed_history(cancel, out, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(out.size(), 4u); + + // Identities (not part of sort). + BOOST_REQUIRE_EQUAL(out.at(0).tx.hash(), test::block1a.transactions_ptr()->at(0)->hash(false)); // tx1 + BOOST_REQUIRE_EQUAL(out.at(1).tx.hash(), test::block2a.transactions_ptr()->at(0)->hash(false)); // tx2 + BOOST_REQUIRE_EQUAL(out.at(2).tx.hash(), test::block2a.transactions_ptr()->at(1)->hash(false)); // tx3 + BOOST_REQUIRE_EQUAL(out.at(3).tx.hash(), test::block3a.transactions_ptr()->at(0)->hash(false)); // tx6 + + // Confirmed by height ascending. + BOOST_REQUIRE_EQUAL(out.at(0).tx.height(), 1u); + BOOST_REQUIRE_EQUAL(out.at(1).tx.height(), 2u); + BOOST_REQUIRE_EQUAL(out.at(2).tx.height(), 2u); + BOOST_REQUIRE_EQUAL(out.at(3).tx.height(), 3u); + + // Unconfirmed rooted before unrooted (not part of sort). + + // Confirmed height by block position. + BOOST_REQUIRE_EQUAL(out.at(0).position, history::unconfirmed_position); // confirmed, spend exceeds value (with missing prevout). + BOOST_REQUIRE_EQUAL(out.at(1).position, history::unconfirmed_position); // confirmed, spend exceeds value. + BOOST_REQUIRE_EQUAL(out.at(2).position, 1u); // confirmed, at position (with missing prevout). + BOOST_REQUIRE_EQUAL(out.at(3).position, history::unconfirmed_position); // confirmed, spend exceeds value. + + // Unconfirmed system::encode_hash(hash) lexically sorted (not part of sort). + + // Fee (not part of sort). + BOOST_REQUIRE_EQUAL(out.at(0).fee, history::missing_prevout); // spend exceeds value (treated as missing prevout). + BOOST_REQUIRE_EQUAL(out.at(1).fee, history::missing_prevout); // spend exceeds value (treated as missing prevout). + BOOST_REQUIRE_EQUAL(out.at(2).fee, history::missing_prevout); // missing prevout. + BOOST_REQUIRE_EQUAL(out.at(3).fee, history::missing_prevout); // spend exceeds value (treated as missing prevout). +} + +BOOST_AUTO_TEST_CASE(query_address__get_history__turbo_block1a_address0__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; + histories out{}; + const std::atomic_bool cancel{}; + BOOST_REQUIRE(!query.get_history(cancel, out, test::block1a_address0, true)); + BOOST_REQUIRE_EQUAL(out.size(), 8u); + + // Identities (not part of sort). + BOOST_REQUIRE_EQUAL(out.at(0).tx.hash(), test::block1a.transactions_ptr()->at(0)->hash(false)); // tx1/tx1 (same) + BOOST_REQUIRE_EQUAL(out.at(1).tx.hash(), test::block2a.transactions_ptr()->at(0)->hash(false)); // tx2/tx2 (same) + BOOST_REQUIRE_EQUAL(out.at(2).tx.hash(), test::block2a.transactions_ptr()->at(1)->hash(false)); // tx3/tx3 (same) + BOOST_REQUIRE_EQUAL(out.at(3).tx.hash(), test::block3a.transactions_ptr()->at(0)->hash(false)); // tx6/tx6 (same) + BOOST_REQUIRE_EQUAL(out.at(4).tx.hash(), test::tx5.hash(false)); // tx5/tx4 + BOOST_REQUIRE_EQUAL(out.at(5).tx.hash(), test::tx4.hash(false)); // tx4/tx8 + BOOST_REQUIRE_EQUAL(out.at(6).tx.hash(), test::block2b.transactions_ptr()->at(0)->hash(false)); // tx8/tx5 + BOOST_REQUIRE_EQUAL(out.at(7).tx.hash(), test::block1b.transactions_ptr()->at(0)->hash(false)); // tx7/tx7 (same) + + // Confirmed by height ascending. + // Unconfirmed rooted before unrooted. + // 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 + + // Confirmed height by block position (not part of sort). + BOOST_REQUIRE_EQUAL(out.at(0).position, history::unconfirmed_position); // tx1 + BOOST_REQUIRE_EQUAL(out.at(1).position, history::unconfirmed_position); // tx2 + BOOST_REQUIRE_EQUAL(out.at(2).position, 1u); // tx3 + BOOST_REQUIRE_EQUAL(out.at(3).position, history::unconfirmed_position); // 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_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(5).fee, system::floored_subtract(0x18u + 0x2au, 0x08u)); // tx4/tx8 + BOOST_REQUIRE_EQUAL(out.at(6).fee, system::floored_subtract(0xb1u + 0xb1u, 0xb2u)); // tx8/tx5 + BOOST_REQUIRE_EQUAL(out.at(7).fee, 0u); // tx7 } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/query/address/address_outpoints.cpp b/test/query/address/address_outpoints.cpp index 0ce98582..18938b14 100644 --- a/test/query/address/address_outpoints.cpp +++ b/test/query/address/address_outpoints.cpp @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_confirmed_unspent_outputs__turbo_genesis outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_confirmed_unspent_outputs(cancel, out, test::genesis_address, true)); + BOOST_REQUIRE(!query.get_confirmed_unspent_outputs(cancel, out, test::genesis_address0, true)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_confirmed_unspent_outputs__genesis__expe outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_confirmed_unspent_outputs(cancel, out, test::genesis_address)); + BOOST_REQUIRE(!query.get_confirmed_unspent_outputs(cancel, out, test::genesis_address0)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } @@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__turbo_above__ex outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 5000000001, true)); + BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address0, 5000000001, true)); BOOST_REQUIRE(out.empty()); } @@ -84,7 +84,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__above__excluded outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 5000000001)); + BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address0, 5000000001)); BOOST_REQUIRE(out.empty()); } @@ -99,7 +99,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__at__included) outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 5000000000)); + BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address0, 5000000000)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } @@ -115,11 +115,11 @@ BOOST_AUTO_TEST_CASE(query_address__get_minimum_unspent_outputs__below__included outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 0)); + BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address0, 0)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); - BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address, 4999999999)); + BOOST_REQUIRE(!query.get_minimum_unspent_outputs(cancel, out, test::genesis_address0, 4999999999)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } @@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outputs__turbo_genesis__expected outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_address_outputs(cancel, out, test::genesis_address, true)); + BOOST_REQUIRE(!query.get_address_outputs(cancel, out, test::genesis_address0, true)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } @@ -153,7 +153,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outputs__genesis__expected) outpoints out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_address_outputs(cancel, out, test::genesis_address)); + BOOST_REQUIRE(!query.get_address_outputs(cancel, out, test::genesis_address0)); BOOST_REQUIRE_EQUAL(out.size(), 1u); BOOST_REQUIRE(*out.begin() == query.get_outpoint(query.to_output(0, 0))); } @@ -169,7 +169,7 @@ BOOST_AUTO_TEST_CASE(query_address__get_address_outputs__cancel__canceled_false) outpoints out{}; std::atomic_bool cancel{ true }; - BOOST_REQUIRE_EQUAL(query.get_address_outputs(cancel, out, test::genesis_address), error::canceled); + BOOST_REQUIRE_EQUAL(query.get_address_outputs(cancel, out, test::genesis_address0), error::canceled); BOOST_REQUIRE(out.empty()); } diff --git a/test/query/address/address_unspent.cpp b/test/query/address/address_unspent.cpp index dfb8c3e6..b0c8e0f1 100644 --- a/test/query/address/address_unspent.cpp +++ b/test/query/address/address_unspent.cpp @@ -26,8 +26,6 @@ BOOST_FIXTURE_TEST_SUITE(query_address_tests, test::directory_setup_fixture) // get_confirmed_unspent // get_unspent -const auto genesis_address = test::genesis_address; - BOOST_AUTO_TEST_CASE(query_address__get_unspent__turbo_genesis__expected) { settings settings{}; @@ -39,15 +37,15 @@ BOOST_AUTO_TEST_CASE(query_address__get_unspent__turbo_genesis__expected) unspents out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_unconfirmed_unspent(cancel, out, genesis_address, true)); + BOOST_REQUIRE(!query.get_unconfirmed_unspent(cancel, out, test::genesis_address0, true)); BOOST_REQUIRE(out.empty()); out.clear(); - BOOST_REQUIRE(!query.get_confirmed_unspent(cancel, out, genesis_address, true)); + BOOST_REQUIRE(!query.get_confirmed_unspent(cancel, out, test::genesis_address0, true)); BOOST_REQUIRE(out.empty()); out.clear(); - BOOST_REQUIRE(!query.get_unspent(cancel, out, genesis_address, true)); + BOOST_REQUIRE(!query.get_unspent(cancel, out, test::genesis_address0, true)); BOOST_REQUIRE(out.empty()); } @@ -62,15 +60,15 @@ BOOST_AUTO_TEST_CASE(query_address__get_unspent__genesis__expected) unspents out{}; const std::atomic_bool cancel{}; - BOOST_REQUIRE(!query.get_unconfirmed_unspent(cancel, out, genesis_address)); + BOOST_REQUIRE(!query.get_unconfirmed_unspent(cancel, out, test::genesis_address0)); BOOST_REQUIRE(out.empty()); out.clear(); - BOOST_REQUIRE(!query.get_confirmed_unspent(cancel, out, genesis_address)); + BOOST_REQUIRE(!query.get_confirmed_unspent(cancel, out, test::genesis_address0)); BOOST_REQUIRE(out.empty()); out.clear(); - BOOST_REQUIRE(!query.get_unspent(cancel, out, genesis_address)); + BOOST_REQUIRE(!query.get_unspent(cancel, out, test::genesis_address0)); BOOST_REQUIRE(out.empty()); } diff --git a/test/types.cpp b/test/types.cpp index c640a972..519f5c95 100644 --- a/test/types.cpp +++ b/test/types.cpp @@ -174,8 +174,8 @@ BOOST_AUTO_TEST_CASE(history__sort_and_dedup__exclusions__removes_excluded_items { std::vector items { - history{ { hash_digest{}, 1 }, 0, max_size_t }, // excluded - history{ { hash_digest{}, 2 }, 0, max_size_t }, // excluded + history{ checkpoint{}, 0, max_size_t }, // excluded (default checkpoint) + history{ checkpoint{}, 0, max_size_t }, // excluded (default checkpoint) history{ { hash_digest{}, 3 }, 0, 10 }, // valid history{ { hash_digest{}, 3 }, 0, 5 }, // valid (same height, lower position) history{ { hash_digest{}, 3 }, 0, 10 } // duplicate @@ -213,11 +213,11 @@ BOOST_AUTO_TEST_CASE(unspent__sort_and_dedup__exclusions__removes_excluded_items { unspents items { - unspent{ { {}, 1 }, 10, max_size_t }, // excluded - unspent{ { {}, 2 }, 200, max_size_t }, // excluded - unspent{ { {}, 3 }, 50, 10 }, // valid confirmed - unspent{ { {}, 4 }, 50, 5 }, // valid confirmed (same height, lower position) - unspent{ { {}, 3 }, 50, 10 } // duplicate + unspent{ outpoint{}, 10, max_size_t }, // excluded (default outpoint) + unspent{ outpoint{}, 200, max_size_t }, // excluded (default outpoint) + unspent{ { {}, 3 }, 50, 10 }, // valid confirmed + unspent{ { {}, 4 }, 50, 5 }, // valid confirmed (same height, lower position) + unspent{ { {}, 3 }, 50, 10 } // duplicate }; unspent::sort_and_dedup(items);