diff --git a/include/bitcoin/network/config/utilities.hpp b/include/bitcoin/network/config/utilities.hpp index 0c7712867..8700bfa6c 100644 --- a/include/bitcoin/network/config/utilities.hpp +++ b/include/bitcoin/network/config/utilities.hpp @@ -36,6 +36,10 @@ BCT_API asio::address from_address(const messages::peer::ip_address& address) NO BCT_API std::string to_normal_host(const std::string& host, uint16_t default_port) NOEXCEPT; +/// Get ascii lowered host:port form of a url, with default substitution. +BCT_API std::string to_normal_origin(const std::string& origin, + uint16_t default_port) NOEXCEPT; + } // namespace config } // namespace network } // namespace libbitcoin diff --git a/include/bitcoin/network/error.hpp b/include/bitcoin/network/error.hpp index bb4dd1f8f..d12fdaed4 100644 --- a/include/bitcoin/network/error.hpp +++ b/include/bitcoin/network/error.hpp @@ -348,6 +348,7 @@ enum error_t : uint8_t missing_parameter, // json-rpc error + jsonrpc_invalid_version, jsonrpc_requires_method, jsonrpc_v1_requires_params, jsonrpc_v1_requires_array_params, diff --git a/include/bitcoin/network/net.hpp b/include/bitcoin/network/net.hpp index 4a8e2ac30..41cf5220a 100644 --- a/include/bitcoin/network/net.hpp +++ b/include/bitcoin/network/net.hpp @@ -116,6 +116,9 @@ class BCT_API net /// Resume all P2P connection. virtual bool resume() NOEXCEPT; + /// Stop all service connections (not resumable). + virtual void stop_services(const code& ec) NOEXCEPT; + /// Properties. /// ----------------------------------------------------------------------- @@ -207,6 +210,11 @@ class BCT_API net return stop_subscriber_.size(); } + virtual size_t service_subscriber_count() const NOEXCEPT + { + return service_subscriber_.size(); + } + virtual size_t connect_subscriber_count() const NOEXCEPT { return connect_subscriber_.size(); @@ -241,6 +249,22 @@ class BCT_API net return session; } + /// Attach service session to network, caller must start (requires strand). + template + typename Session::ptr attach_service(Network& net, Args&&... args) NOEXCEPT + { + BC_ASSERT_MSG(stranded(), "subscribe_service"); + const auto session = attach(net, std::forward(args)...); + + subscribe_service([=](const code&) NOEXCEPT + { + session->stop(); + return false; + }, create_key()); + + return session; + } + /// Base session (no derived session access). /// ----------------------------------------------------------------------- friend class session; @@ -324,6 +348,9 @@ class BCT_API net void do_subscribe_close(const stop_handler& handler, const stop_completer& complete) NOEXCEPT; + code subscribe_service(stop_handler&& handler, object_key key) NOEXCEPT; + void do_stop_services(const code& ec) NOEXCEPT; + // P2P // ------------------------------------------------------------------------ @@ -373,6 +400,7 @@ class BCT_API net channel_ids actives_{}; broadcaster broadcaster_{}; stop_subscriber stop_subscriber_{}; + stop_subscriber service_subscriber_{}; channel_subscriber connect_subscriber_{}; std::unordered_set nonces_{}; }; diff --git a/include/bitcoin/network/net/acceptor.hpp b/include/bitcoin/network/net/acceptor.hpp index 5b1fcd057..b2a82a464 100644 --- a/include/bitcoin/network/net/acceptor.hpp +++ b/include/bitcoin/network/net/acceptor.hpp @@ -100,6 +100,10 @@ class BCT_API acceptor // These are protected by strand. asio::acceptor acceptor_; bool stopped_{ true }; + +private: + void do_handle_accept(const code& ec, const socket::ptr& socket, + const socket_handler& handler) NOEXCEPT; }; } // namespace network diff --git a/include/bitcoin/network/protocols/protocol_version_70002.hpp b/include/bitcoin/network/protocols/protocol_version_70002.hpp index 70a949b8f..bd19d0154 100644 --- a/include/bitcoin/network/protocols/protocol_version_70002.hpp +++ b/include/bitcoin/network/protocols/protocol_version_70002.hpp @@ -42,7 +42,7 @@ class BCT_API protocol_version_70002 /// Construct a version protocol instance using parameterized relay. protocol_version_70002(const session::ptr& session, - const channel::ptr& channel, bool relay) NOEXCEPT; + const channel::ptr& channel, bool relay, bool reject) NOEXCEPT; /// Perform the handshake (requires strand), handler invoked on completion. void shake(result_handler&& handle_event) NOEXCEPT override; @@ -52,6 +52,10 @@ class BCT_API protocol_version_70002 virtual bool handle_receive_reject(const code& ec, const messages::peer::reject::cptr& message) NOEXCEPT; + +private: + // This is thread safe. + const bool reject_; }; } // namespace network diff --git a/include/bitcoin/network/protocols/protocol_version_70014.hpp b/include/bitcoin/network/protocols/protocol_version_70014.hpp index 25382e247..201c33c80 100644 --- a/include/bitcoin/network/protocols/protocol_version_70014.hpp +++ b/include/bitcoin/network/protocols/protocol_version_70014.hpp @@ -52,10 +52,6 @@ class BCT_API protocol_version_70014 const messages::peer::version_acknowledge::cptr& message) NOEXCEPT override; virtual bool handle_receive_send_compact(const code& ec, const messages::peer::send_compact::cptr& message) NOEXCEPT; - -private: - // This is thread safe. - const bool reject_; }; } // namespace network diff --git a/src/channels/channel.cpp b/src/channels/channel.cpp index 6382d4721..6d12400c1 100644 --- a/src/channels/channel.cpp +++ b/src/channels/channel.cpp @@ -88,6 +88,7 @@ void channel::stopping(const code& ec) NOEXCEPT void channel::pause() NOEXCEPT { BC_ASSERT(stranded()); + BC_ASSERT(!held_); stop_expiration(); stop_inactivity(); diff --git a/src/config/credential.cpp b/src/config/credential.cpp index 079569f2f..f217e7a91 100644 --- a/src/config/credential.cpp +++ b/src/config/credential.cpp @@ -92,7 +92,7 @@ std::string credential::to_string() const NOEXCEPT std::istream& operator>>(std::istream& input, credential& argument) THROWS { std::string value{}; - input >> value; + std::getline(input, value); // Methods are optional, so a credential has two or three tokens. const auto tokens = split(value, ":", true, false); diff --git a/src/config/utilities.cpp b/src/config/utilities.cpp index 33869a585..05d556973 100644 --- a/src/config/utilities.cpp +++ b/src/config/utilities.cpp @@ -86,6 +86,20 @@ std::string to_normal_host(const std::string& host, } } +std::string to_normal_origin(const std::string& origin, + uint16_t default_port) NOEXCEPT +{ + try + { + const url url{ origin }; + return endpoint{ url.host(), url.port() }.to_lower(default_port); + } + catch (const std::exception&) + { + return {}; + } +} + } // namespace config } // namespace network } // namespace libbitcoin diff --git a/src/error.cpp b/src/error.cpp index 88e383e8c..88fc3acd9 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -337,6 +337,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { missing_parameter, "missing parameter" }, // json-rpc error + { jsonrpc_invalid_version, "jsonrpc invalid version" }, { jsonrpc_requires_method, "jsonrpc requires method" }, { jsonrpc_v1_requires_params, "jsonrpc v1 requires params" }, { jsonrpc_v1_requires_array_params, "jsonrpc v1 requires array params" }, diff --git a/src/messages/http_body.cpp b/src/messages/http_body.cpp index 0245b8201..bd3fa0984 100644 --- a/src/messages/http_body.cpp +++ b/src/messages/http_body.cpp @@ -40,6 +40,23 @@ void body::reader::init(const length_type& length, boost_code& ec) NOEXCEPT { ec = to_http_code(http_error_t::end_of_stream); }, + [&](file_reader& read) NOEXCEPT + { + if (!value_.get().is_open()) + { + ec = to_errc_code(boost_errc_t::bad_file_descriptor); + return; + } + + try + { + read.init(length, ec); + } + catch (...) + { + ec = to_http_code(http_error_t::end_of_stream); + } + }, [&](auto& read) NOEXCEPT { try @@ -132,16 +149,9 @@ body::writer::out_buffer body::writer::get(boost_code& ec) NOEXCEPT { [&] (std::monostate&) NOEXCEPT { - ec = {}; + ec = to_http_code(http_error_t::end_of_stream); return out_buffer{}; }, - [&](empty_writer&) NOEXCEPT - { - ec = {}; - - // Socket body writer requires non-empty buffer to write empty. - return out_buffer{ std::make_pair(asio::const_buffer{}, false) }; - }, [&](auto& write) NOEXCEPT { try diff --git a/src/messages/peer/detail/reject.cpp b/src/messages/peer/detail/reject.cpp index 0574aa542..d43953ca3 100644 --- a/src/messages/peer/detail/reject.cpp +++ b/src/messages/peer/detail/reject.cpp @@ -90,26 +90,26 @@ reject reject::deserialize(uint32_t, reader& source) NOEXCEPT const auto code = byte_to_reason(source.read_byte()); auto reason = source.read_string(max_reject_message); - const auto get_hash = [&message](reader& source) NOEXCEPT + auto hash = null_hash; + if (source && is_chain(message)) { - if (!source || !is_chain(message)) - return null_hash; + const auto position = source.get_read_position(); + hash = source.read_hash(); // Some nodes don't follow documented convention of hash for tx/block. - // Reset fault that occurs on hash read to accomodate those slackers. - const auto hash = source.read_hash(); if (!source) - source.set_limit(); - - return hash; - }; + { + source.set_position(position); + hash = null_hash; + } + } return { std::move(message), code, std::move(reason), - get_hash(source) + hash }; } diff --git a/src/messages/rpc/body.cpp b/src/messages/rpc/body.cpp index 34206161d..994fff230 100644 --- a/src/messages/rpc/body.cpp +++ b/src/messages/rpc/body.cpp @@ -85,10 +85,12 @@ static void to_request(rpc::request& value, boost_code& ec) NOEXCEPT { // Primary exception type for parsing operations. ec = e.code(); + return; } catch (...) { ec = code{ error::jsonrpc_reader_exception }; + return; } // Set version default. @@ -96,7 +98,11 @@ static void to_request(rpc::request& value, boost_code& ec) NOEXCEPT value.message.jsonrpc = version::v1; // Post-parse semantic validation. - if (value.message.method.empty()) + if (value.message.jsonrpc == version::invalid) + { + ec = code{ error::jsonrpc_invalid_version }; + } + else if (value.message.method.empty()) { ec = code{ error::jsonrpc_requires_method }; } diff --git a/src/messages/rpc/model.cpp b/src/messages/rpc/model.cpp index 70880bbcc..5e2df90fa 100644 --- a/src/messages/rpc/model.cpp +++ b/src/messages/rpc/model.cpp @@ -130,6 +130,10 @@ DEFINE_JSON_FROM_TAG(value_t) auto& object = value.as_object(); for (const auto& pair: visit) object.emplace(pair.first, value_from(pair.second)); + }, + [&](const any_t&) THROWS + { + throw ostream_exception{ "any_t" }; } }, instance.value()); } @@ -381,7 +385,8 @@ DEFINE_JSON_TO_TAG(response_t) } // error - if (const auto it = object.find("error"); it != object.end()) + if (const auto it = object.find("error"); + it != object.end() && !it->value().is_null()) { result_t result{}; const auto& error = it->value().as_object(); diff --git a/src/net.cpp b/src/net.cpp index 17eec7592..2be3ae251 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -301,6 +301,7 @@ void net::do_close() NOEXCEPT // Notify and delete all stop subscribers (all sessions). stop_subscriber_.stop(error::service_stopped); + service_subscriber_.stop(error::service_stopped); // Notify and delete subscribers to channel notifications. connect_subscriber_.stop_default(error::service_stopped); @@ -344,6 +345,19 @@ bool net::resume() NOEXCEPT return true; } +void net::stop_services(const code& ec) NOEXCEPT +{ + boost::asio::post(strand_, + std::bind(&net::do_stop_services, this, ec)); +} + +void net::do_stop_services(const code& ec) NOEXCEPT +{ + BC_ASSERT(stranded()); + suspend_services(); + service_subscriber_.stop(ec); +} + bool net::suspended() const NOEXCEPT { return connect_suspended_.load() || accept_suspended_.load(); @@ -466,6 +480,12 @@ code net::subscribe_close(stop_handler&& handler, object_key key) NOEXCEPT return stop_subscriber_.subscribe(std::move(handler), key); } +code net::subscribe_service(stop_handler&& handler, object_key key) NOEXCEPT +{ + BC_ASSERT(stranded()); + return service_subscriber_.subscribe(std::move(handler), key); +} + // public void net::subscribe_close(stop_handler&& handler, stop_completer&& complete) NOEXCEPT diff --git a/src/net/acceptor.cpp b/src/net/acceptor.cpp index c6f04ac65..758b86ae4 100644 --- a/src/net/acceptor.cpp +++ b/src/net/acceptor.cpp @@ -164,7 +164,7 @@ void acceptor::accept(socket_handler&& handler) NOEXCEPT shared_from_this(), _1, socket, std::move(handler))); } -// private +// protected void acceptor::handle_accept(const code& ec, const socket::ptr& socket, const socket_handler& handler) NOEXCEPT { @@ -173,11 +173,20 @@ void acceptor::handle_accept(const code& ec, const socket::ptr& socket, if (!stranded()) { boost::asio::post(strand_, - std::bind(&acceptor::handle_accept, + std::bind(&acceptor::do_handle_accept, shared_from_this(), ec, socket, handler)); return; } + do_handle_accept(ec, socket, handler); +} + +// private +void acceptor::do_handle_accept(const code& ec, const socket::ptr& socket, + const socket_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + if (ec) { socket->stop(); diff --git a/src/net/socket_body.cpp b/src/net/socket_body.cpp index a8d002ced..222649e04 100644 --- a/src/net/socket_body.cpp +++ b/src/net/socket_body.cpp @@ -63,7 +63,7 @@ void socket::do_body_read(boost_code ec, size_t total, if (ec) { - const auto code = error::http_to_error_code(ec); + const auto code = error::rpc_to_error_code(ec); if (code == error::unknown) logx("body-read", ec); handler(code, total); return; @@ -103,7 +103,7 @@ void socket::do_body_read(boost_code ec, size_t total, if (ec) { - const auto code = error::http_to_error_code(ec); + const auto code = error::rpc_to_error_code(ec); if (code == error::unknown) logx("body-read", ec); handler(code, total); return; @@ -209,12 +209,6 @@ void socket::do_body_write(boost_code ec, size_t total, BC_ASSERT(stranded()); const auto buffer = ec ? write_state::out_buffer{} : out->writer.get(ec); - if (!buffer.has_value()) - { - handler(error::bad_stream, total); - return; - } - if (ec) { const auto code = error::http_to_error_code(ec); @@ -223,8 +217,9 @@ void socket::do_body_write(boost_code ec, size_t total, return; } - out->more = buffer.value().second; - const auto& data = buffer.value().first; + out->more = buffer.has_value() && buffer.value().second; + const auto data = buffer.has_value() ? + buffer.value().first : asio::const_buffer{}; // A ws message closes on the final chunk (finish = !more), so a body // that spans multiple get() passes is delivered as one multi-frame @@ -290,12 +285,6 @@ void socket::do_body_notify(boost_code ec, size_t total, BC_ASSERT(stranded()); const auto buffer = ec ? notify_state::out_buffer{} : out->writer.get(ec); - if (!buffer.has_value()) - { - handler(error::bad_stream, total); - return; - } - if (ec) { const auto code = error::http_to_error_code(ec); @@ -304,8 +293,9 @@ void socket::do_body_notify(boost_code ec, size_t total, return; } - out->more = buffer.value().second; - const auto& data = buffer.value().first; + out->more = buffer.has_value() && buffer.value().second; + const auto data = buffer.has_value() ? + buffer.value().first : asio::const_buffer{}; // TODO: derive websocket binary/text from body type mapping. async_write(data, false, !out->more, diff --git a/src/net/socket_http.cpp b/src/net/socket_http.cpp index 16c55385e..d112ba6e9 100644 --- a/src/net/socket_http.cpp +++ b/src/net/socket_http.cpp @@ -470,12 +470,6 @@ void socket::do_chunk_write(boost_code ec, size_t total, } const auto buffer = ec ? chunk_state::out_buffer{} : out->writer.get(ec); - if (!buffer.has_value()) - { - handler(error::bad_stream, total); - return; - } - if (ec) { const auto code = error::http_to_error_code(ec); @@ -484,6 +478,13 @@ void socket::do_chunk_write(boost_code ec, size_t total, return; } + if (!buffer.has_value()) + { + out->more = false; + handle_chunk_write({}, zero, total, out, handler); + return; + } + out->more = buffer.value().second; const auto& data = buffer.value().first; diff --git a/src/protocols/protocol_http.cpp b/src/protocols/protocol_http.cpp index fb7c452b7..86b74d793 100644 --- a/src/protocols/protocol_http.cpp +++ b/src/protocols/protocol_http.cpp @@ -364,7 +364,7 @@ bool protocol_http::is_allowed_origin(const fields& fields, return options_.allow_opaque_origin; return options_.origins.empty() || contains(options_.origin_names(), - config::to_normal_host(origin, default_port())); + config::to_normal_origin(origin, default_port())); } void protocol_http::add_common_headers(fields& fields, diff --git a/src/protocols/protocol_version_106.cpp b/src/protocols/protocol_version_106.cpp index a3928a857..a7279e335 100644 --- a/src/protocols/protocol_version_106.cpp +++ b/src/protocols/protocol_version_106.cpp @@ -251,7 +251,11 @@ bool protocol_version_106::handle_receive_acknowledge(const code& ec, // Premature or multiple verack disallowed (persists for channel life). if (!sent_version_ || received_acknowledge_) { - rejection(error::protocol_violation); + if (complete()) + stop(error::protocol_violation); + else + rejection(error::protocol_violation); + return false; } @@ -301,7 +305,11 @@ bool protocol_version_106::handle_receive_version(const code& ec, // Multiple version messages not allowed (persists for channel life). if (received_version_) { - rejection(error::protocol_violation); + if (complete()) + stop(error::protocol_violation); + else + rejection(error::protocol_violation); + return false; } diff --git a/src/protocols/protocol_version_70002.cpp b/src/protocols/protocol_version_70002.cpp index b2b1e688a..165056736 100644 --- a/src/protocols/protocol_version_70002.cpp +++ b/src/protocols/protocol_version_70002.cpp @@ -38,14 +38,15 @@ using namespace std::placeholders; protocol_version_70002::protocol_version_70002(const session::ptr& session, const channel::ptr& channel) NOEXCEPT : protocol_version_70002(session, channel, - session->network_settings().enable_relay) + session->network_settings().enable_relay, + session->network_settings().enable_reject) { } protocol_version_70002::protocol_version_70002(const session::ptr& session, - const channel::ptr& channel, - bool relay) NOEXCEPT + const channel::ptr& channel, bool relay, bool reject) NOEXCEPT : protocol_version_70001(session, channel, relay), + reject_(reject), tracker(session->log) { } @@ -60,7 +61,10 @@ void protocol_version_70002::shake(result_handler&& handle_event) NOEXCEPT if (started()) return; - SUBSCRIBE_CHANNEL(reject, handle_receive_reject, _1, _2); + if (reject_) + { + SUBSCRIBE_CHANNEL(reject, handle_receive_reject, _1, _2); + } protocol_version_70001::shake(std::move(handle_event)); } @@ -73,6 +77,12 @@ void protocol_version_70002::rejection(const code& ec) NOEXCEPT { BC_ASSERT_MSG(stranded(), "protocol_version_70002"); + if (!reject_) + { + protocol_version_70001::rejection(ec); + return; + } + // Handshake completion may result before completion of this send (okay). if (ec == error::peer_insufficient) { diff --git a/src/protocols/protocol_version_70014.cpp b/src/protocols/protocol_version_70014.cpp index 0f8655822..d408863f9 100644 --- a/src/protocols/protocol_version_70014.cpp +++ b/src/protocols/protocol_version_70014.cpp @@ -48,8 +48,7 @@ protocol_version_70014::protocol_version_70014(const session::ptr& session, protocol_version_70014::protocol_version_70014(const session::ptr& session, const channel::ptr& channel, bool relay, bool reject) NOEXCEPT - : protocol_version_70002(session, channel, relay), - reject_(reject), + : protocol_version_70002(session, channel, relay, reject), tracker(session->log) { } @@ -66,14 +65,7 @@ void protocol_version_70014::shake(result_handler&& handle_event) NOEXCEPT SUBSCRIBE_CHANNEL(send_compact, handle_receive_send_compact, _1, _2); - // Protocol versions are cumulative, but reject is optional. - if (reject_) - { - protocol_version_70002::shake(std::move(handle_event)); - return; - } - - protocol_version_70001::shake(std::move(handle_event)); + protocol_version_70002::shake(std::move(handle_event)); } // Outgoing [signal compact blocks version 2, low bandwidth (bip152)]. diff --git a/src/protocols/protocol_version_70016.cpp b/src/protocols/protocol_version_70016.cpp index 84938afa2..be1133ff3 100644 --- a/src/protocols/protocol_version_70016.cpp +++ b/src/protocols/protocol_version_70016.cpp @@ -82,10 +82,14 @@ void protocol_version_70016::signal() NOEXCEPT const auto& settings = network_settings(); if (settings.enable_address && settings.enable_address_v2 && settings.gossip_v2() && negotiated_version() >= level::bip155) + { SEND(send_address_v2{}, handle_send, _1); + } if (settings.enable_witness_tx && negotiated_version() >= level::bip339) + { SEND(witness_tx_id_relay{}, handle_send, _1); + } protocol_version_70002::signal(); } @@ -123,6 +127,14 @@ bool protocol_version_70016::handle_receive_send_address_v2(const code& ec, if (stopped(ec)) return false; + // Early send_address_v2. + if (is_zero(peer_version()->value)) + { + LOGR("Early send_address_v2 from [" << opposite() << "]."); + stop(error::protocol_violation); + return false; + } + // Late send_address_v2. if (complete_) { @@ -143,6 +155,14 @@ bool protocol_version_70016::handle_receive_witness_tx_id_relay(const code& ec, if (stopped(ec)) return false; + // Early witness_tx_id_relay. + if (is_zero(peer_version()->value)) + { + LOGR("Early witness_tx_id_relay from [" << opposite() << "]."); + stop(error::protocol_violation); + return false; + } + // Late witness_tx_id_relay. if (complete_) { diff --git a/src/sessions/session_seed.cpp b/src/sessions/session_seed.cpp index 8637c8ee5..d5381ee3a 100644 --- a/src/sessions/session_seed.cpp +++ b/src/sessions/session_seed.cpp @@ -203,8 +203,8 @@ void session_seed::attach_handshake(const channel::ptr& channel, // Protocol versions are cumulative, but reject is deprecated. else if (is_configured(level::bip61) && reject) - channel->attach(self, - relay)->shake(std::move(handler)); + channel->attach(self, relay, + reject)->shake(std::move(handler)); else if (is_configured(level::bip37)) channel->attach(self, diff --git a/test/channels/channel_rpc.cpp b/test/channels/channel_rpc.cpp index 4b7701b0b..38213fbaa 100644 --- a/test/channels/channel_rpc.cpp +++ b/test/channels/channel_rpc.cpp @@ -132,6 +132,11 @@ struct rpc_small_fixture } }; +struct rpc_directory_fixture + : test::directory_setup_fixture, rpc_loopback_fixture +{ +}; + BOOST_AUTO_TEST_SUITE(socket_rpc_tests) BOOST_FIXTURE_TEST_CASE(socket__rpc_read__request__expected, rpc_loopback_fixture) @@ -183,7 +188,7 @@ BOOST_FIXTURE_TEST_CASE(socket__rpc_read__trailing_comma__syntax, rpc_loopback_f BOOST_REQUIRE_EQUAL(rpc_await(result), error::syntax); } -BOOST_FIXTURE_TEST_CASE(socket__rpc_read__missing_method__failure, rpc_loopback_fixture) +BOOST_FIXTURE_TEST_CASE(socket__rpc_read__missing_method__jsonrpc_requires_method, rpc_loopback_fixture) { http::flat_buffer buffer{ 4096 }; rpc::request request{}; @@ -191,7 +196,7 @@ BOOST_FIXTURE_TEST_CASE(socket__rpc_read__missing_method__failure, rpc_loopback_ server->rpc_read(buffer, request, rpc_complete(result)); send(R"({"jsonrpc":"2.0","id":1,"params":[]})" "\n"); - BOOST_REQUIRE(rpc_await(result)); + BOOST_REQUIRE_EQUAL(rpc_await(result), error::jsonrpc_requires_method); } BOOST_FIXTURE_TEST_CASE(socket__rpc_read__buffer_smaller_than_request__expected, rpc_loopback_fixture) @@ -289,6 +294,22 @@ BOOST_FIXTURE_TEST_CASE(socket__rpc_notify__peer_closed__failure, rpc_loopback_f BOOST_REQUIRE(rpc_await(result)); } +BOOST_FIXTURE_TEST_CASE(socket__body_write__empty_file__success, rpc_directory_fixture) +{ + BOOST_REQUIRE(test::create(TEST_PATH)); + + boost_code ec{}; + http::file_value file{}; + file.open((TEST_PATH).c_str(), boost::beast::file_mode::read, ec); + BOOST_REQUIRE(!ec); + + http::response response{}; + response.body() = std::move(file); + const auto result = rpc_make_promise(); + server->body_write(std::move(response), rpc_complete(result)); + BOOST_REQUIRE_EQUAL(rpc_await(result), error::success); +} + BOOST_FIXTURE_TEST_CASE(socket__body_read__unreadable_body__end_of_stream, rpc_loopback_fixture) { http::flat_buffer buffer{ 4096 }; diff --git a/test/config/credential.cpp b/test/config/credential.cpp index f44e7528c..f29b85e4c 100644 --- a/test/config/credential.cpp +++ b/test/config/credential.cpp @@ -119,6 +119,14 @@ BOOST_AUTO_TEST_CASE(credential__to_string__methoded__round_trips) BOOST_REQUIRE_EQUAL(instance.to_string(), "username:password:getblock,getblockcount"); } +BOOST_AUTO_TEST_CASE(credential__construct__spaced_password__expected_digest) +{ + const credential instance{ "Aladdin:open sesame" }; + BOOST_REQUIRE_EQUAL(instance.username(), "Aladdin"); + BOOST_REQUIRE_EQUAL(instance.password(), "open sesame"); + BOOST_REQUIRE_EQUAL(instance.digest(), system::sha256_hash("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==")); +} + // operators BOOST_AUTO_TEST_CASE(credential__istream__methoded__expected) diff --git a/test/config/utilities.cpp b/test/config/utilities.cpp index 468595256..6a6d15fda 100644 --- a/test/config/utilities.cpp +++ b/test/config/utilities.cpp @@ -543,4 +543,51 @@ BOOST_AUTO_TEST_CASE(utilities__to_normal_host__numeric_nonzero_nonzero__numeric BOOST_REQUIRE_EQUAL(to_normal_host("127.0.0.1:80", 42), "127.0.0.1:80"); } +// to_normal_origin + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__empty__empty) +{ + BOOST_REQUIRE(to_normal_origin("", 42).empty()); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__no_scheme__default_port) +{ + BOOST_REQUIRE_EQUAL(to_normal_origin("Example.com", 42), "example.com:42"); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__http__default_port) +{ + BOOST_REQUIRE_EQUAL(to_normal_origin("http://Example.com", 42), "example.com:42"); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__https__default_port) +{ + BOOST_REQUIRE_EQUAL(to_normal_origin("https://example.com", 42), "example.com:42"); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__upper_scheme__empty) +{ + BOOST_REQUIRE(to_normal_origin("HTTPS://example.com", 42).empty()); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__tcp_scheme__default_port) +{ + BOOST_REQUIRE_EQUAL(to_normal_origin("tcp://example.com", 42), "example.com:42"); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__explicit_port__explicit_port) +{ + BOOST_REQUIRE_EQUAL(to_normal_origin("http://127.0.0.1:8080", 42), "127.0.0.1:8080"); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__ipv6__expected) +{ + BOOST_REQUIRE_EQUAL(to_normal_origin("https://[::1]", 42), "[::1]:42"); +} + +BOOST_AUTO_TEST_CASE(utilities__to_normal_origin__scheme_only__empty) +{ + BOOST_REQUIRE(to_normal_origin("http://", 42).empty()); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/messages/http_body_reader.cpp b/test/messages/http_body_reader.cpp index 5106428dd..2f4257451 100644 --- a/test/messages/http_body_reader.cpp +++ b/test/messages/http_body_reader.cpp @@ -104,33 +104,33 @@ BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stre BOOST_REQUIRE(std::holds_alternative(reader.reader_)); } -// TODO: linux debug boost asserts on body_.file_.is_open(). -////BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stream_with_attachment__constructs_file_reader) -////{ -//// message_header header{}; -//// header.set(http::field::content_type, "application/octet-stream"); -//// header.set(http::field::content_disposition, "filename=somenonsense.jpg"); -//// body::value_type value{}; -//// accessor reader(header, value); -//// length_type length{ max_size_t }; -//// boost_code ec{}; -//// reader.init(length, ec); -//// BOOST_REQUIRE(std::holds_alternative(reader.reader_)); -////} +BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stream_with_attachment__file_reader_bad_file_descriptor) +{ + message_header header{}; + header.set(http::field::content_type, "application/octet-stream"); + header.set(http::field::content_disposition, "filename=somenonsense.jpg"); + body::value_type value{}; + accessor reader(header, value); + length_type length{ max_size_t }; + boost_code ec{}; + reader.init(length, ec); + BOOST_REQUIRE(std::holds_alternative(reader.reader_)); + BOOST_REQUIRE(ec == error::to_errc_code(error::boost_errc_t::bad_file_descriptor)); +} -// TODO: linux debug boost asserts on body_.file_.is_open(). -////BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stream_with_dirty_attachment__constructs_file_reader) -////{ -//// message_header header{}; -//// header.set(http::field::content_type, "application/octet-stream"); -//// header.set(http::field::content_disposition, "dirty 42; filename* = somenonsense.jpg; some other nonsense"); -//// body::value_type value{}; -//// accessor reader(header, value); -//// length_type length{ max_size_t }; -//// boost_code ec{}; -//// reader.init(length, ec); -//// BOOST_REQUIRE(std::holds_alternative(reader.reader_)); -////} +BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stream_with_dirty_attachment__file_reader_bad_file_descriptor) +{ + message_header header{}; + header.set(http::field::content_type, "application/octet-stream"); + header.set(http::field::content_disposition, "dirty 42; filename* = somenonsense.jpg; some other nonsense"); + body::value_type value{}; + accessor reader(header, value); + length_type length{ max_size_t }; + boost_code ec{}; + reader.init(length, ec); + BOOST_REQUIRE(std::holds_alternative(reader.reader_)); + BOOST_REQUIRE(ec == error::to_errc_code(error::boost_errc_t::bad_file_descriptor)); +} BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_text_plain__constructs_string_reader) { diff --git a/test/messages/peer/detail/reject.cpp b/test/messages/peer/detail/reject.cpp index 8af142307..7f3d98293 100644 --- a/test/messages/peer/detail/reject.cpp +++ b/test/messages/peer/detail/reject.cpp @@ -100,6 +100,36 @@ BOOST_AUTO_TEST_CASE(reject__deserialize1__tx__expected) BOOST_REQUIRE_EQUAL(message->message, transaction::command); BOOST_REQUIRE(message->code == reject::reason_code::dust); BOOST_REQUIRE_EQUAL(message->reason, "dust"); + BOOST_REQUIRE_EQUAL(message->hash, genesis_tx_hash); +} + +BOOST_AUTO_TEST_CASE(reject__deserialize1__tx_without_hash__null_hash) +{ + const auto data = system::base16_chunk("027478410464757374"); + const auto message = reject::deserialize(level::bip61, data); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->message, transaction::command); + BOOST_REQUIRE_EQUAL(message->reason, "dust"); + BOOST_REQUIRE_EQUAL(message->hash, system::null_hash); +} + +BOOST_AUTO_TEST_CASE(reject__deserialize1__tx_partial_hash__null_hash) +{ + const auto data = system::base16_chunk("0274784104647573743ba3edfd7a7b12b2"); + const auto message = reject::deserialize(level::bip61, data); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->message, transaction::command); + BOOST_REQUIRE_EQUAL(message->reason, "dust"); + BOOST_REQUIRE_EQUAL(message->hash, system::null_hash); +} + +BOOST_AUTO_TEST_CASE(reject__deserialize2__tx__exhausted) +{ + system::read::bytes::copy source(dust_tx_payload); + const auto message = reject::deserialize(level::bip61, source); + BOOST_REQUIRE(source); + BOOST_REQUIRE(source.is_exhausted()); + BOOST_REQUIRE_EQUAL(message.hash, genesis_tx_hash); } BOOST_AUTO_TEST_CASE(reject__deserialize1__version__null_hash) diff --git a/test/messages/rpc/body_reader.cpp b/test/messages/rpc/body_reader.cpp index 7299e1216..29f5b756c 100644 --- a/test/messages/rpc/body_reader.cpp +++ b/test/messages/rpc/body_reader.cpp @@ -786,6 +786,42 @@ BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__v2_without_method__jsonrpc_require BOOST_REQUIRE(ec == error::jsonrpc_requires_method); } +BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__numeric_method__not_string) +{ + const std::string_view text{ R"({"jsonrpc":"2.0","id":1,"method":42})" }; + const asio::const_buffer buffer{ text.data(), text.size() }; + rpc::request_body::value_type body{}; + request_header header{}; + rpc::request_body::reader reader(header, body); + boost_code ec{}; + reader.init(text.size(), ec); + BOOST_REQUIRE(!ec); + + reader.put(buffer, ec); + BOOST_REQUIRE(!ec); + + reader.finish(ec); + BOOST_REQUIRE(ec == boost::json::error::not_string); +} + +BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__v1_boolean_id__jsonrpc_reader_exception) +{ + const std::string_view text{ R"({"id":true,"method":"getblockcount","params":[]})" }; + const asio::const_buffer buffer{ text.data(), text.size() }; + rpc::request_body::value_type body{}; + request_header header{}; + rpc::request_body::reader reader(header, body); + boost_code ec{}; + reader.init(text.size(), ec); + BOOST_REQUIRE(!ec); + + reader.put(buffer, ec); + BOOST_REQUIRE(!ec); + + reader.finish(ec); + BOOST_REQUIRE(ec == error::jsonrpc_reader_exception); +} + BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__v1_without_params__jsonrpc_v1_requires_params) { const std::string_view text{ R"({"id":1,"method":"getblockcount"})" }; @@ -890,6 +926,42 @@ BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__v2_null_id_object_params__success_ BOOST_REQUIRE_EQUAL(std::get(params.at("minuend").value()), 42.0); } +BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__version_3_0__jsonrpc_invalid_version) +{ + const std::string_view text{ R"({"jsonrpc":"3.0","id":1,"method":"getblockcount","params":[]})" }; + const asio::const_buffer buffer{ text.data(), text.size() }; + rpc::request_body::value_type body{}; + request_header header{}; + rpc::request_body::reader reader(header, body); + boost_code ec{}; + reader.init(text.size(), ec); + BOOST_REQUIRE(!ec); + + reader.put(buffer, ec); + BOOST_REQUIRE(!ec); + + reader.finish(ec); + BOOST_REQUIRE(ec == error::jsonrpc_invalid_version); +} + +BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__numeric_version__jsonrpc_invalid_version) +{ + const std::string_view text{ R"({"jsonrpc":2.0,"id":1,"method":"getblockcount","params":[]})" }; + const asio::const_buffer buffer{ text.data(), text.size() }; + rpc::request_body::value_type body{}; + request_header header{}; + rpc::request_body::reader reader(header, body); + boost_code ec{}; + reader.init(text.size(), ec); + BOOST_REQUIRE(!ec); + + reader.put(buffer, ec); + BOOST_REQUIRE(!ec); + + reader.finish(ec); + BOOST_REQUIRE(ec == error::jsonrpc_invalid_version); +} + BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__v2_nested_params__success_expected) { const std::string_view text{ R"({"jsonrpc":"2.0","id":7,"method":"m","params":[null,true,[1,"a"],{"k":false}]})" }; diff --git a/test/messages/rpc/body_writer.cpp b/test/messages/rpc/body_writer.cpp index d03a823a1..918e88be9 100644 --- a/test/messages/rpc/body_writer.cpp +++ b/test/messages/rpc/body_writer.cpp @@ -68,6 +68,18 @@ BOOST_AUTO_TEST_CASE(rpc_body_writer__init__default__success) BOOST_REQUIRE(!ec); } +BOOST_AUTO_TEST_CASE(rpc_body_writer__init__any_result__jsonrpc_writer_exception) +{ + http::flat_buffer scratch{ 64 * 1024 }; + rpc::response_body::value_type body{}; + body.buffer = &scratch; + body.message.result = value_t{ any_t{ system::to_shared(42) } }; + rpc::response_body::writer writer(body); + boost_code ec{}; + writer.init(ec); + BOOST_REQUIRE(ec == error::jsonrpc_writer_exception); +} + BOOST_AUTO_TEST_CASE(rpc_body_writer__get__null_response_non_terminated__success_expected_no_more) { const std::string_view expected{ R"({"error":null,"result":null})" }; diff --git a/test/messages/rpc/types.cpp b/test/messages/rpc/types.cpp index fea84d094..2720f3dfc 100644 --- a/test/messages/rpc/types.cpp +++ b/test/messages/rpc/types.cpp @@ -380,6 +380,11 @@ BOOST_AUTO_TEST_CASE(rpc_model__value_to__json_types__expected_alternatives) BOOST_REQUIRE_EQUAL(std::get(boost::json::value_to(boost::json::parse(R"({"a":1})")).value()).size(), 1u); } +BOOST_AUTO_TEST_CASE(rpc_model__value_from__any__throws) +{ + BOOST_REQUIRE_THROW(boost::json::value_from(value_t{ any_t{ system::to_shared(42) } }), ostream_exception); +} + BOOST_AUTO_TEST_CASE(rpc_model__identity_to__json_types__expected_alternatives) { BOOST_REQUIRE(std::holds_alternative(boost::json::value_to(boost::json::parse("null")))); @@ -431,6 +436,25 @@ BOOST_AUTO_TEST_CASE(rpc_model__response_to__v2_result_numeric_id__expected) BOOST_REQUIRE_EQUAL(std::get(response.result.value().value()), 19.0); } +BOOST_AUTO_TEST_CASE(rpc_model__response_to__v1_result_null_error__expected) +{ + const auto model = boost::json::parse(R"({"result":1,"error":null,"id":1})"); + const auto response = boost::json::value_to(model); + BOOST_REQUIRE(response.jsonrpc == version::undefined); + BOOST_REQUIRE_EQUAL(std::get(response.id.value()), 1); + BOOST_REQUIRE(!response.error.has_value()); + BOOST_REQUIRE_EQUAL(std::get(response.result.value().value()), 1.0); +} + +BOOST_AUTO_TEST_CASE(rpc_model__response_to__v1_result__round_trips) +{ + const auto model = boost::json::value_from(response_t{ version::v1, identity_t{ 1 }, {}, value_t{ true } }); + const auto response = boost::json::value_to(model); + BOOST_REQUIRE(response.jsonrpc == version::v1); + BOOST_REQUIRE(!response.error.has_value()); + BOOST_REQUIRE(std::get(response.result.value().value())); +} + BOOST_AUTO_TEST_CASE(rpc_model__response_to__empty_object__defaults) { const auto response = boost::json::value_to(boost::json::parse("{}")); diff --git a/test/net.cpp b/test/net.cpp index b7407d243..87cf68e13 100644 --- a/test/net.cpp +++ b/test/net.cpp @@ -737,6 +737,28 @@ class net_accessor { net::subscribe_close(std::move(handler)); } + + template + typename Session::ptr attach_service_stranded() NOEXCEPT + { + return net::attach_service(*this); + } +}; + +class mock_service_session + : public session_manual +{ +public: + typedef std::shared_ptr ptr; + using session_manual::session_manual; + + void stop() NOEXCEPT override + { + ++stops; + session_manual::stop(); + } + + size_t stops{}; }; class closed_net_accessor @@ -1044,6 +1066,23 @@ BOOST_AUTO_TEST_CASE(net__connect_handled__closed__service_stopped) BOOST_REQUIRE_EQUAL(result, error::service_stopped); } +BOOST_AUTO_TEST_CASE(net__stop_services__attached_service__stopped_once) +{ + const logger log{}; + const settings set(selection::mainnet); + net_accessor net(set, log); + + const auto session = on_strand(net, [&]() NOEXCEPT { return net.attach_service_stranded(); }); + BOOST_REQUIRE_EQUAL(on_strand(net, [&]() NOEXCEPT { return net.service_subscriber_count(); }), 1u); + BOOST_REQUIRE_EQUAL(on_strand(net, [&]() NOEXCEPT { return net.stop_subscriber_count(); }), 1u); + + net.stop_services(error::service_stopped); + BOOST_REQUIRE_EQUAL(on_strand(net, [&]() NOEXCEPT { return net.service_subscriber_count(); }), 0u); + BOOST_REQUIRE_EQUAL(on_strand(net, [&]() NOEXCEPT { return session->stops; }), 1u); + net.close(); + BOOST_REQUIRE_EQUAL(session->stops, 2u); +} + BOOST_AUTO_TEST_CASE(net__subscribe_close__stranded__subscribed_then_stopped) { const logger log{}; diff --git a/test/net/acceptor_sam.cpp b/test/net/acceptor_sam.cpp index 7a7a7117c..efbbd363a 100644 --- a/test/net/acceptor_sam.cpp +++ b/test/net/acceptor_sam.cpp @@ -606,6 +606,18 @@ BOOST_FIXTURE_TEST_CASE(acceptor_sam__accept__forwarded_peer_invalid_destination BOOST_REQUIRE_EQUAL(result.get(), error::sam_response_invalid); } +BOOST_FIXTURE_TEST_CASE(acceptor_sam__accept__forwarded_peer_destination__success, sam_setup_fixture) +{ + BOOST_REQUIRE_EQUAL(create(), error::success); + auto result = accept(); + establish(); + connect_peer(); + write_line(peer, destination + " FROM_PORT=0 TO_PORT=0"); + + BOOST_REQUIRE(result.wait_for(seconds(10)) == std::future_status::ready); + BOOST_REQUIRE_EQUAL(result.get(), error::success); +} + BOOST_FIXTURE_TEST_CASE(acceptor_sam__accept__forward_closed__session_closed, sam_setup_fixture) { BOOST_REQUIRE_EQUAL(create(), error::success); diff --git a/test/protocols/protocol_version_106.cpp b/test/protocols/protocol_version_106.cpp index 6bb5e0d1a..f416f3a6f 100644 --- a/test/protocols/protocol_version_106.cpp +++ b/test/protocols/protocol_version_106.cpp @@ -193,26 +193,22 @@ BOOST_AUTO_TEST_CASE(protocol_version_106__receive_version__timestamp_within_ske BOOST_REQUIRE_EQUAL(receive(level::bip31)->nonce, 42_u64); } -BOOST_AUTO_TEST_CASE(protocol_version_106__receive_version__duplicate_after_handshake__ignored) +BOOST_AUTO_TEST_CASE(protocol_version_106__receive_version__duplicate_after_handshake__dropped) { settings_.protocol_maximum = level::bip31; BOOST_REQUIRE(open()); BOOST_REQUIRE(handshake(level::bip31)); send_version(level::bip31, service::node_none, network::unix_time()); - - send(ping{ 42 }, level::bip31); - BOOST_REQUIRE_EQUAL(receive(level::bip31)->nonce, 42_u64); + BOOST_REQUIRE(dropped()); } -BOOST_AUTO_TEST_CASE(protocol_version_106__receive_acknowledge__duplicate_after_handshake__ignored) +BOOST_AUTO_TEST_CASE(protocol_version_106__receive_acknowledge__duplicate_after_handshake__dropped) { settings_.protocol_maximum = level::bip31; BOOST_REQUIRE(open()); BOOST_REQUIRE(handshake(level::bip31)); send(version_acknowledge{}, level::bip31); - - send(ping{ 42 }, level::bip31); - BOOST_REQUIRE_EQUAL(receive(level::bip31)->nonce, 42_u64); + BOOST_REQUIRE(dropped()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/protocols/protocol_version_70002.cpp b/test/protocols/protocol_version_70002.cpp index f3316a6f4..306160918 100644 --- a/test/protocols/protocol_version_70002.cpp +++ b/test/protocols/protocol_version_70002.cpp @@ -61,16 +61,12 @@ BOOST_AUTO_TEST_CASE(protocol_version_70002__receive_reject__during_handshake__h BOOST_REQUIRE_EQUAL(receive(level::bip61)->nonce, 42_u64); } -BOOST_AUTO_TEST_CASE(protocol_version_70002__receive_version__duplicate_after_handshake__reject_duplicate) +BOOST_AUTO_TEST_CASE(protocol_version_70002__receive_version__duplicate_after_handshake__dropped) { BOOST_REQUIRE(open()); BOOST_REQUIRE(handshake(level::bip61)); send_version(level::bip61, service::node_none, network::unix_time()); - - const auto message = receive(level::bip61); - BOOST_REQUIRE(message); - BOOST_REQUIRE_EQUAL(message->message, version::command); - BOOST_REQUIRE(message->code == reject::reason_code::duplicate); + BOOST_REQUIRE(dropped()); } BOOST_AUTO_TEST_CASE(protocol_version_70002__receive_version__below_minimum__dropped) @@ -81,6 +77,15 @@ BOOST_AUTO_TEST_CASE(protocol_version_70002__receive_version__below_minimum__dro BOOST_REQUIRE(dropped()); } +BOOST_AUTO_TEST_CASE(protocol_version_70002__receive_version__reject_disabled_below_minimum__dropped) +{ + settings_.enable_reject = false; + settings_.protocol_minimum = level::bip61; + BOOST_REQUIRE(open()); + send_version(level::bip37, service::node_none, network::unix_time()); + BOOST_REQUIRE(dropped()); +} + BOOST_AUTO_TEST_CASE(protocol_version_70002__receive_version__invalid_services__dropped) { BOOST_REQUIRE(open()); diff --git a/test/protocols/protocol_version_70016.cpp b/test/protocols/protocol_version_70016.cpp index aa9cef48f..54d45f108 100644 --- a/test/protocols/protocol_version_70016.cpp +++ b/test/protocols/protocol_version_70016.cpp @@ -114,6 +114,23 @@ BOOST_AUTO_TEST_CASE(protocol_version_70016__handshake__witness_tx_bip152_peer__ BOOST_REQUIRE_EQUAL(receive().first, version_acknowledge::command); } +BOOST_AUTO_TEST_CASE(protocol_version_70016__receive_send_address_v2__before_version__dropped) +{ + BOOST_REQUIRE(open()); + + send(send_address_v2{}, level::bip155); + BOOST_REQUIRE(dropped()); +} + +BOOST_AUTO_TEST_CASE(protocol_version_70016__receive_witness_tx_id_relay__before_version__dropped) +{ + settings_.enable_witness_tx = true; + BOOST_REQUIRE(open()); + + send(witness_tx_id_relay{}, level::bip339); + BOOST_REQUIRE(dropped()); +} + BOOST_AUTO_TEST_CASE(protocol_version_70016__receive_send_address_v2__after_acknowledge__dropped) { BOOST_REQUIRE(open()); @@ -133,17 +150,12 @@ BOOST_AUTO_TEST_CASE(protocol_version_70016__receive_witness_tx_id_relay__after_ BOOST_REQUIRE(dropped()); } -BOOST_AUTO_TEST_CASE(protocol_version_70016__receive_version__reject_enabled_duplicate_after_handshake__reject_duplicate) +BOOST_AUTO_TEST_CASE(protocol_version_70016__receive_version__duplicate_after_handshake__dropped) { - settings_.enable_reject = true; BOOST_REQUIRE(open()); BOOST_REQUIRE(handshake(level::bip155)); - send_version(level::bip155, service::node_none, network::unix_time()); - const auto message = receive(level::bip155); - BOOST_REQUIRE(message); - BOOST_REQUIRE_EQUAL(message->message, version::command); - BOOST_REQUIRE(message->code == reject::reason_code::duplicate); + BOOST_REQUIRE(dropped()); } BOOST_AUTO_TEST_SUITE_END()