diff --git a/include/bitcoin/node/protocols/protocol_explore.hpp b/include/bitcoin/node/protocols/protocol_explore.hpp index a0c3f06af..adb7a681a 100644 --- a/include/bitcoin/node/protocols/protocol_explore.hpp +++ b/include/bitcoin/node/protocols/protocol_explore.hpp @@ -48,6 +48,87 @@ class BCN_API protocol_explore } protected: + + /// TODO: move to own source. + /// Pagination and filtering are via query string. + enum targets + { + /// /v[]/block/hash/[bkhash] {1} + /// /v[]/block/height/[height] {1}s + block, + + /// /v[]/block/hash/[bkhash]/filter {1} + /// /v[]/block/height/[height]/filter {1} + filter, + + /// /v[]/block/hash/[bkhash]/header {1} + /// /v[]/block/height/[height]/header {1} + header, + + /// /v[]/transaction/hash/[txhash] {1} + /// /v[]/block/hash/[bkhash]/transaction/[position] {1} + /// /v[]/block/height/[height]/transaction/[position] {1} + transaction, + + /// /v[]/block/hash/[bkhash]/transactions {all txs in the block} + /// /v[]/block/height/[height]/transactions {all txs in the block} + transactions, + + // -------------------------------------------------------------------- + + /// /v[]/input/[txhash]/[index] {1} + input, + + /// /v[]/inputs/[txhash] {all inputs in the tx} + inputs, + + /// /v[]/input/[txhash]/[index]/script {1} + input_script, + + /// /v[]/input/[txhash]/scripts {all input scripts in the tx} + input_scripts, + + /// /v[]/input/[txhash]/[index]/witness {1} + input_witness, + + /// /v[]/input/[txhash]/witnesses {all witnesses in the tx} + input_witnesses, + + // -------------------------------------------------------------------- + + /// /v[]/output/[txhash]/[index] {1} + output, + + /// /v[]/outputs/[txhash] {all outputs in the tx} + outputs, + + /// /v[]/output/[txhash]/[index]/script {1} + output_script, + + /// /v[]/output/[txhash]/scripts {all output scripts in the tx} + output_scripts, + + /// /v[]/output/[txhash]/[index]/spender {1 - confirmed} + output_spender, + + /// /v[]/output/[txhash]/spenders {all} + output_spenders, + + // -------------------------------------------------------------------- + + /// /v[]/address/[output-script-hash] {all} + address + }; + + /// Senders. + void send_json(const network::http::request& request, + boost::json::value&& model) NOEXCEPT; + void send_data(const network::http::request& request, + system::data_chunk&& data) NOEXCEPT; + void send_text(const network::http::request& request, + std::string&& hexidecimal) NOEXCEPT; + + /// Receivers. void handle_receive_get(const code& ec, const network::http::method::get& request) NOEXCEPT override; }; diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index 1f4f3ccc6..6f6f101fb 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -25,15 +25,19 @@ namespace node { #define CLASS protocol_explore -using namespace system; -using namespace network::http; +using namespace boost::json; using namespace std::placeholders; +using namespace network::http; +using namespace system; + +const auto binary = mime_type::application_octet_stream; +const auto json = mime_type::application_json; +const auto text = mime_type::text_plain; BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) // Handle get method. // ---------------------------------------------------------------------------- -// fields[], request->target(), file.is_open() are undeclared noexcept. void protocol_explore::handle_receive_get(const code& ec, const method::get& request) NOEXCEPT @@ -57,6 +61,85 @@ void protocol_explore::handle_receive_get(const code& ec, return; } + const auto target = request->target(); + if (!is_origin_form(target)) + { + send_bad_target(*request); + return; + } + + wallet::uri uri{}; + if (!uri.decode(target)) + { + send_bad_target(*request); + return; + } + + /////////////////////////////////////////////////////////////////////////// + // TODO: formatted error responses. + // TODO: priority sort and dispatch. + // TODO: URI path parse (see API doc). + // TODO: performance timing header. + /////////////////////////////////////////////////////////////////////////// + + const auto get_tx = [&](const auto& path) NOEXCEPT + { + hash_digest hash{}; + if (!decode_hash(hash, path)) + { + send_bad_target(*request); + return chain::transaction::cptr{}; + } + + const auto& query = archive(); + return query.get_transaction(query.to_tx(hash), true); + }; + + auto params = uri.decode_query(); + const auto hex = trim_copy(uri.path(), { "/" }); + const auto accepts = to_mime_types((*request)[field::accept]); + + if (contains(accepts, json) || params["format"] == "json") + { + const auto tx = get_tx(hex); + if (!tx) + { + send_not_found(*request); + return; + } + + send_json(*request, value_from(*tx)); + return; + } + + if (contains(accepts, binary) || params["format"] == "binary") + { + const auto tx = get_tx(hex); + if (!tx) + { + send_not_found(*request); + return; + } + + send_data(*request, tx->to_data(true)); + return; + } + + if (contains(accepts, text) || params["format"] == "text") + { + const auto tx = get_tx(hex); + if (!tx) + { + send_not_found(*request); + return; + } + + send_text(*request, encode_base16(tx->to_data(true))); + return; + } + + // Default to html (single page site). + // Empty path implies malformed target (terminal). auto path = to_local_path(request->target()); if (path.empty()) @@ -87,6 +170,44 @@ void protocol_explore::handle_receive_get(const code& ec, send_file(*request, std::move(file), file_mime_type(path)); } +// TODO: buffer should be reused, so set at the channel. +// json_value is not a sized body, so this sets chunked encoding. +void protocol_explore::send_json(const request& request, + boost::json::value&& model) NOEXCEPT +{ + BC_ASSERT_MSG(stranded(), "strand"); + response response{ status::ok, request.version() }; + add_common_headers(response, request); + response.set(field::content_type, from_mime_type(json)); + response.body() = { std::move(model) }; + response.prepare_payload(); + SEND(std::move(response), handle_complete, _1, error::success); +} + +void protocol_explore::send_data(const request& request, + data_chunk&& data) NOEXCEPT +{ + BC_ASSERT_MSG(stranded(), "strand"); + data_response response{ status::ok, request.version() }; + add_common_headers(response, request); + response.set(field::content_type, from_mime_type(binary)); + response.body() = std::move(data); + response.prepare_payload(); + SEND(std::move(response), handle_complete, _1, error::success); +} + +void protocol_explore::send_text(const request& request, + std::string&& hexidecimal) NOEXCEPT +{ + BC_ASSERT_MSG(stranded(), "strand"); + string_response response{ status::ok, request.version() }; + add_common_headers(response, request); + response.set(field::content_type, from_mime_type(text)); + response.body() = std::move(hexidecimal); + response.prepare_payload(); + SEND(std::move(response), handle_complete, _1, error::success); +} + BC_POP_WARNING() } // namespace node