diff --git a/include/bitcoin/node/chasers/chaser_validate.hpp b/include/bitcoin/node/chasers/chaser_validate.hpp index eb595996..cbb8503c 100644 --- a/include/bitcoin/node/chasers/chaser_validate.hpp +++ b/include/bitcoin/node/chasers/chaser_validate.hpp @@ -154,7 +154,6 @@ class BCN_API chaser_validate // These are thread safe. const uint32_t subsidy_interval_; const uint64_t initial_subsidy_; - const size_t silent_start_height_; const size_t maximum_backlog_; const size_t maximum_height_; const uint64_t batch_target_; diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index d4e5b508..09cbdd9d 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -120,6 +120,7 @@ enum error_t : uint8_t transaction3, transaction4, transaction5, + transaction6, estimates_initialize, estimates_push1, estimates_push2, diff --git a/src/chasers/chaser_transaction.cpp b/src/chasers/chaser_transaction.cpp index 071b7496..c06559c1 100644 --- a/src/chasers/chaser_transaction.cpp +++ b/src/chasers/chaser_transaction.cpp @@ -185,10 +185,19 @@ void chaser_transaction::do_submit(const transactions_cptr& txs, bool test, return; } - if (!pooled && !query.set_pooled(link, tx, pool_)) + if (!pooled) { - handler(fault(error::transaction2), index); - return; + if (!query.set_pooled(link, tx, pool_)) + { + handler(fault(error::transaction2), index); + return; + } + + if (query.silent_enabled() && !query.set_silent(link, tx)) + { + handler(fault(error::transaction6), index); + return; + } } fire(events::tx_archived, to_rate(tx)); @@ -233,29 +242,43 @@ void chaser_transaction::do_submit_compact(const transactions_cptr& txs, return; } - // Txs are an arbitrary subset of the block, so block checks are deferred to - // validation. Each is archived before the next, so in block order a parent - // is resolved from the store. - size_t index{}; - auto filled = links; - for (auto& fk: filled) + using link_t = database::tx_link::integer; + const auto filler = [&](link_t& fk, size_t index) NOEXCEPT { - if (fk != terminal) - continue; - database::tx_link out{}; if (const auto ec = fill(out, *txs->at(index), ctx)) { const auto faulted = node::error::error_category::contains(ec) || database::error::error_category::contains(ec); handler(faulted ? ec : unconfirmable(link, ctx.height, ec), index); - return; + return false; } fk = out; + return true; + }; + + // Txs are an arbitrary subset of the block, so block checks are deferred to + // validation. Each is archived before the next, so in block order a parent + // is resolved from the store. The coinbase (never a parent within its + // block) is archived last, as validation indexes only txs linked above it. + auto filled = links; + const auto deferred = filled.front() == terminal; + auto index = deferred ? one : zero; + for (auto fk = std::next(filled.begin()); fk != filled.end(); ++fk) + { + if (*fk != terminal) + continue; + + if (!filler(*fk, index)) + return; + ++index; } + if (deferred && !filler(filled.front(), zero)) + return; + const auto strong = is_under_checkpoint(ctx.height); if (const auto ec = query.set_code(link, filled, strong)) { @@ -301,8 +324,22 @@ code chaser_transaction::fill(database::tx_link& out, if (const auto ec = query.set_code(out, pooled, tx)) return fault(ec); - if (!pooled && !tx.is_coinbase() && !query.set_pooled(out, tx, ctx)) - return fault(error::transaction2); + const auto coinbase = tx.is_coinbase(); + if (!pooled && !coinbase) + { + if (!query.set_pooled(out, tx, ctx)) + return fault(error::transaction2); + + if (ctx.height >= query.silent_start_height() && + !query.set_silent(out, tx)) + return fault(error::transaction6); + } + + if (!coinbase) + { + fire(events::tx_archived, to_rate(tx)); + notify(error::success, chases::transaction{ out }); + } return error::success; } diff --git a/src/chasers/chaser_validate.cpp b/src/chasers/chaser_validate.cpp index 7602b4a2..53755d99 100644 --- a/src/chasers/chaser_validate.cpp +++ b/src/chasers/chaser_validate.cpp @@ -41,7 +41,6 @@ chaser_validate::chaser_validate(full_node& node) NOEXCEPT validation_strand_(validation_threadpool_.service().get_executor()), subsidy_interval_(node.system_settings().subsidy_interval_blocks), initial_subsidy_(node.system_settings().initial_subsidy()), - silent_start_height_(node.node_settings().silent_start_height), maximum_backlog_(node.node_settings().maximum_concurrency_()), maximum_height_(node.node_settings().maximum_height_()), batch_target_(node.node_settings().batch_signatures), @@ -210,13 +209,14 @@ void chaser_validate::do_bumped(height_t height) NOEXCEPT const auto bypass = is_under_checkpoint(height) || query.is_milestone(link); + const auto silent = height >= query.silent_start_height(); switch (ec.value()) { case database::error::unvalidated: case database::error::unknown_state: { - if (bypass && !filter_) + if (bypass && !filter_ && !silent) { complete_block(error::success, link, height, true); } diff --git a/src/chasers/chaser_validate_parallel.cpp b/src/chasers/chaser_validate_parallel.cpp index 50ce45e9..a2314cc4 100644 --- a/src/chasers/chaser_validate_parallel.cpp +++ b/src/chasers/chaser_validate_parallel.cpp @@ -102,7 +102,7 @@ code chaser_validate::populate(bool bypass, const chain::block& block, if (bypass) { - // Populating for filters only (no validation metadata required). + // Populating for optional indexes only (no validation metadata). block.populate(ctx); if (!query.populate_without_metadata(block)) return system::error::missing_previous_output; @@ -139,12 +139,12 @@ code chaser_validate::validate_pooled(bool& pooled, const header_link& link, error::validate11 : ec; } -// A pooled block is valid, so is produced only as required for filters. +// A pooled block is valid, so is produced only as required for indexes. code chaser_validate::complete_pooled(const header_link& link, const chain::context& ctx) NOEXCEPT { auto& query = archive(); - if (filter_ || (ctx.height >= silent_start_height_)) + if (filter_ || (ctx.height >= query.silent_start_height())) { bool batched{}, capturing{}; constexpr auto bypass = true; @@ -210,7 +210,7 @@ code chaser_validate::validate(bool& batched, bool& capturing, bool bypass, if (!query.set_filter_body(link, block)) return error::validate8; - if ((ctx.height >= silent_start_height_) && + if ((ctx.height >= query.silent_start_height()) && !query.set_silent(link, block)) return error::validate9; diff --git a/src/configuration.cpp b/src/configuration.cpp index 99aefd4a..42871f3a 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -33,9 +33,21 @@ configuration::configuration(system::chain::selection context) NOEXCEPT { } +// Silent payment indexing requires witness data, which a pruned store does not +// archive for checkpointed or milestoned (bypassed) blocks. code configuration::initialize() NOEXCEPT { - database.initialize(bitcoin, node.limited_blocks); + const auto silent = node.silent_start_height != max_uint32; + if (silent && !node.require_witness) + return network::error::invalid_configuration; + + const auto checkpoint = bitcoin.top_checkpoint().height(); + const auto milestone = bitcoin.milestone.height(); + const auto bypassed = std::max(checkpoint, milestone); + if (silent && node.limited_blocks && node.silent_start_height <= bypassed) + return network::error::invalid_configuration; + + database.initialize(bitcoin, node.limited_blocks, node.silent_start_height); return network.initialize(); } diff --git a/src/error.cpp b/src/error.cpp index 6ebcb224..87de915f 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -110,6 +110,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { transaction3, "transaction3" }, { transaction4, "transaction4" }, { transaction5, "transaction5" }, + { transaction6, "transaction6" }, { estimates_initialize, "estimates_initialize" }, { estimates_push1, "estimates_push1" }, { estimates_push2, "estimates_push2" }, diff --git a/test/configuration.cpp b/test/configuration.cpp index 35f8ec10..e83201d8 100644 --- a/test/configuration.cpp +++ b/test/configuration.cpp @@ -39,4 +39,47 @@ BOOST_AUTO_TEST_CASE(configuration__initialize__mainnet__success_peer_identifier BOOST_REQUIRE_EQUAL(instance.network.peer.identifier, 0xd9b4bef9_u32); } +BOOST_AUTO_TEST_CASE(configuration__initialize__silent_without_witness__invalid_configuration) +{ + node::configuration instance(chain::selection::mainnet); + instance.node.silent_start_height = 0; + instance.node.require_witness = false; + BOOST_REQUIRE_EQUAL(instance.initialize(), network::error::invalid_configuration); +} + +BOOST_AUTO_TEST_CASE(configuration__initialize__silent_with_witness__success) +{ + node::configuration instance(chain::selection::mainnet); + instance.node.silent_start_height = 0; + instance.node.require_witness = true; + BOOST_REQUIRE(!instance.initialize()); +} + +BOOST_AUTO_TEST_CASE(configuration__initialize__limited_silent_at_bypassed__invalid_configuration) +{ + node::configuration instance(chain::selection::mainnet); + const auto bypassed = std::max(instance.bitcoin.top_checkpoint().height(), instance.bitcoin.milestone.height()); + instance.node.limited_blocks = true; + instance.node.silent_start_height = possible_narrow_cast(bypassed); + BOOST_REQUIRE_EQUAL(instance.initialize(), network::error::invalid_configuration); +} + +BOOST_AUTO_TEST_CASE(configuration__initialize__limited_silent_above_bypassed__success) +{ + node::configuration instance(chain::selection::mainnet); + const auto bypassed = std::max(instance.bitcoin.top_checkpoint().height(), instance.bitcoin.milestone.height()); + instance.node.limited_blocks = true; + instance.node.silent_start_height = possible_narrow_cast(add1(bypassed)); + BOOST_REQUIRE(!instance.initialize()); +} + +BOOST_AUTO_TEST_CASE(configuration__initialize__unlimited_silent_at_bypassed__success) +{ + node::configuration instance(chain::selection::mainnet); + const auto bypassed = std::max(instance.bitcoin.top_checkpoint().height(), instance.bitcoin.milestone.height()); + instance.node.limited_blocks = false; + instance.node.silent_start_height = possible_narrow_cast(bypassed); + BOOST_REQUIRE(!instance.initialize()); +} + BOOST_AUTO_TEST_SUITE_END()