From ce33a1c07526f065e72ae28910373c577e0564d3 Mon Sep 17 00:00:00 2001 From: echennells Date: Mon, 28 Sep 2026 15:41:24 +0000 Subject: [PATCH] Seed the address pool when an outbound connection finds no address. --- include/bitcoin/network/net.hpp | 3 + .../bitcoin/network/sessions/session_peer.hpp | 3 + .../bitcoin/network/sessions/session_seed.hpp | 11 + src/net.cpp | 42 +- src/sessions/session_outbound.cpp | 1 + src/sessions/session_peer.cpp | 5 + src/sessions/session_seed.cpp | 16 + test/net.cpp | 271 ++++++++++++ test/sessions/session_outbound.cpp | 71 +++ test/sessions/session_seed.cpp | 412 ++++++++++++++++++ 10 files changed, 833 insertions(+), 2 deletions(-) diff --git a/include/bitcoin/network/net.hpp b/include/bitcoin/network/net.hpp index 4a8e2ac30..ee3812f1d 100644 --- a/include/bitcoin/network/net.hpp +++ b/include/bitcoin/network/net.hpp @@ -283,6 +283,7 @@ class BCT_API net virtual void fetch(address_handler&& handler) NOEXCEPT; virtual void save(const address_cptr& message, count_handler&& complete) NOEXCEPT; + virtual void seed() NOEXCEPT; /// P2P loopback detection. virtual bool store_nonce(const channel_peer& channel) NOEXCEPT; @@ -344,6 +345,7 @@ class BCT_API net void do_fetch(const address_handler& handler) NOEXCEPT; void do_save(const address_cptr& message, const count_handler& handler) NOEXCEPT; + void do_seed() NOEXCEPT; // P2P diagnostic accumulators. void do_fetch_totals(const totals_handler& handler) NOEXCEPT; @@ -362,6 +364,7 @@ class BCT_API net // These are protected by strand. session_manual::ptr manual_{}; + session_seed::ptr seed_{}; threadpool threadpool_; // This is thread safe. diff --git a/include/bitcoin/network/sessions/session_peer.hpp b/include/bitcoin/network/sessions/session_peer.hpp index 74f589599..a51da3540 100644 --- a/include/bitcoin/network/sessions/session_peer.hpp +++ b/include/bitcoin/network/sessions/session_peer.hpp @@ -57,6 +57,9 @@ class BCT_API session_peer virtual void save(const address_cptr& message, count_handler&& handler) const NOEXCEPT; + /// Seed the address pool if below minimum, unless seeding or backing off. + virtual void seed() const NOEXCEPT; + /// Properties. /// ----------------------------------------------------------------------- diff --git a/include/bitcoin/network/sessions/session_seed.hpp b/include/bitcoin/network/sessions/session_seed.hpp index 03e56ae14..4c5e82860 100644 --- a/include/bitcoin/network/sessions/session_seed.hpp +++ b/include/bitcoin/network/sessions/session_seed.hpp @@ -47,6 +47,13 @@ class BCT_API session_seed /// Seeding is complete invocation of the handler. void start(result_handler&& handler) NOEXCEPT override; + /// Seed connections are running (call from network strand). + virtual bool seeding() const NOEXCEPT; + + /// Time seed connections last completed, zero if never (call from network + /// strand). + virtual steady_clock::time_point completed() const NOEXCEPT; + protected: /// The configured options for this peer session (covariant). const options_t& options() const NOEXCEPT override; @@ -77,6 +84,10 @@ class BCT_API session_seed const channel::ptr& channel) NOEXCEPT; void handle_channel_stop(const code& ec, const channel::ptr& channel, const race::ptr& racer) NOEXCEPT; + + // These are protected by strand. + steady_clock::time_point completed_{}; + bool seeding_{}; }; } // namespace network diff --git a/src/net.cpp b/src/net.cpp index 17eec7592..4a10ee9fd 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -38,6 +38,9 @@ using namespace system; using namespace std::placeholders; constexpr auto encryption = messages::peer::service::node_encrypted_transport; +// Seeding is not repeated within this many seeding timeouts of completion. +constexpr auto seed_backoff = 10; + net::net(const settings& settings, const logger& log, uint64_t required_services, uint64_t provided_services) NOEXCEPT : settings_(settings), @@ -217,7 +220,8 @@ void net::handle_start(const code& ec, const result_handler& handler) NOEXCEPT return; } - attach_seed_session()->start(move_copy(handler)); + seed_ = attach_seed_session(); + seed_->start(move_copy(handler)); } // Run sequence (seeding may be ongoing after its handler is invoked). @@ -296,8 +300,9 @@ void net::do_close() NOEXCEPT { BC_ASSERT(stranded()); - // Release reference to manual session (also held by stop subscriber). + // Release session references (also held by stop subscriber). if (manual_) manual_.reset(); + if (seed_) seed_.reset(); // Notify and delete all stop subscribers (all sessions). stop_subscriber_.stop(error::service_stopped); @@ -703,6 +708,39 @@ void net::do_save(const address_cptr& message, hosts_.save(message, move_copy(handler)); } +void net::seed() NOEXCEPT +{ + boost::asio::post(strand_, + std::bind(&net::do_seed, this)); +} + +void net::do_seed() NOEXCEPT +{ + BC_ASSERT(stranded()); + + // address_not_found may only mean that no pooled address suits the slot, + // so seed only below the minimum (and never without seeds). + const auto& outbound = network_settings().outbound; + if (closed() || outbound.seeds.empty() || + address_count() >= outbound.minimum_address_count()) + return; + + // The startup seed session is retained before outbound can seed. + BC_ASSERT(seed_); + + // One seed session at a time, and none soon after one completes. + const auto completed = seed_->completed(); + const auto backoff = outbound.seeding_timeout() * seed_backoff; + + if (seed_->seeding() || (completed != steady_clock::time_point{} && + (steady_clock::now() - completed) < backoff)) + return; + + // The handler signals sufficiency, seeding ends upon completion. + seed_ = attach_seed_session(); + seed_->start([](const code&) NOEXCEPT {}); +} + // P2P self address. // ---------------------------------------------------------------------------- diff --git a/src/sessions/session_outbound.cpp b/src/sessions/session_outbound.cpp index aa4985e10..31c13e3c6 100644 --- a/src/sessions/session_outbound.cpp +++ b/src/sessions/session_outbound.cpp @@ -263,6 +263,7 @@ void session_outbound::handle_connect(const code& ec, if (ec == error::address_not_found) { LOGS("Address pool is empty."); + seed(); defer(network_settings().connect_timeout(options()), BIND(start_connect, _1, slot)); return; diff --git a/src/sessions/session_peer.cpp b/src/sessions/session_peer.cpp index aa5db6515..5b35fe98b 100644 --- a/src/sessions/session_peer.cpp +++ b/src/sessions/session_peer.cpp @@ -284,6 +284,11 @@ void session_peer::save(const address_cptr& message, network_.save(message, std::move(handler)); } +void session_peer::seed() const NOEXCEPT +{ + network_.seed(); +} + BC_POP_WARNING() BC_POP_WARNING() BC_POP_WARNING() diff --git a/src/sessions/session_seed.cpp b/src/sessions/session_seed.cpp index 8637c8ee5..a9cb15717 100644 --- a/src/sessions/session_seed.cpp +++ b/src/sessions/session_seed.cpp @@ -52,6 +52,18 @@ const session_seed::options_t& session_seed::options() const NOEXCEPT return static_cast(session_peer::options()); } +bool session_seed::seeding() const NOEXCEPT +{ + BC_ASSERT(stranded()); + return seeding_; +} + +steady_clock::time_point session_seed::completed() const NOEXCEPT +{ + BC_ASSERT(stranded()); + return completed_; +} + // Start/stop sequence. // ---------------------------------------------------------------------------- @@ -122,6 +134,8 @@ void session_seed::handle_started(const code& ec, const auto racer = std::make_shared(seeds, required); BC_POP_WARNING() + seeding_ = true; + // Invoke sufficient on count, invoke complete with all seeds stopped. racer->start(move_copy(handler), BIND(stop_seed, _1)); @@ -278,6 +292,8 @@ void session_seed::stop_seed(const code&) NOEXCEPT BC_ASSERT(stranded()); LOGN("Seed session complete."); + completed_ = steady_clock::now(); + seeding_ = false; unsubscribe_close(); } diff --git a/test/net.cpp b/test/net.cpp index b7407d243..ecf8cce95 100644 --- a/test/net.cpp +++ b/test/net.cpp @@ -1067,4 +1067,275 @@ BOOST_AUTO_TEST_CASE(net__subscribe_close__stranded__subscribed_then_stopped) BOOST_REQUIRE_EQUAL(promise.get_future().get(), error::service_stopped); } +// seed + +class mock_session_seed_complete + : public session_seed +{ +public: + using session_seed::session_seed; + + void start(result_handler&& handler) NOEXCEPT override + { + handler(error::success); + } +}; + +// Sufficient upon start, with seed connections still running. +class mock_session_seed_running + : public session_seed +{ +public: + using session_seed::session_seed; + + void start(result_handler&& handler) NOEXCEPT override + { + handler(error::success); + } + + bool seeding() const NOEXCEPT override + { + return true; + } +}; + +// Seed connections completed just now. +class mock_session_seed_completed_recently + : public session_seed +{ +public: + using session_seed::session_seed; + + void start(result_handler&& handler) NOEXCEPT override + { + handler(error::success); + } + + steady_clock::time_point completed() const NOEXCEPT override + { + return steady_clock::now(); + } +}; + +// Seed connections completed long before any backoff. +class mock_session_seed_completed_long_ago + : public session_seed +{ +public: + using session_seed::session_seed; + + void start(result_handler&& handler) NOEXCEPT override + { + handler(error::success); + } + + steady_clock::time_point completed() const NOEXCEPT override + { + return steady_clock::now() - std::chrono::hours{ 1 }; + } +}; + +template +class mock_net_seed + : public net +{ +public: + using net::net; + + size_t seed_sessions() const NOEXCEPT + { + return seed_sessions_; + } + + // Start, which attaches and retains the startup seed session. + code start_and_wait() NOEXCEPT + { + std::promise started; + start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + + return started.get_future().get(); + } + + // Seed sessions attached after the startup seed session. + size_t reseeds() const NOEXCEPT + { + return system::floored_subtract(seed_sessions_.load(), one); + } + + // The seed is posted to the strand, so a subsequent post follows it. + void seed_and_wait() NOEXCEPT + { + seed(); + on_strand(*this, []() NOEXCEPT { return true; }); + } + + code start_hosts() NOEXCEPT override + { + return error::success; + } + + session_manual::ptr attach_manual_session() NOEXCEPT override + { + return attach(*this); + } + + // The first seed session is the startup session. + session_seed::ptr attach_seed_session() NOEXCEPT override + { + if (is_one(++seed_sessions_)) + return attach(*this); + + return attach(*this); + } + +private: + std::atomic seed_sessions_{}; + + class mock_session_manual + : public session_manual + { + public: + using session_manual::session_manual; + + void start(result_handler&& handler) NOEXCEPT override + { + handler(error::success); + } + }; +}; + +template +class closed_mock_net_seed + : public mock_net_seed +{ +public: + using mock_net_seed::mock_net_seed; + + bool closed() const NOEXCEPT override + { + return true; + } +}; + +// The pool holds the minimum, though none may be usable for a slot. +template +class sufficient_mock_net_seed + : public mock_net_seed +{ +public: + using mock_net_seed::mock_net_seed; + + size_t address_count() const NOEXCEPT override + { + return this->network_settings().outbound.minimum_address_count(); + } +}; + +BOOST_AUTO_TEST_CASE(net__seed__once__one_session) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 1u); +} + +BOOST_AUTO_TEST_CASE(net__seed__startup_running__no_session) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 0u); +} + +BOOST_AUTO_TEST_CASE(net__seed__twice_running__one_session) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 1u); +} + +BOOST_AUTO_TEST_CASE(net__seed__twice_complete__two_sessions) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 2u); +} + +BOOST_AUTO_TEST_CASE(net__seed__no_seeds__no_session) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.seeds.clear(); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 0u); +} + +BOOST_AUTO_TEST_CASE(net__seed__sufficient_addresses__no_session) +{ + const logger log{}; + const settings set(selection::mainnet); + sufficient_mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 0u); +} + +BOOST_AUTO_TEST_CASE(net__seed__startup_completed_recently__no_session) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 0u); +} + +BOOST_AUTO_TEST_CASE(net__seed__twice_completed_recently__one_session) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 1u); +} + +BOOST_AUTO_TEST_CASE(net__seed__twice_completed_long_ago__two_sessions) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net_seed net(set, log); + BOOST_REQUIRE_EQUAL(net.start_and_wait(), error::success); + net.seed_and_wait(); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.reseeds(), 2u); +} + +BOOST_AUTO_TEST_CASE(net__seed__closed__no_session) +{ + const logger log{}; + const settings set(selection::mainnet); + closed_mock_net_seed net(set, log); + net.seed_and_wait(); + BOOST_REQUIRE_EQUAL(net.seed_sessions(), 0u); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/sessions/session_outbound.cpp b/test/sessions/session_outbound.cpp index 0a503655e..d3f1e5c3c 100644 --- a/test/sessions/session_outbound.cpp +++ b/test/sessions/session_outbound.cpp @@ -229,6 +229,34 @@ class mock_session_outbound_one_address } }; +class mock_session_outbound_no_address + : public mock_session_outbound_one_address_count +{ +public: + typedef std::shared_ptr ptr; + + using mock_session_outbound_one_address_count:: + mock_session_outbound_one_address_count; + + void take(hosts::family, address_item_handler&& handler) const NOEXCEPT override + { + handler(error::address_not_found, {}); + } + + void seed() const NOEXCEPT override + { + seeded_ = true; + } + + bool seeded() const NOEXCEPT + { + return seeded_; + } + +private: + mutable std::atomic_bool seeded_{ false }; +}; + class mock_session_outbound_groups : public mock_session_outbound_one_address { @@ -610,6 +638,49 @@ BOOST_AUTO_TEST_CASE(session_outbound__start__no_address_count__address_not_foun BOOST_REQUIRE(session->stopped()); } +BOOST_AUTO_TEST_CASE(session_outbound__start__no_address__seeded) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.host_pool_capacity = 1; + set.outbound.connect_batch_size = 1; + set.outbound.connections = 1; + set.outbound.connect_timeout_seconds = 10000; + mock_net<> net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::success); + + std::promise seed; + boost::asio::post(net.strand(), [=, &seed]() NOEXCEPT + { + seed.set_value(session->seeded()); + }); + + const auto seeded = seed.get_future().get(); + + std::promise stopped; + boost::asio::post(net.strand(), [=, &stopped]() NOEXCEPT + { + session->stop(); + stopped.set_value(true); + }); + + BOOST_REQUIRE(stopped.get_future().get()); + BOOST_REQUIRE(session->stopped()); + BOOST_REQUIRE(seeded); +} + BOOST_AUTO_TEST_CASE(session_outbound__start__restart__operation_failed) { const logger log{}; diff --git a/test/sessions/session_seed.cpp b/test/sessions/session_seed.cpp index 68ce0db97..2a70f2532 100644 --- a/test/sessions/session_seed.cpp +++ b/test/sessions/session_seed.cpp @@ -117,6 +117,56 @@ class mock_connector_connect_fail } }; +class mock_connector_connect_pending + : public mock_connector_connect_success +{ +public: + typedef std::shared_ptr ptr; + + using mock_connector_connect_success::mock_connector_connect_success; + + void start(const std::string&, uint16_t, const config::address&, + const config::endpoint&, socket_handler&& handler) NOEXCEPT override + { + handler_ = std::move(handler); + } + + // The pending connect is canceled upon stop. + void stop() NOEXCEPT override + { + if (handler_) + { + const auto handler = std::move(handler_); + handler_ = {}; + boost::asio::post(strand_, [=]() NOEXCEPT + { + handler(error::operation_canceled, nullptr); + }); + } + + mock_connector_connect_success::stop(); + } + +private: + socket_handler handler_{}; +}; + +class mock_connector_connect_suspended + : public mock_connector_connect_success +{ +public: + typedef std::shared_ptr ptr; + + using mock_connector_connect_success::mock_connector_connect_success; + + // A suspended connector invokes its handler without posting. + void start(const std::string&, uint16_t, const config::address&, + const config::endpoint&, socket_handler&& handler) NOEXCEPT override + { + handler(error::service_suspended, nullptr); + } +}; + class mock_session_seed : public session_seed { @@ -218,6 +268,32 @@ class mock_session_seed_increasing_address_count mutable size_t count_{ zero }; }; +class mock_session_seed_first_fails + : public mock_session_seed_increasing_address_count +{ +public: + using mock_session_seed_increasing_address_count:: + mock_session_seed_increasing_address_count; + + // Fail the first seed, so the race is sufficient as the others connect. + void start_seed(const code& ec, const config::endpoint& seed, + const connector::ptr& connector, + const socket_handler& handler) NOEXCEPT override + { + if (!failed_.exchange(true)) + { + handler(error::invalid_magic, nullptr); + return; + } + + mock_session_seed_increasing_address_count::start_seed(ec, seed, + connector, handler); + } + +private: + std::atomic_bool failed_{ false }; +}; + template class mock_net : public net @@ -727,6 +803,342 @@ BOOST_AUTO_TEST_CASE(session_seed__start__not_seeded__seeding_unsuccessful) BOOST_REQUIRE(session->stopped()); } +// seeding + +BOOST_AUTO_TEST_CASE(session_seed__seeding__not_started__false) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise seeding; + boost::asio::post(net.strand(), [=, &seeding]() NOEXCEPT + { + seeding.set_value(session->seeding()); + }); + + BOOST_REQUIRE(!seeding.get_future().get()); +} + +BOOST_AUTO_TEST_CASE(session_seed__seeding__no_outbound__false) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 0; + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::success); + + std::promise seeding; + boost::asio::post(net.strand(), [=, &seeding]() NOEXCEPT + { + seeding.set_value(session->seeding()); + }); + + BOOST_REQUIRE(!seeding.get_future().get()); +} + +BOOST_AUTO_TEST_CASE(session_seed__seeding__connect_fail__false) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 1; + set.outbound.host_pool_capacity = 1; + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::seeding_unsuccessful); + + std::promise seeding; + boost::asio::post(net.strand(), [=, &seeding]() NOEXCEPT + { + seeding.set_value(session->seeding()); + }); + + BOOST_REQUIRE(!seeding.get_future().get()); + + std::promise stopped; + boost::asio::post(net.strand(), [=, &stopped]() NOEXCEPT + { + session->stop(); + stopped.set_value(true); + }); + + BOOST_REQUIRE(stopped.get_future().get()); + BOOST_REQUIRE(session->stopped()); +} + +BOOST_AUTO_TEST_CASE(session_seed__completed__not_started__zero) +{ + const logger log{}; + const settings set(selection::mainnet); + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise completed; + boost::asio::post(net.strand(), [=, &completed]() NOEXCEPT + { + completed.set_value(session->completed()); + }); + + BOOST_REQUIRE(completed.get_future().get() == steady_clock::time_point{}); +} + +BOOST_AUTO_TEST_CASE(session_seed__completed__no_outbound__zero) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 0; + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::success); + + std::promise completed; + boost::asio::post(net.strand(), [=, &completed]() NOEXCEPT + { + completed.set_value(session->completed()); + }); + + BOOST_REQUIRE(completed.get_future().get() == steady_clock::time_point{}); +} + +BOOST_AUTO_TEST_CASE(session_seed__completed__connect_fail__nonzero) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 1; + set.outbound.host_pool_capacity = 1; + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::seeding_unsuccessful); + + std::promise completed; + boost::asio::post(net.strand(), [=, &completed]() NOEXCEPT + { + completed.set_value(session->completed()); + }); + + BOOST_REQUIRE(completed.get_future().get() != steady_clock::time_point{}); + + std::promise stopped; + boost::asio::post(net.strand(), [=, &stopped]() NOEXCEPT + { + session->stop(); + stopped.set_value(true); + }); + + BOOST_REQUIRE(stopped.get_future().get()); + BOOST_REQUIRE(session->stopped()); +} + +BOOST_AUTO_TEST_CASE(session_seed__seeding__connecting__true) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 1; + set.outbound.host_pool_capacity = 1; + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + std::promise seeding; + boost::asio::post(net.strand(), [=, &seeding]() NOEXCEPT + { + seeding.set_value(session->seeding()); + }); + + const auto connecting = seeding.get_future().get(); + + std::promise stopped; + boost::asio::post(net.strand(), [=, &stopped]() NOEXCEPT + { + session->stop(); + stopped.set_value(true); + }); + + BOOST_REQUIRE(stopped.get_future().get()); + BOOST_REQUIRE(session->stopped()); + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::seeding_unsuccessful); + BOOST_REQUIRE(connecting); +} + +BOOST_AUTO_TEST_CASE(session_seed__seeding__connecting_stopped__false) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 1; + set.outbound.host_pool_capacity = 1; + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + std::promise stopped; + boost::asio::post(net.strand(), [=, &stopped]() NOEXCEPT + { + session->stop(); + stopped.set_value(true); + }); + + BOOST_REQUIRE(stopped.get_future().get()); + BOOST_REQUIRE(session->stopped()); + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::seeding_unsuccessful); + + std::promise seeding; + boost::asio::post(net.strand(), [=, &seeding]() NOEXCEPT + { + seeding.set_value(session->seeding()); + }); + + BOOST_REQUIRE(!seeding.get_future().get()); +} + +BOOST_AUTO_TEST_CASE(session_seed__seeding__sufficient_connecting__true) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 1; + set.outbound.connect_batch_size = 1; + set.outbound.host_pool_capacity = 1; + set.outbound.seeds.resize(2); + BOOST_REQUIRE_EQUAL(set.outbound.minimum_address_count(), one); + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + std::promise seeding; + boost::asio::post(net.strand(), [=, &seeding]() NOEXCEPT + { + seeding.set_value(session->seeding()); + }); + + const auto connecting = seeding.get_future().get(); + + std::promise stopped; + boost::asio::post(net.strand(), [=, &stopped]() NOEXCEPT + { + session->stop(); + stopped.set_value(true); + }); + + BOOST_REQUIRE(stopped.get_future().get()); + BOOST_REQUIRE(session->stopped()); + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::success); + BOOST_REQUIRE(connecting); +} + +BOOST_AUTO_TEST_CASE(session_seed__seeding__connect_suspended__false) +{ + const logger log{}; + settings set(selection::mainnet); + set.outbound.connections = 1; + set.outbound.host_pool_capacity = 1; + mock_net net(set, log); + auto session = std::make_shared(net, 1); + BOOST_REQUIRE(session->stopped()); + + std::promise started; + boost::asio::post(net.strand(), [=, &started]() NOEXCEPT + { + session->start([&](const code& ec) NOEXCEPT + { + started.set_value(ec); + }); + }); + + BOOST_REQUIRE_EQUAL(started.get_future().get(), error::seeding_unsuccessful); + + std::promise seeding; + boost::asio::post(net.strand(), [=, &seeding]() NOEXCEPT + { + seeding.set_value(session->seeding()); + }); + + BOOST_REQUIRE(!seeding.get_future().get()); + + std::promise stopped; + boost::asio::post(net.strand(), [=, &stopped]() NOEXCEPT + { + session->stop(); + stopped.set_value(true); + }); + + BOOST_REQUIRE(stopped.get_future().get()); + BOOST_REQUIRE(session->stopped()); +} + ////BOOST_AUTO_TEST_CASE(session_seed__live__one_address__expected) ////{ //// const logger log{};