diff --git a/console/executor_dumps.cpp b/console/executor_dumps.cpp index d4b3afac..6eec512c 100644 --- a/console/executor_dumps.cpp +++ b/console/executor_dumps.cpp @@ -152,9 +152,9 @@ void executor::dump_progress() const logger(format(BN_MEASURE_PROGRESS) % query_.get_fork() % query_.get_top_confirmed() % - encode_hash(query_.get_header_key(query_.to_confirmed(query_.get_top_confirmed()))) % + encode_hash(query_.get_top_confirmed_hash()) % query_.get_top_candidate() % - encode_hash(query_.get_header_key(query_.to_candidate(query_.get_top_candidate()))) % + encode_hash(query_.get_top_candidate_hash()) % query_.get_top_associated() % (query_.get_top_candidate() - query_.get_unassociated_count()) % query_.get_confirmed_size() % diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index d85a6618..1e21cfe3 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -118,8 +118,10 @@ enum error_t : uint8_t invalid_subcomponent, extra_segment, - /// server (json-rpc parse codes) - unexpected_parse + /// server (rpc response codes) + not_found, + invalid_argument, + not_implemented }; // No current need for error_code equivalence mapping. diff --git a/include/bitcoin/node/interfaces/bitcoind_rpc.hpp b/include/bitcoin/node/interfaces/bitcoind_rpc.hpp index d0d5ef57..579c7551 100644 --- a/include/bitcoin/node/interfaces/bitcoind_rpc.hpp +++ b/include/bitcoin/node/interfaces/bitcoind_rpc.hpp @@ -32,21 +32,21 @@ struct bitcoind_rpc_methods { /// Blockchain methods. method<"getbestblockhash">{}, - method<"getblock", string_t, optional<1>>{ "blockhash", "verbosity" }, + method<"getblock", string_t, optional<1.0>>{ "blockhash", "verbosity" }, method<"getblockchaininfo">{}, method<"getblockcount">{}, method<"getblockfilter", string_t, optional<"basic"_t>>{ "blockhash", "filtertype" }, method<"getblockhash", number_t>{ "height" }, method<"getblockheader", string_t, optional>{ "blockhash", "verbose" }, method<"getblockstats", string_t, optional>{ "hash_or_height", "stats" }, - method<"getchaintxstats", optional<-1>, optional<""_t>>{ "nblocks", "blockhash" }, + method<"getchaintxstats", optional<-1.0>, optional<""_t>>{ "nblocks", "blockhash" }, method<"getchainwork">{}, method<"gettxout", string_t, number_t, optional>{ "txid", "n", "include_mempool" }, method<"gettxoutsetinfo">{}, method<"pruneblockchain", number_t>{ "height" }, method<"savemempool">{}, method<"scantxoutset", string_t, optional>{ "action", "scanobjects" }, - method<"verifychain", optional<4>, optional<288>>{ "checklevel", "nblocks" }, + method<"verifychain", optional<4.0>, optional<288.0>>{ "checklevel", "nblocks" }, method<"verifytxoutset", string_t>{ "input_verify_flag" }, /////// Control methods. diff --git a/include/bitcoin/node/protocols/protocol_bitcoind_rpc.hpp b/include/bitcoin/node/protocols/protocol_bitcoind_rpc.hpp index bc1eec34..ce8624e7 100644 --- a/include/bitcoin/node/protocols/protocol_bitcoind_rpc.hpp +++ b/include/bitcoin/node/protocols/protocol_bitcoind_rpc.hpp @@ -68,7 +68,8 @@ class BCN_API protocol_bitcoind_rpc bool handle_get_best_block_hash(const code& ec, rpc_interface::get_best_block_hash) NOEXCEPT; bool handle_get_block(const code& ec, - rpc_interface::get_block, const std::string&, double) NOEXCEPT; + rpc_interface::get_block, const std::string&, + double verbosity) NOEXCEPT; bool handle_get_block_chain_info(const code& ec, rpc_interface::get_block_chain_info) NOEXCEPT; bool handle_get_block_count(const code& ec, @@ -104,6 +105,15 @@ class BCN_API protocol_bitcoind_rpc bool handle_verify_tx_out_set(const code& ec, rpc_interface::verify_tx_out_set, const std::string&) NOEXCEPT; + /// Senders. + void send_error(const code& ec) NOEXCEPT; + void send_error(const code& ec, size_t size_hint) NOEXCEPT; + void send_error(const code& ec, network::rpc::value_option&& error, + size_t size_hint) NOEXCEPT; + void send_text(std::string&& hexidecimal) NOEXCEPT; + void send_result(network::rpc::value_option&& result, + size_t size_hint) NOEXCEPT; + private: template inline void subscribe(Method&& method, Args&&... args) NOEXCEPT @@ -111,11 +121,22 @@ class BCN_API protocol_bitcoind_rpc rpc_dispatcher_.subscribe(BIND_SHARED(method, args)); } - // Send the response. - void send_json(boost::json::value&& model, size_t size_hint) NOEXCEPT; + // Senders. + void send_rpc(network::rpc::response_t&& model, + size_t size_hint) NOEXCEPT; + + // Cache request for serialization (requires strand). + void set_rpc_request(network::rpc::version version, + const network::rpc::id_option& id, + const network::http::request_cptr& request) NOEXCEPT; + + // Obtain cached request and clear cache (requires strand). + network::http::request_cptr reset_rpc_request() NOEXCEPT; - // This is protected by strand. + // These are protected by strand. rpc_dispatcher rpc_dispatcher_{}; + network::rpc::version version_{}; + network::rpc::id_option id_{}; }; } // namespace node diff --git a/src/error.cpp b/src/error.cpp index 172a9a6c..7e0c6f7b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -49,7 +49,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { duplicate_block, "duplicate block" }, { duplicate_header, "duplicate header" }, - /// faults + // faults { protocol1, "protocol1" }, { protocol2, "protocol2" }, { header1, "header1" }, @@ -90,7 +90,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { confirm12, "confirm12" }, { confirm13, "confirm13" }, - /// server (url parse codes) + // server (url parse codes) { empty_path, "empty_path" }, { invalid_number, "invalid_number" }, { invalid_hash, "invalid_hash" }, @@ -107,7 +107,11 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { invalid_component, "invalid_component" }, { invalid_subcomponent, "invalid_subcomponent" }, { extra_segment, "extra_segment" }, - { unexpected_parse, "unexpected_parse" } + + // server (rpc response codes) + { not_found, "not_found" }, + { invalid_argument, "invalid_argument" }, + { not_implemented, "not_implemented" } }; DEFINE_ERROR_T_CATEGORY(error, "node", "node code") diff --git a/src/protocols/protocol_bitcoind_rpc.cpp b/src/protocols/protocol_bitcoind_rpc.cpp index 0e434dd5..35f7c0af 100644 --- a/src/protocols/protocol_bitcoind_rpc.cpp +++ b/src/protocols/protocol_bitcoind_rpc.cpp @@ -31,9 +31,9 @@ namespace node { using namespace system; using namespace network::rpc; using namespace network::http; +using namespace network::json; using namespace network::monad; using namespace std::placeholders; -using namespace boost::json; BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) @@ -130,173 +130,311 @@ void protocol_bitcoind_rpc::handle_receive_post(const code& ec, return; } - const auto& body = post->body(); - if (!body.contains()) + // Endpoint accepts only json-rpc posts. + if (!post->body().contains()) { - send_not_acceptable(*post); + send_bad_request(*post); return; } + // Get the parsed json-rpc request object. + // v1 or v2 both supported, batch not yet supported. + // v1 null id and v2 missing id implies notification and no response. + const auto& request = post->body().get().message; + // The post is saved off during asynchonous handling and used in send_json // to formulate response headers, isolating handlers from http semantics. - set_request(post); + set_rpc_request(request.jsonrpc, request.id, post); - const auto& request = body.get().message; + // Dispatch the request to subscribers. if (const auto code = rpc_dispatcher_.notify(request)) stop(code); } +template +std::string to_hex(const Object& object, size_t size, Args&&... args) NOEXCEPT +{ + std::string out(two * size, '\0'); + stream::out::fast sink{ out }; + write::base16::fast writer{ sink }; + object.to_data(writer, std::forward(args)...); + BC_ASSERT(writer); + return out; +} + // Handlers. // ---------------------------------------------------------------------------- // github.com/bitcoin/bitcoin/blob/master/doc/JSON-RPC-interface.md // TODO: precompute size for buffer hints. -// {"jsonrpc": "1.0", "id": "curltest", "method": "getbestblockhash", "params": []} bool protocol_bitcoind_rpc::handle_get_best_block_hash(const code& ec, rpc_interface::get_best_block_hash) NOEXCEPT { if (stopped(ec)) return false; - const auto& query = archive(); - const auto hash = query.get_header_key(query.to_confirmed( - query.get_top_confirmed())); - - const response_t model{ .result = encode_hash(hash) }; - send_json(value_from(model), two * system::hash_size); + const auto hash = archive().get_top_confirmed_hash(); + send_result(encode_hash(hash), two * system::hash_size); return true; } -// method<"getblock", string_t, optional<0_u32>>{ "blockhash", "verbosity" }, bool protocol_bitcoind_rpc::handle_get_block(const code& ec, - rpc_interface::get_block, const std::string&, double) NOEXCEPT + rpc_interface::get_block, const std::string& blockhash, + double verbosity) NOEXCEPT { - return !ec; + if (stopped(ec)) + return false; + + hash_digest hash{}; + if (!decode_hash(hash, blockhash)) + { + send_error(error::not_found, blockhash, blockhash.size()); + return true; + } + + constexpr auto witness = true; + const auto& query = archive(); + const auto link = query.to_header(hash); + + if (verbosity == 0.0) + { + const auto block = query.get_block(link, witness); + if (is_null(block)) + { + send_error(error::not_found, blockhash, blockhash.size()); + return true; + } + + send_text(to_hex(*block, block->serialized_size(witness), witness)); + return true; + } + + if (verbosity == 1.0) + { + send_error(error::not_implemented); + return true; + } + + if (verbosity == 2.0) + { + send_error(error::not_implemented); + return true; + } + + send_error(error::invalid_argument); + return true; } bool protocol_bitcoind_rpc::handle_get_block_chain_info(const code& ec, rpc_interface::get_block_chain_info) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } bool protocol_bitcoind_rpc::handle_get_block_count(const code& ec, rpc_interface::get_block_count) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"getblockfilter", string_t, optional<"basic"_t>>{ "blockhash", "filtertype" }, bool protocol_bitcoind_rpc::handle_get_block_filter(const code& ec, rpc_interface::get_block_filter, const std::string&, const std::string&) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"getblockhash", number_t>{ "height" }, bool protocol_bitcoind_rpc::handle_get_block_hash(const code& ec, rpc_interface::get_block_hash, network::rpc::number_t) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"getblockheader", string_t, optional>{ "blockhash", "verbose" }, bool protocol_bitcoind_rpc::handle_get_block_header(const code& ec, rpc_interface::get_block_header, const std::string&, bool) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"getblockstats", string_t, optional>{ "hash_or_height", "stats" }, bool protocol_bitcoind_rpc::handle_get_block_stats(const code& ec, rpc_interface::get_block_stats, const std::string&, const network::rpc::array_t&) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"getchaintxstats", optional<-1_i32>, optional<""_t>>{ "nblocks", "blockhash" }, bool protocol_bitcoind_rpc::handle_get_chain_tx_stats(const code& ec, rpc_interface::get_chain_tx_stats, double, const std::string&) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } bool protocol_bitcoind_rpc::handle_get_chain_work(const code& ec, rpc_interface::get_chain_work) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"gettxout", string_t, number_t, optional>{ "txid", "n", "include_mempool" }, bool protocol_bitcoind_rpc::handle_get_tx_out(const code& ec, rpc_interface::get_tx_out, const std::string&, double, bool) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } bool protocol_bitcoind_rpc::handle_get_tx_out_set_info(const code& ec, rpc_interface::get_tx_out_set_info) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"pruneblockchain", number_t>{ "height" }, bool protocol_bitcoind_rpc::handle_prune_block_chain(const code& ec, rpc_interface::prune_block_chain, double) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } bool protocol_bitcoind_rpc::handle_save_mem_pool(const code& ec, rpc_interface::save_mem_pool) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"scantxoutset", string_t, optional>{ "action", "scanobjects" }, bool protocol_bitcoind_rpc::handle_scan_tx_out_set(const code& ec, rpc_interface::scan_tx_out_set, const std::string&, const network::rpc::array_t&) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"verifychain", optional<4_u32>, optional<288_u32>>{ "checklevel", "nblocks" }, bool protocol_bitcoind_rpc::handle_verify_chain(const code& ec, rpc_interface::verify_chain, double, double) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// method<"verifytxoutset", string_t>{ "input_verify_flag" }, bool protocol_bitcoind_rpc::handle_verify_tx_out_set(const code& ec, rpc_interface::verify_tx_out_set, const std::string&) NOEXCEPT { - return !ec; + if (stopped(ec)) return false; + send_error(error::not_implemented); + return true; } -// private +// Senders // ---------------------------------------------------------------------------- -// TODO: post-process response for json-rpc version. -void protocol_bitcoind_rpc::send_json(boost::json::value&& model, +void protocol_bitcoind_rpc::send_error(const code& ec) NOEXCEPT +{ + send_error(ec, two * ec.message().size()); +} + +void protocol_bitcoind_rpc::send_error(const code& ec, + size_t size_hint) NOEXCEPT +{ + send_error(ec, {}, size_hint); +} + +void protocol_bitcoind_rpc::send_error(const code& ec, value_option&& error, + size_t size_hint) NOEXCEPT +{ + BC_ASSERT(stranded()); + send_rpc( + { + .jsonrpc = version_, + .id = id_, + .error = result_t + { + .code = ec.value(), + .message = ec.message(), + .data = std::move(error) + } + }, size_hint); +} + +void protocol_bitcoind_rpc::send_text(std::string&& hexidecimal) NOEXCEPT +{ + BC_ASSERT(stranded()); + send_result(hexidecimal, hexidecimal.size()); +} + +void protocol_bitcoind_rpc::send_result(value_option&& result, size_t size_hint) NOEXCEPT { BC_ASSERT(stranded()); - using namespace network::monad; - const auto request = reset_request(); - constexpr auto json = media_type::application_json; + send_rpc( + { + .jsonrpc = version_, + .id = id_, + .result = std::move(result) + }, size_hint); +} + +// private +void protocol_bitcoind_rpc::send_rpc(response_t&& model, + size_t size_hint) NOEXCEPT +{ + BC_ASSERT(stranded()); + static const auto json = from_media_type(media_type::application_json); + const auto request = reset_rpc_request(); response response{ status::ok, request->version() }; add_common_headers(response, *request); add_access_control_headers(response, *request); - response.set(field::content_type, from_media_type(json)); - response.body() = json_value{ std::move(model), size_hint }; + response.set(field::content_type, json); + response.body() = rpcout_value + { + { .size_hint = size_hint }, std::move(model), + }; response.prepare_payload(); SEND(std::move(response), handle_complete, _1, error::success); } +// private +void protocol_bitcoind_rpc::set_rpc_request(version version, + const id_option& id, const request_cptr& request) NOEXCEPT +{ + BC_ASSERT(stranded()); + id_ = id; + version_ = version; + set_request(request); +} + +// private +request_cptr protocol_bitcoind_rpc::reset_rpc_request() NOEXCEPT +{ + BC_ASSERT(stranded()); + id_.reset(); + version_ = version::undefined; + return reset_request(); +} + BC_POP_WARNING() BC_POP_WARNING() BC_POP_WARNING() diff --git a/test/error.cpp b/test/error.cpp index 72f5a0a2..a4e056b5 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -371,13 +371,33 @@ BOOST_AUTO_TEST_CASE(error_t__code__extra_segment__true_exected_message) BOOST_REQUIRE_EQUAL(ec.message(), "extra_segment"); } -BOOST_AUTO_TEST_CASE(error_t__code__unexpected_parse__true_exected_message) +// server (rpc response codes) + +BOOST_AUTO_TEST_CASE(error_t__code__not_found__true_exected_message) +{ + constexpr auto value = error::not_found; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "not_found"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__invalid_argument__true_exected_message) +{ + constexpr auto value = error::invalid_argument; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "invalid_argument"); +} + +BOOST_AUTO_TEST_CASE(error_t__code__not_implemented__true_exected_message) { - constexpr auto value = error::unexpected_parse; + constexpr auto value = error::not_implemented; const auto ec = code(value); BOOST_REQUIRE(ec); BOOST_REQUIRE(ec == value); - BOOST_REQUIRE_EQUAL(ec.message(), "unexpected_parse"); + BOOST_REQUIRE_EQUAL(ec.message(), "not_implemented"); } BOOST_AUTO_TEST_SUITE_END()