diff --git a/include/bitcoin/node/error.hpp b/include/bitcoin/node/error.hpp index d0ce214c..13d584b2 100644 --- a/include/bitcoin/node/error.hpp +++ b/include/bitcoin/node/error.hpp @@ -112,6 +112,7 @@ enum error_t : uint8_t missing_position, missing_id_type, invalid_id_type, + missing_type_id, missing_component, invalid_component, invalid_subcomponent, diff --git a/include/bitcoin/node/protocols/protocol_explore.hpp b/include/bitcoin/node/protocols/protocol_explore.hpp index 03120a66..8f01451f 100644 --- a/include/bitcoin/node/protocols/protocol_explore.hpp +++ b/include/bitcoin/node/protocols/protocol_explore.hpp @@ -66,45 +66,69 @@ class BCN_API protocol_explore bool handle_get_header(const code& ec, interface::header, uint8_t version, uint8_t media, std::optional hash, std::optional height) NOEXCEPT; - ////bool handle_get_filter(const code& ec, interface::filter, - //// uint8_t version, uint8_t media, std::optional hash, - //// std::optional height) NOEXCEPT; - ////bool handle_get_block_txs(const code& ec, interface::block_txs, - //// uint8_t version, uint8_t media, std::optional hash, - //// std::optional height) NOEXCEPT; - - ////bool handle_get_block_tx(const code& ec, interface::block_tx, - //// uint8_t version, uint8_t media, uint32_t position, - //// std::optional hash, - //// std::optional height, bool witness) NOEXCEPT; - + bool handle_get_block_txs(const code& ec, interface::block_txs, + uint8_t version, uint8_t media, std::optional hash, + std::optional height) NOEXCEPT; + bool handle_get_block_tx(const code& ec, interface::block_tx, + uint8_t version, uint8_t media, uint32_t position, + std::optional hash, + std::optional height, bool witness) NOEXCEPT; bool handle_get_transaction(const code& ec, interface::transaction, uint8_t version, uint8_t media, const system::hash_cptr& hash, bool witness) NOEXCEPT; - ////bool handle_get_address(const code& ec, interface::address, - //// uint8_t version, uint8_t media, const system::hash_cptr& hash) NOEXCEPT; - - ////bool handle_get_input(const code& ec, interface::input, - //// uint8_t version, uint8_t media, const system::hash_cptr& hash, - //// std::optional index) NOEXCEPT; - ////bool handle_get_input_script(const code& ec, interface::input_script, - //// uint8_t version, uint8_t media, const system::hash_cptr& hash, - //// std::optional index) NOEXCEPT; - ////bool handle_get_input_witness(const code& ec, interface::input_witness, - //// uint8_t version, uint8_t media, const system::hash_cptr& hash, - //// std::optional index) NOEXCEPT; - - ////bool handle_get_output(const code& ec, interface::output, - //// uint8_t version, uint8_t media, const system::hash_cptr& hash, - //// std::optional index) NOEXCEPT; - ////bool handle_get_output_script(const code& ec, interface::output_script, - //// uint8_t version, uint8_t media, const system::hash_cptr& hash, - //// std::optional index) NOEXCEPT; - ////bool handle_get_output_spender(const code& ec, interface::output_spender, - //// uint8_t version, uint8_t media, const system::hash_cptr& hash, - //// std::optional index) NOEXCEPT; + bool handle_get_tx_block(const code& ec, interface::tx_block, + uint8_t version, uint8_t media, + const system::hash_cptr& hash) NOEXCEPT; + + bool handle_get_inputs(const code& ec, interface::inputs, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + bool witness) NOEXCEPT; + bool handle_get_input(const code& ec, interface::input, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + uint32_t index, bool witness) NOEXCEPT; + bool handle_get_input_script(const code& ec, interface::input_script, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + uint32_t index) NOEXCEPT; + bool handle_get_input_witness(const code& ec, interface::input_witness, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + uint32_t index) NOEXCEPT; + + bool handle_get_outputs(const code& ec, interface::outputs, + uint8_t version, uint8_t media, + const system::hash_cptr& hash) NOEXCEPT; + bool handle_get_output(const code& ec, interface::output, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + uint32_t index) NOEXCEPT; + bool handle_get_output_script(const code& ec, interface::output_script, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + uint32_t index) NOEXCEPT; + bool handle_get_output_spender(const code& ec, interface::output_spender, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + uint32_t index) NOEXCEPT; + bool handle_get_output_spenders(const code& ec, interface::output_spender, + uint8_t version, uint8_t media, const system::hash_cptr& hash, + uint32_t index) NOEXCEPT; + + bool handle_get_address(const code& ec, interface::address, + uint8_t version, uint8_t media, + const system::hash_cptr& hash) NOEXCEPT; + bool handle_get_filter(const code& ec, interface::filter, uint8_t version, + uint8_t media, uint8_t type, std::optional hash, + std::optional height) NOEXCEPT; + bool handle_get_filter_hash(const code& ec, interface::filter_hash, + uint8_t version, uint8_t media, uint8_t type, + std::optional hash, + std::optional height) NOEXCEPT; + bool handle_get_filter_header(const code& ec, interface::filter_header, + uint8_t version, uint8_t media, uint8_t type, + std::optional hash, + std::optional height) NOEXCEPT; private: + void send_wire(uint8_t media, system::data_chunk&& data) NOEXCEPT; + database::header_link to_header(const std::optional& height, + const std::optional& hash) NOEXCEPT; + dispatcher dispatcher_{}; }; diff --git a/include/bitcoin/node/protocols/protocol_html.hpp b/include/bitcoin/node/protocols/protocol_html.hpp index da11cb04..e7979b6d 100644 --- a/include/bitcoin/node/protocols/protocol_html.hpp +++ b/include/bitcoin/node/protocols/protocol_html.hpp @@ -64,7 +64,7 @@ class BCN_API protocol_html boost::json::value&& model, size_t size_hint) NOEXCEPT; virtual void send_text(const network::http::request& request, std::string&& hexidecimal) NOEXCEPT; - virtual void send_data(const network::http::request& request, + virtual void send_chunk(const network::http::request& request, system::data_chunk&& bytes) NOEXCEPT; virtual void send_file(const network::http::request& request, network::http::file&& file, network::http::media_type type) NOEXCEPT; diff --git a/src/error.cpp b/src/error.cpp index 5765cd80..172a9a6c 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -102,6 +102,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { missing_position, "missing_position" }, { missing_id_type, "missing_id_type" }, { invalid_id_type, "invalid_id_type" }, + { missing_type_id, "missing_type_id" }, { missing_component, "missing_component" }, { invalid_component, "invalid_component" }, { invalid_subcomponent, "invalid_subcomponent" }, diff --git a/src/parse/target.cpp b/src/parse/target.cpp index 03d7709f..04106507 100644 --- a/src/parse/target.cpp +++ b/src/parse/target.cpp @@ -84,18 +84,7 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT // transaction, address, inputs, and outputs are identical excluding names; // input and output are identical excluding names; block is unique. const auto target = segments[segment++]; - if (target == "transaction") - { - if (segment == segments.size()) - return error::missing_hash; - - const auto hash = to_hash(segments[segment++]); - if (!hash) return error::invalid_hash; - - method = "transaction"; - params["hash"] = hash; - } - else if (target == "address") + if (target == "address") { if (segment == segments.size()) return error::missing_hash; @@ -114,7 +103,7 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT const auto hash = to_hash(segments[segment++]); if (!hash) return error::invalid_hash; - method = "input"; + method = "inputs"; params["hash"] = hash; } else if (target == "outputs") @@ -125,7 +114,7 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT const auto hash = to_hash(segments[segment++]); if (!hash) return error::invalid_hash; - method = "output"; + method = "outputs"; params["hash"] = hash; } else if (target == "input") @@ -141,36 +130,36 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT return error::missing_component; const auto component = segments[segment++]; - if (component == "scripts") - { - method = "input_script"; - } - else if (component == "witnesses") + ////if (component == "scripts") + ////{ + //// method = "input_scripts"; + ////} + ////else if (component == "witnesses") + ////{ + //// method = "input_witnesses"; + ////} + ////else + ////{ + uint32_t index{}; + if (!to_number(index, component)) + return error::invalid_number; + + params["index"] = index; + if (segment == segments.size()) { - method = "input_witness"; + method = "input"; } else { - uint32_t index{}; - if (!to_number(index, component)) - return error::invalid_number; - - params["index"] = index; - if (segment == segments.size()) - { - method = "input"; - } + auto subcomponent = segments[segment++]; + if (subcomponent == "script") + method = "input_script"; + else if (subcomponent == "witness") + method = "input_witness"; else - { - auto subcomponent = segments[segment++]; - if (subcomponent == "script") - method = "input_script"; - else if (subcomponent == "witness") - method = "input_witness"; - else - return error::invalid_subcomponent; - } + return error::invalid_subcomponent; } + ////} } else if (target == "output") { @@ -185,13 +174,14 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT return error::missing_component; const auto component = segments[segment++]; - if (component == "scripts") - { - method = "output_script"; - } - else if (component == "spenders") + ////if (component == "scripts") + ////{ + //// method = "output_scripts"; + ////} + ////else + if (component == "spenders") { - method = "output_spender"; + method = "output_spenders"; } else { @@ -216,6 +206,28 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT } } } + else if (target == "transaction") + { + if (segment == segments.size()) + return error::missing_hash; + + const auto hash = to_hash(segments[segment++]); + if (!hash) return error::invalid_hash; + + params["hash"] = hash; + if (segment == segments.size()) + { + method = "transaction"; + } + else + { + const auto component = segments[segment++]; + if (component == "block") + method = "tx_block"; + else + return error::invalid_component; + } + } else if (target == "block") { if (segment == segments.size()) @@ -273,10 +285,33 @@ code parse_target(request_t& out, const std::string_view& path) NOEXCEPT } else if (component == "header") method = "header"; - else if (component == "filter") - method = "filter"; else if (component == "transactions") method = "block_txs"; + else if (component == "filter") + { + if (segment == segments.size()) + return error::missing_type_id; + + uint8_t type{}; + if (!to_number(type, segments[segment++])) + return error::invalid_number; + + params["type"] = type; + if (segment == segments.size()) + { + method = "filter"; + } + else + { + const auto subcomponent = segments[segment++]; + if (subcomponent == "hash") + method = "filter_hash"; + else if (subcomponent == "header") + method = "filter_header"; + else + return error::invalid_subcomponent; + } + } else return error::invalid_component; } diff --git a/src/protocols/protocol_explore.cpp b/src/protocols/protocol_explore.cpp index d51ad1ba..b9f33b90 100644 --- a/src/protocols/protocol_explore.cpp +++ b/src/protocols/protocol_explore.cpp @@ -18,7 +18,10 @@ */ #include +#include #include +#include +#include #include #include @@ -33,7 +36,9 @@ namespace node { using namespace system; using namespace network::rpc; using namespace network::http; +using namespace network::messages::peer; using namespace std::placeholders; +using namespace boost::json; // Avoiding namespace conflict. using object_type = network::rpc::object_t; @@ -55,17 +60,26 @@ void protocol_explore::start() NOEXCEPT SUBSCRIBE_EXPLORE(handle_get_block, _1, _2, _3, _4, _5, _6, _7); SUBSCRIBE_EXPLORE(handle_get_header, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_filter, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_block_txs, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_block_tx, _1, _2, _3, _4, _5, _6, _7, _8); + SUBSCRIBE_EXPLORE(handle_get_block_txs, _1, _2, _3, _4, _5, _6); + SUBSCRIBE_EXPLORE(handle_get_block_tx, _1, _2, _3, _4, _5, _6, _7, _8); SUBSCRIBE_EXPLORE(handle_get_transaction, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_address, _1, _2, _3, _4, _5); - ////SUBSCRIBE_EXPLORE(handle_get_input, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_input_script, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_input_witness, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_output, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_output_script, _1, _2, _3, _4, _5, _6); - ////SUBSCRIBE_EXPLORE(handle_get_output_spender, _1, _2, _3, _4, _5, _6); + SUBSCRIBE_EXPLORE(handle_get_tx_block, _1, _2, _3, _4, _5); + + SUBSCRIBE_EXPLORE(handle_get_inputs, _1, _2, _3, _4, _5, _6); + SUBSCRIBE_EXPLORE(handle_get_input, _1, _2, _3, _4, _5, _6, _7); + SUBSCRIBE_EXPLORE(handle_get_input_script, _1, _2, _3, _4, _5, _6); + SUBSCRIBE_EXPLORE(handle_get_input_witness, _1, _2, _3, _4, _5, _6); + + SUBSCRIBE_EXPLORE(handle_get_outputs, _1, _2, _3, _4, _5); + SUBSCRIBE_EXPLORE(handle_get_output, _1, _2, _3, _4, _5, _6); + SUBSCRIBE_EXPLORE(handle_get_output_script, _1, _2, _3, _4, _5, _6); + SUBSCRIBE_EXPLORE(handle_get_output_spender, _1, _2, _3, _4, _5, _6); + SUBSCRIBE_EXPLORE(handle_get_output_spenders, _1, _2, _3, _4, _5, _6); + + SUBSCRIBE_EXPLORE(handle_get_address, _1, _2, _3, _4, _5); + SUBSCRIBE_EXPLORE(handle_get_filter, _1, _2, _3, _4, _5, _6, _7); + SUBSCRIBE_EXPLORE(handle_get_filter_hash, _1, _2, _3, _4, _5, _6, _7); + SUBSCRIBE_EXPLORE(handle_get_filter_header, _1, _2, _3, _4, _5, _6, _7); protocol_html::start(); } @@ -81,9 +95,14 @@ void protocol_explore::stopping(const code& ec) NOEXCEPT bool protocol_explore::try_dispatch_object(const request& request) NOEXCEPT { + BC_ASSERT(stranded()); + request_t model{}; - if (const auto ec = parse_target(model, request.target())) + if (LOG_ONLY(const auto ec =) parse_target(model, request.target())) + { + LOGA("Request parse [" << request.target() << "] " << ec.message()); return false; + } if (!parse_query(model, request)) { @@ -100,41 +119,33 @@ bool protocol_explore::try_dispatch_object(const request& request) NOEXCEPT // Handlers. // ---------------------------------------------------------------------------- +constexpr auto data = to_value(media_type::application_octet_stream); +constexpr auto json = to_value(media_type::application_json); +constexpr auto text = to_value(media_type::text_plain); + bool protocol_explore::handle_get_block(const code& ec, interface::block, uint8_t, uint8_t media, std::optional hash, std::optional height, bool witness) NOEXCEPT { - BC_ASSERT(stranded()); - if (stopped(ec)) return false; - const auto& query = archive(); - const auto link = hash.has_value() ? - query.to_header(*(hash.value())) : (height.has_value() ? - query.to_confirmed(height.value()) : database::header_link{}); - - // TODO: there's no request. - const network::http::request request{}; - - if (const auto ptr = query.get_block(link, witness)) + if (const auto ptr = archive().get_block(to_header(height, hash), witness)) { switch (media) { - case to_value(media_type::application_octet_stream): - send_data(request, ptr->to_data(witness)); - return true; - case to_value(media_type::text_plain): - send_text(request, encode_base16(ptr->to_data(witness))); + case data: + case text: + send_wire(media, ptr->to_data(witness)); return true; - case to_value(media_type::application_json): - send_json(request, value_from(ptr), - ptr->serialized_size(witness)); + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size(witness)); return true; } } - send_not_found(request); + send_not_found({}); return true; } @@ -142,37 +153,90 @@ bool protocol_explore::handle_get_header(const code& ec, interface::header, uint8_t, uint8_t media, std::optional hash, std::optional height) NOEXCEPT { - BC_ASSERT(stranded()); + if (stopped(ec)) + return false; + + if (const auto ptr = archive().get_header(to_header(height, hash))) + { + switch (media) + { + case data: + case text: + send_wire(media, ptr->to_data()); + return true; + case json: + send_json({}, value_from(ptr), + two * chain::header::serialized_size()); + return true; + } + } + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_block_txs(const code& ec, + interface::block_txs, uint8_t, uint8_t media, + std::optional hash, std::optional height) NOEXCEPT +{ if (stopped(ec)) return false; const auto& query = archive(); - const auto link = hash.has_value() ? - query.to_header(*(hash.value())) : (height.has_value() ? - query.to_confirmed(height.value()) : database::header_link{}); + if (const auto hashes = query.get_tx_keys(to_header(height, hash)); + !hashes.empty()) + { + const auto size = hashes.size() * hash_size; - // TODO: there's no request. - const network::http::request request{}; + switch (media) + { + case data: + case text: + { + const auto data = pointer_cast(hashes.data()); + send_wire(media, to_chunk({ data, std::next(data, size) })); + return true; + } + case json: + { + array out(hashes.size()); + std::ranges::transform(hashes, out.begin(), + [](const auto& hash) { return encode_base16(hash); }); + send_json({}, out, two * size); + return true; + } + } + } - if (const auto ptr = query.get_header(link)) + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_block_tx(const code& ec, interface::block_tx, + uint8_t, uint8_t media, uint32_t position, std::optional hash, + std::optional height, bool witness) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (const auto ptr = query.get_transaction(query.to_transaction( + to_header(height, hash), position), witness)) { switch (media) { - case to_value(media_type::application_octet_stream): - send_data(request, ptr->to_data()); - return true; - case to_value(media_type::text_plain): - send_text(request, encode_base16(ptr->to_data())); + case data: + case text: + send_wire(media, ptr->to_data(witness)); return true; - case to_value(media_type::application_json): - send_json(request, value_from(ptr), - chain::header::serialized_size()); + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size(witness)); return true; } } - send_not_found(request); + send_not_found({}); return true; } @@ -180,37 +244,596 @@ bool protocol_explore::handle_get_transaction(const code& ec, interface::transaction, uint8_t, uint8_t media, const hash_cptr& hash, bool witness) NOEXCEPT { - BC_ASSERT(stranded()); + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (const auto ptr = query.get_transaction(query.to_tx(*hash), witness)) + { + switch (media) + { + case data: + case text: + send_wire(media, ptr->to_data(witness)); + return true; + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size(witness)); + return true; + } + } + + send_not_found({}); + return true; +} +bool protocol_explore::handle_get_tx_block(const code& ec, interface::tx_block, + uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT +{ if (stopped(ec)) return false; const auto& query = archive(); + const auto block = query.to_block(query.to_tx(*hash)); + if (block.is_terminal()) + { + send_not_found({}); + return true; + } - // TODO: there's no request. - const network::http::request request{}; + const auto key = query.get_header_key(block); + if (key == null_hash) + { + send_internal_server_error({}, database::error::integrity); + return true; + } - if (const auto ptr = query.get_transaction(query.to_tx(*hash), witness)) + switch (media) + { + case data: + case text: + send_wire(media, to_chunk(key)); + return true; + case json: + send_json({}, value_from(encode_base16(key)), two * hash_size); + return true; + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_inputs(const code& ec, interface::inputs, + uint8_t, uint8_t media, const hash_cptr& hash, bool witness) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + const auto tx = query.to_tx(*hash); + if (tx.is_terminal()) + { + send_not_found({}); + return true; + } + + const auto inputs = query.get_inputs(tx, witness); + if (!inputs || inputs->empty()) + { + send_internal_server_error({}, database::error::integrity); + return true; + } + + // Wire serialization size of inputs set. + const auto size = std::accumulate(inputs->begin(), inputs->end(), zero, + [&witness](size_t total, const auto& output) NOEXCEPT + { return total + output->serialized_size(witness); }); + + switch (media) + { + case data: + { + data_chunk out(size); + stream::out::fast sink{ out }; + write::bytes::fast writer{ sink }; + for (const auto& output: *inputs) + output->to_data(writer); + + send_chunk({}, std::move(out)); + return true; + } + case text: + { + std::string out{}; + out.resize(two * size); + stream::out::fast sink{ out }; + write::base16::fast writer{ sink }; + for (const auto& output: *inputs) + output->to_data(writer); + + send_text({}, std::move(out)); + return true; + } + case json: + { + send_json({}, value_from(*inputs), two * size); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_input(const code& ec, interface::input, + uint8_t, uint8_t media, const hash_cptr& hash, uint32_t index, + bool witness) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (const auto ptr = query.get_input(query.to_tx(*hash), index, witness)) + { + switch (media) + { + case data: + case text: + send_wire(media, ptr->to_data()); + return true; + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size(witness)); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_input_script(const code& ec, + interface::input_script, uint8_t, uint8_t media, const hash_cptr& hash, + uint32_t index) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (const auto ptr = query.get_input_script(query.to_point( + query.to_tx(*hash), index))) + { + switch (media) + { + case data: + case text: + send_wire(media, ptr->to_data(false)); + return true; + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size(false)); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_input_witness(const code& ec, + interface::input_witness, uint8_t, uint8_t media, const hash_cptr& hash, + uint32_t index) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (const auto ptr = query.get_witness(query.to_point( + query.to_tx(*hash), index))) + { + switch (media) + { + case data: + case text: + send_wire(media, ptr->to_data(false)); + return true; + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size(false)); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_outputs(const code& ec, interface::outputs, + uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + const auto tx = query.to_tx(*hash); + if (tx.is_terminal()) + { + send_not_found({}); + return true; + } + + const auto outputs = query.get_outputs(tx); + if (!outputs || outputs->empty()) + { + send_internal_server_error({}, database::error::integrity); + return true; + } + + // Wire serialization size of outputs set. + const auto size = std::accumulate(outputs->begin(), outputs->end(), zero, + [](size_t total, const auto& output) NOEXCEPT + { return total + output->serialized_size(); }); + + switch (media) + { + case data: + { + data_chunk out(size); + stream::out::fast sink{ out }; + write::bytes::fast writer{ sink }; + for (const auto& output: *outputs) + output->to_data(writer); + + send_chunk({}, std::move(out)); + return true; + } + case text: + { + std::string out{}; + out.resize(two * size); + stream::out::fast sink{ out }; + write::base16::fast writer{ sink }; + for (const auto& output: *outputs) + output->to_data(writer); + + send_text({}, std::move(out)); + return true; + } + case json: + { + send_json({}, value_from(*outputs), two * size); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_output(const code& ec, interface::output, + uint8_t, uint8_t media, const hash_cptr& hash, + uint32_t index) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (const auto ptr = query.get_output(query.to_tx(*hash), index)) + { + switch (media) + { + case data: + case text: + send_wire(media, ptr->to_data()); + return true; + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size()); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_output_script(const code& ec, + interface::output_script, uint8_t, uint8_t media, const hash_cptr& hash, + uint32_t index) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (const auto ptr = query.get_output_script(query.to_output( + query.to_tx(*hash), index))) + { + switch (media) + { + case data: + case text: + send_wire(media, ptr->to_data(false)); + return true; + case json: + send_json({}, value_from(ptr), + two * ptr->serialized_size(false)); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_output_spender(const code& ec, + interface::output_spender, uint8_t, uint8_t media, const hash_cptr& hash, + uint32_t index) NOEXCEPT +{ + if (stopped(ec)) + return false; + + // TODO: query only confirmed spender. + const auto& query = archive(); + const auto spenders = query.to_spenders(*hash, index); + if (spenders.empty()) + { + send_not_found({}); + return true; + } + + if (const auto point = query.get_point(spenders.front()); + point.hash() != null_hash) + { + switch (media) + { + case data: + case text: + send_wire(media, point.to_data()); + return true; + case json: + send_json({}, value_from(point), + two * chain::point::serialized_size()); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_output_spenders(const code& ec, + interface::output_spender, uint8_t, uint8_t media, const hash_cptr& hash, + uint32_t index) NOEXCEPT +{ + if (stopped(ec)) + return false; + + // TODO: iterate, sort by height/position/index. + const auto& query = archive(); + const auto spenders = query.to_spenders(*hash, index); + if (spenders.empty()) + { + send_not_found({}); + return true; + } + + // TODO: iterate, sort in query. + if (const auto point = query.get_point(spenders.front()); + point.hash() != null_hash) + { + switch (media) + { + case data: + case text: + send_wire(media, {}); + return true; + case json: + send_json({}, value_from(point), {}); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_address(const code& ec, interface::address, + uint8_t, uint8_t media, const hash_cptr& hash) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (!query.address_enabled()) + { + send_not_implemented({}); + return true; + } + + // TODO: iterate, sort by height/position/index. + database::output_links outputs{}; + if (!query.to_address_outputs(outputs, *hash)) + { + send_internal_server_error({}, database::error::integrity); + return true; + } + + if (outputs.empty()) + { + send_not_found({}); + return true; + } + + // TODO: iterate, sort in query. + if (const auto ptr = query.get_output(outputs.front())) { switch (media) { - case to_value(media_type::application_octet_stream): - send_data(request, ptr->to_data(witness)); + case data: + case text: + send_wire(media, {}); return true; - case to_value(media_type::text_plain): - send_text(request, encode_base16(ptr->to_data(witness))); + case json: + send_json({}, value_from(*ptr), {}); return true; - case to_value(media_type::application_json): - send_json(request, value_from(ptr), - ptr->serialized_size(witness)); + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_filter(const code& ec, interface::filter, + uint8_t, uint8_t media, uint8_t type, + std::optional hash, + std::optional height) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (!query.filter_enabled()) + { + send_not_implemented({}); + return true; + } + + if (type != client_filter::type_id::neutrino) + { + send_not_found({}); + return true; + } + + data_chunk filter{}; + if (query.get_filter_body(filter, to_header(height, hash))) + { + switch (media) + { + case data: + case text: + send_wire(media, std::move(filter)); + return true; + case json: + send_json({}, value_from(encode_base16(filter)), + two * filter.size()); + return true; + } + } + + send_not_found({}); + return true; +} + +bool protocol_explore::handle_get_filter_hash(const code& ec, + interface::filter_hash, uint8_t, uint8_t media, uint8_t type, + std::optional hash, + std::optional height) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (!query.filter_enabled()) + { + send_not_implemented({}); + return true; + } + + if (type != client_filter::type_id::neutrino) + { + send_not_found({}); + return true; + } + + data_chunk chunk{ hash_size }; + auto& filter_hash = unsafe_array_cast(chunk.data()); + if (query.get_filter_hash(filter_hash, to_header(height, hash))) + { + switch (media) + { + case data: + case text: + send_wire(media, std::move(chunk)); + return true; + case json: + send_json({}, value_from(encode_base16(chunk)), + two * chunk.size()); return true; } } - send_not_found(request); + send_not_found({}); return true; } +bool protocol_explore::handle_get_filter_header(const code& ec, + interface::filter_header, uint8_t, uint8_t media, uint8_t type, + std::optional hash, + std::optional height) NOEXCEPT +{ + if (stopped(ec)) + return false; + + const auto& query = archive(); + if (!query.filter_enabled()) + { + send_not_implemented({}); + return true; + } + + if (type != client_filter::type_id::neutrino) + { + send_not_found({}); + return true; + } + + data_chunk chunk{ hash_size }; + auto& filter_head = unsafe_array_cast(chunk.data()); + if (query.get_filter_head(filter_head, to_header(height, hash))) + { + switch (media) + { + case data: + case text: + send_wire(media, std::move(chunk)); + return true; + case json: + const auto base16 = encode_base16(chunk); + send_json({}, value_from(base16), base16.size()); + return true; + } + } + + send_not_found({}); + return true; +} + +// private +// ---------------------------------------------------------------------------- + +void protocol_explore::send_wire(uint8_t media, data_chunk&& chunk) NOEXCEPT +{ + if (media == data) + send_chunk({}, std::move(chunk)); + else + send_text({}, encode_base16(chunk)); +} + +database::header_link protocol_explore::to_header( + const std::optional& height, + const std::optional& hash) NOEXCEPT +{ + const auto& query = archive(); + + if (hash.has_value()) + return query.to_header(*(hash.value())); + + if (height.has_value()) + return query.to_confirmed(height.value()); + + return {}; +} + BC_POP_WARNING() BC_POP_WARNING() BC_POP_WARNING() diff --git a/src/protocols/protocol_html.cpp b/src/protocols/protocol_html.cpp index 1e94a4e2..4a6ec394 100644 --- a/src/protocols/protocol_html.cpp +++ b/src/protocols/protocol_html.cpp @@ -186,7 +186,7 @@ void protocol_html::send_text(const request& request, SEND(std::move(response), handle_complete, _1, error::success); } -void protocol_html::send_data(const request& request, +void protocol_html::send_chunk(const request& request, system::data_chunk&& bytes) NOEXCEPT { BC_ASSERT(stranded()); diff --git a/test/error.cpp b/test/error.cpp index cd5f9b55..72f5a0a2 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -326,6 +326,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__invalid_id_type__true_exected_message) BOOST_REQUIRE_EQUAL(ec.message(), "invalid_id_type"); } +BOOST_AUTO_TEST_CASE(error_t__code__missing_type_id__true_exected_message) +{ + constexpr auto value = error::missing_type_id; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "missing_type_id"); +} + BOOST_AUTO_TEST_CASE(error_t__code__missing_component__true_exected_message) { constexpr auto value = error::missing_component; diff --git a/test/parse/target.cpp b/test/parse/target.cpp index 867c1c11..df9cdb2e 100644 --- a/test/parse/target.cpp +++ b/test/parse/target.cpp @@ -63,6 +63,48 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__invalid_target__invalid_target) BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/invalid"), node::error::invalid_target); } +// block/height + +BOOST_AUTO_TEST_CASE(parse__parse_target__block_height_valid__expected) +{ + const std::string path = "/v42/block/height/123456"; + + request_t request{}; + BOOST_REQUIRE(!parse_target(request, path)); + BOOST_REQUIRE_EQUAL(request.method, "block"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 2u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); + + const auto height = std::get(object.at("height").value()); + BOOST_REQUIRE_EQUAL(height, 123456u); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__block_height_missing_height__missing_height) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height"), node::error::missing_height); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__block_height_invalid_height__invalid_number) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/invalid"), node::error::invalid_number); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__block_height_invalid_component__invalid_component) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/extra"), node::error::invalid_component); +} + // block/hash BOOST_AUTO_TEST_CASE(parse__parse_target__block_hash_valid__expected) @@ -179,13 +221,13 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__header_hash_extra_segment__extra_segme BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); } -// filter/height +// block_txs/height -BOOST_AUTO_TEST_CASE(parse__parse_target__filter_height_valid__expected) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_height_valid__expected) { request_t request{}; - BOOST_REQUIRE(!parse_target(request, "v42/block/height/123456/filter/")); - BOOST_REQUIRE_EQUAL(request.method, "filter"); + BOOST_REQUIRE(!parse_target(request, "/v42/block/height/123456/transactions")); + BOOST_REQUIRE_EQUAL(request.method, "block_txs"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); @@ -201,21 +243,21 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__filter_height_valid__expected) BOOST_REQUIRE_EQUAL(height, 123456u); } -BOOST_AUTO_TEST_CASE(parse__parse_target__filter_height_extra_segment__extra_segment) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_height_extra_segment__extra_segment) { request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/filter/extra"), node::error::extra_segment); + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transactions/extra"), node::error::extra_segment); } -// filter/hash +// block_txs/hash -BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_valid__expected) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_hash_valid__expected) { - const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/filter"; + const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/transactions"; request_t request{}; BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "filter"); + BOOST_REQUIRE_EQUAL(request.method, "block_txs"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); @@ -235,57 +277,72 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_valid__expected) BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); } -BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_extra_segment__extra_segment) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_hash_extra_segment__extra_segment) { - const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/filter/extra"; + const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transactions/extra"; request_t out{}; BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); } -// block_txs/height +// block_tx/height -BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_height_valid__expected) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_valid__expected) { request_t request{}; - BOOST_REQUIRE(!parse_target(request, "/v42/block/height/123456/transactions")); - BOOST_REQUIRE_EQUAL(request.method, "block_txs"); + BOOST_REQUIRE(!parse_target(request, "/v42/block/height/123456/transaction/7")); + BOOST_REQUIRE_EQUAL(request.method, "block_tx"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); BOOST_REQUIRE(std::holds_alternative(params)); const auto& object = std::get(request.params.value()); - BOOST_REQUIRE_EQUAL(object.size(), 2u); + BOOST_REQUIRE_EQUAL(object.size(), 3u); const auto version = std::get(object.at("version").value()); BOOST_REQUIRE_EQUAL(version, 42u); const auto height = std::get(object.at("height").value()); BOOST_REQUIRE_EQUAL(height, 123456u); + + const auto position = std::get(object.at("position").value()); + BOOST_REQUIRE_EQUAL(position, 7u); } -BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_height_extra_segment__extra_segment) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_missing_position__missing_position) { request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transactions/extra"), node::error::extra_segment); + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transaction"), node::error::missing_position); } -// block_txs/hash +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_invalid_position__invalid_number) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transaction/invalid"), node::error::invalid_number); +} -BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_hash_valid__expected) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_extra_segment__extra_segment) { - const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/transactions"; + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transaction/7/extra"), node::error::extra_segment); +} + +// block_tx/hash + +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_valid__expected) +{ + const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/transaction/7"; request_t request{}; BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "block_txs"); + BOOST_REQUIRE_EQUAL(request.method, "block_tx"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); BOOST_REQUIRE(std::holds_alternative(params)); const auto& object = std::get(request.params.value()); - BOOST_REQUIRE_EQUAL(object.size(), 2u); + BOOST_REQUIRE_EQUAL(object.size(), 3u); const auto version = std::get(object.at("version").value()); BOOST_REQUIRE_EQUAL(version, 42u); @@ -296,74 +353,93 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_hash_valid__expected) const auto& hash_cptr = any.get(); BOOST_REQUIRE(hash_cptr); BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); + + const auto position = std::get(object.at("position").value()); + BOOST_REQUIRE_EQUAL(position, 7u); } -BOOST_AUTO_TEST_CASE(parse__parse_target__block_txs_hash_extra_segment__extra_segment) +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_missing_position__missing_position) { - const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transactions/extra"; + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transaction"), node::error::missing_position); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_invalid_position__invalid_number) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transaction/invalid"), node::error::invalid_number); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_extra_segment__extra_segment) +{ + const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transaction/7/extra"; request_t out{}; BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); } -// block_tx/height +// transaction -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_valid__expected) +BOOST_AUTO_TEST_CASE(parse__parse_target__transaction_valid__expected) { + const std::string path = "/v42/transaction/0000000000000000000000000000000000000000000000000000000000000042"; + request_t request{}; - BOOST_REQUIRE(!parse_target(request, "/v42/block/height/123456/transaction/7")); - BOOST_REQUIRE_EQUAL(request.method, "block_tx"); + BOOST_REQUIRE(!parse_target(request, path)); + BOOST_REQUIRE_EQUAL(request.method, "transaction"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); BOOST_REQUIRE(std::holds_alternative(params)); const auto& object = std::get(request.params.value()); - BOOST_REQUIRE_EQUAL(object.size(), 3u); + BOOST_REQUIRE_EQUAL(object.size(), 2u); const auto version = std::get(object.at("version").value()); BOOST_REQUIRE_EQUAL(version, 42u); - const auto height = std::get(object.at("height").value()); - BOOST_REQUIRE_EQUAL(height, 123456u); + const auto& any = std::get(object.at("hash").value()); + BOOST_REQUIRE(any.holds_alternative()); - const auto position = std::get(object.at("position").value()); - BOOST_REQUIRE_EQUAL(position, 7u); + const auto& hash_cptr = any.get(); + BOOST_REQUIRE(hash_cptr); + BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); } -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_missing_position__missing_position) +BOOST_AUTO_TEST_CASE(parse__parse_target__transaction_missing_hash__missing_hash) { request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transaction"), node::error::missing_position); + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/transaction"), node::error::missing_hash); } -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_invalid_position__invalid_number) +BOOST_AUTO_TEST_CASE(parse__parse_target__transaction_invalid_hash__invalid_hash) { request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transaction/invalid"), node::error::invalid_number); + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/transaction/invalidhex"), node::error::invalid_hash); } -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_height_extra_segment__extra_segment) +BOOST_AUTO_TEST_CASE(parse__parse_target__transaction_invalid_component__invalid_component) { + const std::string path = "/v3/transaction/0000000000000000000000000000000000000000000000000000000000000000/extra"; request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/transaction/7/extra"), node::error::extra_segment); + BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::invalid_component); } -// block_tx/hash +// tx_block -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_valid__expected) +BOOST_AUTO_TEST_CASE(parse__parse_target__tx_block_valid__expected) { - const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/transaction/7"; + const std::string path = "/v42/transaction/0000000000000000000000000000000000000000000000000000000000000042/block"; request_t request{}; BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "block_tx"); + BOOST_REQUIRE_EQUAL(request.method, "tx_block"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); BOOST_REQUIRE(std::holds_alternative(params)); const auto& object = std::get(request.params.value()); - BOOST_REQUIRE_EQUAL(object.size(), 3u); + BOOST_REQUIRE_EQUAL(object.size(), 2u); const auto version = std::get(object.at("version").value()); BOOST_REQUIRE_EQUAL(version, 42u); @@ -374,26 +450,18 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_valid__expected) const auto& hash_cptr = any.get(); BOOST_REQUIRE(hash_cptr); BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); - - const auto position = std::get(object.at("position").value()); - BOOST_REQUIRE_EQUAL(position, 7u); -} - -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_missing_position__missing_position) -{ - request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transaction"), node::error::missing_position); } -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_invalid_position__invalid_number) +BOOST_AUTO_TEST_CASE(parse__parse_target__tx_block_invalid_component__invalid_component) { + const std::string path = "/v3/transaction/0000000000000000000000000000000000000000000000000000000000000000/invalid"; request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transaction/invalid"), node::error::invalid_number); + BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::invalid_component); } -BOOST_AUTO_TEST_CASE(parse__parse_target__block_tx_hash_extra_segment__extra_segment) +BOOST_AUTO_TEST_CASE(parse__parse_target__tx_block_extra_segment__extra_segment) { - const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/transaction/7/extra"; + const std::string path = "/v3/transaction/0000000000000000000000000000000000000000000000000000000000000000/block/extra"; request_t out{}; BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); } @@ -406,7 +474,7 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__inputs_valid__expected) request_t request{}; BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "input"); + BOOST_REQUIRE_EQUAL(request.method, "inputs"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); @@ -549,38 +617,38 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__input_script_extra_segment__extra_segm // input_scripts -BOOST_AUTO_TEST_CASE(parse__parse_target__input_scripts_valid__expected) -{ - const std::string path = "/v255/input/0000000000000000000000000000000000000000000000000000000000000042/scripts"; - - request_t request{}; - BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "input_script"); - BOOST_REQUIRE(request.params.has_value()); - - const auto& params = request.params.value(); - BOOST_REQUIRE(std::holds_alternative(params)); - - const auto& object = std::get(request.params.value()); - BOOST_REQUIRE_EQUAL(object.size(), 2u); - - const auto version = std::get(object.at("version").value()); - BOOST_REQUIRE_EQUAL(version, 255u); - - const auto& any = std::get(object.at("hash").value()); - BOOST_REQUIRE(any.holds_alternative()); - - const auto& hash_cptr = any.get(); - BOOST_REQUIRE(hash_cptr); - BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); -} - -BOOST_AUTO_TEST_CASE(parse__parse_target__input_scripts_extra_segment__extra_segment) -{ - const std::string path = "/v3/input/0000000000000000000000000000000000000000000000000000000000000000/scripts/extra"; - request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); -} +////BOOST_AUTO_TEST_CASE(parse__parse_target__input_scripts_valid__expected) +////{ +//// const std::string path = "/v255/input/0000000000000000000000000000000000000000000000000000000000000042/scripts"; +//// +//// request_t request{}; +//// BOOST_REQUIRE(!parse_target(request, path)); +//// BOOST_REQUIRE_EQUAL(request.method, "input_scripts"); +//// BOOST_REQUIRE(request.params.has_value()); +//// +//// const auto& params = request.params.value(); +//// BOOST_REQUIRE(std::holds_alternative(params)); +//// +//// const auto& object = std::get(request.params.value()); +//// BOOST_REQUIRE_EQUAL(object.size(), 2u); +//// +//// const auto version = std::get(object.at("version").value()); +//// BOOST_REQUIRE_EQUAL(version, 255u); +//// +//// const auto& any = std::get(object.at("hash").value()); +//// BOOST_REQUIRE(any.holds_alternative()); +//// +//// const auto& hash_cptr = any.get(); +//// BOOST_REQUIRE(hash_cptr); +//// BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); +////} +//// +////BOOST_AUTO_TEST_CASE(parse__parse_target__input_scripts_extra_segment__extra_segment) +////{ +//// const std::string path = "/v3/input/0000000000000000000000000000000000000000000000000000000000000000/scripts/extra"; +//// request_t out{}; +//// BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); +////} // input_witness @@ -622,38 +690,38 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__input_witness_extra_segment__extra_seg // input_witnesses -BOOST_AUTO_TEST_CASE(parse__parse_target__input_witnesses_valid__expected) -{ - const std::string path = "/v255/input/0000000000000000000000000000000000000000000000000000000000000042/witnesses"; - - request_t request{}; - BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "input_witness"); - BOOST_REQUIRE(request.params.has_value()); - - const auto& params = request.params.value(); - BOOST_REQUIRE(std::holds_alternative(params)); - - const auto& object = std::get(request.params.value()); - BOOST_REQUIRE_EQUAL(object.size(), 2u); - - const auto version = std::get(object.at("version").value()); - BOOST_REQUIRE_EQUAL(version, 255u); - - const auto& any = std::get(object.at("hash").value()); - BOOST_REQUIRE(any.holds_alternative()); - - const auto& hash_cptr = any.get(); - BOOST_REQUIRE(hash_cptr); - BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); -} - -BOOST_AUTO_TEST_CASE(parse__parse_target__input_witnesses_extra_segment__extra_segment) -{ - const std::string path = "/v3/input/0000000000000000000000000000000000000000000000000000000000000000/witnesses/extra"; - request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); -} +////BOOST_AUTO_TEST_CASE(parse__parse_target__input_witnesses_valid__expected) +////{ +//// const std::string path = "/v255/input/0000000000000000000000000000000000000000000000000000000000000042/witnesses"; +//// +//// request_t request{}; +//// BOOST_REQUIRE(!parse_target(request, path)); +//// BOOST_REQUIRE_EQUAL(request.method, "input_witnesses"); +//// BOOST_REQUIRE(request.params.has_value()); +//// +//// const auto& params = request.params.value(); +//// BOOST_REQUIRE(std::holds_alternative(params)); +//// +//// const auto& object = std::get(request.params.value()); +//// BOOST_REQUIRE_EQUAL(object.size(), 2u); +//// +//// const auto version = std::get(object.at("version").value()); +//// BOOST_REQUIRE_EQUAL(version, 255u); +//// +//// const auto& any = std::get(object.at("hash").value()); +//// BOOST_REQUIRE(any.holds_alternative()); +//// +//// const auto& hash_cptr = any.get(); +//// BOOST_REQUIRE(hash_cptr); +//// BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); +////} +//// +////BOOST_AUTO_TEST_CASE(parse__parse_target__input_witnesses_extra_segment__extra_segment) +////{ +//// const std::string path = "/v3/input/0000000000000000000000000000000000000000000000000000000000000000/witnesses/extra"; +//// request_t out{}; +//// BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); +////} // outputs @@ -663,7 +731,7 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__outputs_valid__expected) request_t request{}; BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "output"); + BOOST_REQUIRE_EQUAL(request.method, "outputs"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); @@ -794,38 +862,38 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__output_script_extra_segment__extra_seg // output_scripts -BOOST_AUTO_TEST_CASE(parse__parse_target__output_scripts_valid__expected) -{ - const std::string path = "/v255/output/0000000000000000000000000000000000000000000000000000000000000042/scripts"; - - request_t request{}; - BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "output_script"); - BOOST_REQUIRE(request.params.has_value()); - - const auto& params = request.params.value(); - BOOST_REQUIRE(std::holds_alternative(params)); - - const auto& object = std::get(request.params.value()); - BOOST_REQUIRE_EQUAL(object.size(), 2u); - - const auto version = std::get(object.at("version").value()); - BOOST_REQUIRE_EQUAL(version, 255u); - - const auto& any = std::get(object.at("hash").value()); - BOOST_REQUIRE(any.holds_alternative()); - - const auto& hash_cptr = any.get(); - BOOST_REQUIRE(hash_cptr); - BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); -} - -BOOST_AUTO_TEST_CASE(parse__parse_target__output_scripts_extra_segment__extra_segment) -{ - const std::string path = "/v3/output/0000000000000000000000000000000000000000000000000000000000000000/scripts/extra"; - request_t out{}; - BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); -} +////BOOST_AUTO_TEST_CASE(parse__parse_target__output_scripts_valid__expected) +////{ +//// const std::string path = "/v255/output/0000000000000000000000000000000000000000000000000000000000000042/scripts"; +//// +//// request_t request{}; +//// BOOST_REQUIRE(!parse_target(request, path)); +//// BOOST_REQUIRE_EQUAL(request.method, "output_scripts"); +//// BOOST_REQUIRE(request.params.has_value()); +//// +//// const auto& params = request.params.value(); +//// BOOST_REQUIRE(std::holds_alternative(params)); +//// +//// const auto& object = std::get(request.params.value()); +//// BOOST_REQUIRE_EQUAL(object.size(), 2u); +//// +//// const auto version = std::get(object.at("version").value()); +//// BOOST_REQUIRE_EQUAL(version, 255u); +//// +//// const auto& any = std::get(object.at("hash").value()); +//// BOOST_REQUIRE(any.holds_alternative()); +//// +//// const auto& hash_cptr = any.get(); +//// BOOST_REQUIRE(hash_cptr); +//// BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); +////} +//// +////BOOST_AUTO_TEST_CASE(parse__parse_target__output_scripts_extra_segment__extra_segment) +////{ +//// const std::string path = "/v3/output/0000000000000000000000000000000000000000000000000000000000000000/scripts/extra"; +//// request_t out{}; +//// BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); +////} // output_spender @@ -873,7 +941,7 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__output_spenders_valid__expected) request_t request{}; BOOST_REQUIRE(!parse_target(request, path)); - BOOST_REQUIRE_EQUAL(request.method, "output_spender"); + BOOST_REQUIRE_EQUAL(request.method, "output_spenders"); BOOST_REQUIRE(request.params.has_value()); const auto& params = request.params.value(); @@ -947,4 +1015,232 @@ BOOST_AUTO_TEST_CASE(parse__parse_target__address_extra_segment__extra_segment) BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); } +// filter/height + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_height_valid__expected) +{ + request_t request{}; + BOOST_REQUIRE(!parse_target(request, "v42/block/height/123456/filter/255")); + BOOST_REQUIRE_EQUAL(request.method, "filter"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 3u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); + + const auto height = std::get(object.at("height").value()); + BOOST_REQUIRE_EQUAL(height, 123456u); + + const auto type = std::get(object.at("type").value()); + BOOST_REQUIRE_EQUAL(type, 255u); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_height_invalid_subcomponent__invalid_subcomponent) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/filter/42/extra"), node::error::invalid_subcomponent); +} + +// filter/hash + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_valid__expected) +{ + const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/filter/255"; + + request_t request{}; + BOOST_REQUIRE(!parse_target(request, path)); + BOOST_REQUIRE_EQUAL(request.method, "filter"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 3u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); + + const auto& any = std::get(object.at("hash").value()); + BOOST_REQUIRE(any.holds_alternative()); + + const auto& hash_cptr = any.get(); + BOOST_REQUIRE(hash_cptr); + BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); + + const auto type = std::get(object.at("type").value()); + BOOST_REQUIRE_EQUAL(type, 255u); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_invalid_subcomponent__invalid_subcomponent) +{ + const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/filter/42/extra"; + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::invalid_subcomponent); +} + +// filter_hash/height + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_height_valid__expected) +{ + request_t request{}; + BOOST_REQUIRE(!parse_target(request, "/v42/block/height/123456/filter/255/hash")); + BOOST_REQUIRE_EQUAL(request.method, "filter_hash"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 3u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); + + const auto height = std::get(object.at("height").value()); + BOOST_REQUIRE_EQUAL(height, 123456u); + + const auto type = std::get(object.at("type").value()); + BOOST_REQUIRE_EQUAL(type, 255u); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_height_extra_segment__extra_segment) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/filter/42/hash/extra"), node::error::extra_segment); +} + +// filter_hash/hash + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_hash_valid__expected) +{ + const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/filter/255/hash"; + + request_t request{}; + BOOST_REQUIRE(!parse_target(request, path)); + BOOST_REQUIRE_EQUAL(request.method, "filter_hash"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 3u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); + + const auto& any = std::get(object.at("hash").value()); + BOOST_REQUIRE(any.holds_alternative()); + + const auto& hash_cptr = any.get(); + BOOST_REQUIRE(hash_cptr); + BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); + + const auto type = std::get(object.at("type").value()); + BOOST_REQUIRE_EQUAL(type, 255u); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_hash_hash_extra_segment__extra_segment) +{ + const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/filter/42/hash/extra"; + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); +} + +// filter_header/height + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_header_height_valid__expected) +{ + request_t request{}; + BOOST_REQUIRE(!parse_target(request, "/v42/block/height/123456/filter/255/header")); + BOOST_REQUIRE_EQUAL(request.method, "filter_header"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 3u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); + + const auto height = std::get(object.at("height").value()); + BOOST_REQUIRE_EQUAL(height, 123456u); + + const auto type = std::get(object.at("type").value()); + BOOST_REQUIRE_EQUAL(type, 255u); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_header_height_extra_segment__extra_segment) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/filter/42/header/extra"), node::error::extra_segment); +} + +// filter_header/hash + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_header_hash_valid__expected) +{ + const std::string path = "/v42/block/hash/0000000000000000000000000000000000000000000000000000000000000042/filter/255/header"; + + request_t request{}; + BOOST_REQUIRE(!parse_target(request, path)); + BOOST_REQUIRE_EQUAL(request.method, "filter_header"); + BOOST_REQUIRE(request.params.has_value()); + + const auto& params = request.params.value(); + BOOST_REQUIRE(std::holds_alternative(params)); + + const auto& object = std::get(request.params.value()); + BOOST_REQUIRE_EQUAL(object.size(), 3u); + + const auto version = std::get(object.at("version").value()); + BOOST_REQUIRE_EQUAL(version, 42u); + + const auto& any = std::get(object.at("hash").value()); + BOOST_REQUIRE(any.holds_alternative()); + + const auto& hash_cptr = any.get(); + BOOST_REQUIRE(hash_cptr); + BOOST_REQUIRE_EQUAL(to_uintx(*hash_cptr), uint256_t{ 0x42 }); + + const auto type = std::get(object.at("type").value()); + BOOST_REQUIRE_EQUAL(type, 255u); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_header_hash_extra_segment__extra_segment) +{ + const std::string path = "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/filter/42/header/extra"; + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, path), node::error::extra_segment); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_missing_type_id__missing_type_id) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/filter"), node::error::missing_type_id); + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/filter"), node::error::missing_type_id); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_invalid_type__invalid_number) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/filter/invalid"), node::error::invalid_number); + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/filter/invalid"), node::error::invalid_number); +} + +BOOST_AUTO_TEST_CASE(parse__parse_target__filter_invalid_subcomponent__invalid_subcomponent) +{ + request_t out{}; + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/height/123/filter/42/invalid"), node::error::invalid_subcomponent); + BOOST_REQUIRE_EQUAL(parse_target(out, "/v3/block/hash/0000000000000000000000000000000000000000000000000000000000000000/filter/42/invalid"), node::error::invalid_subcomponent); +} + BOOST_AUTO_TEST_SUITE_END()