diff --git a/console/executor.cpp b/console/executor.cpp index 3e1cf37a..8b385d07 100644 --- a/console/executor.cpp +++ b/console/executor.cpp @@ -44,6 +44,10 @@ std::optional executor::poller_thread_{}; executor& executor::factory(parser& metadata, std::istream& input, std::ostream& output, std::ostream& error) { + // TODO: expose fork_flags() mapping from system static chain_state method. + ////metadata.configured.database.fork_flags = + //// metadata.configured.bitcoin.fork_flags(); + static executor instance(metadata, input, output, error); return instance; } diff --git a/console/executor_store.cpp b/console/executor_store.cpp index 6b561ced..cf87f205 100644 --- a/console/executor_store.cpp +++ b/console/executor_store.cpp @@ -113,6 +113,14 @@ code executor::open_store_coded(bool details) return ec; } + // TODO: compare configured fork flags to genesis archive. + ////if (!query_.consistent_forks()) + ////{ + //// const code ec{ database::error::inconsistent_forks }; + //// logger(format(BS_DATABASE_START_FAIL) % ec.message()); + //// return ec; + ////} + logger(BS_DATABASE_STARTED); return error::success; } diff --git a/test/protocols/bitcoind/bitcoind_rpc.cpp b/test/protocols/bitcoind/bitcoind_rpc.cpp index 8b7c8f2b..6d4439ca 100644 --- a/test/protocols/bitcoind/bitcoind_rpc.cpp +++ b/test/protocols/bitcoind/bitcoind_rpc.cpp @@ -216,6 +216,84 @@ BOOST_AUTO_TEST_CASE(bitcoind_rpc__getblockfilter__filters_disabled__error) BOOST_REQUIRE(has_error(response)); } +// batch +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(bitcoind_rpc__batch__two_requests__two_ordered_responses) +{ + const auto response = rpc_body( + R"([{"jsonrpc":"2.0","id":1,"method":"getblockcount","params":[]},)" + R"({"jsonrpc":"2.0","id":2,"method":"getbestblockhash","params":[]}])"); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 2u); + BOOST_REQUIRE_EQUAL(batch.at(0).at("id").as_int64(), 1); + BOOST_REQUIRE_EQUAL(batch.at(0).at("result").as_int64(), 9); + BOOST_REQUIRE_EQUAL(batch.at(1).at("id").as_int64(), 2); + BOOST_REQUIRE_EQUAL(as_text(batch.at(1).at("result")), block9); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rpc__batch__single_element__array_of_one) +{ + const auto response = rpc_body( + R"([{"jsonrpc":"2.0","id":7,"method":"getblockcount","params":[]}])"); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 1u); + BOOST_REQUIRE_EQUAL(batch.at(0).at("id").as_int64(), 7); + BOOST_REQUIRE_EQUAL(batch.at(0).at("result").as_int64(), 9); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rpc__batch__error_element__delivered_in_order) +{ + const auto unknown = hash_param(null_hash, "1"); + const auto response = rpc_body((boost_format( + R"([{"jsonrpc":"2.0","id":1,"method":"getblockcount","params":[]},)" + R"({"jsonrpc":"2.0","id":2,"method":"getrawtransaction","params":%1%},)" + R"({"jsonrpc":"2.0","id":3,"method":"getblockcount","params":[]}])") % + unknown).str()); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 3u); + BOOST_REQUIRE_EQUAL(batch.at(0).at("result").as_int64(), 9); + BOOST_REQUIRE_EQUAL(batch.at(1).at("id").as_int64(), 2); + BOOST_REQUIRE(has_error(batch.at(1))); + BOOST_REQUIRE_EQUAL(batch.at(2).at("result").as_int64(), 9); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rpc__batch__v1_second_element__closed_partial_batch) +{ + // The batched v1 policy stop closes the open batch response (the first + // element was delivered and responded). + const auto response = rpc_body( + R"([{"jsonrpc":"2.0","id":1,"method":"getblockcount","params":[]},)" + R"({"id":2,"method":"getblockcount","params":[]}])"); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 1u); + BOOST_REQUIRE_EQUAL(batch.at(0).at("id").as_int64(), 1); + BOOST_REQUIRE_EQUAL(batch.at(0).at("result").as_int64(), 9); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rpc__batch__v1_element__dropped) +{ + // Batch participation of a v1 message (no jsonrpc field) drops (http). + const auto response = rpc_body( + R"([{"id":1,"method":"getblockcount","params":[]}])"); + + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(bitcoind_rpc__batch__empty__dropped) +{ + const auto response = rpc_body("[]"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + BOOST_AUTO_TEST_SUITE_END() // segwit (witness store: genesis + two blocks carrying witness transactions) diff --git a/test/protocols/bitcoind/bitcoind_setup_fixture.cpp b/test/protocols/bitcoind/bitcoind_setup_fixture.cpp index c6b3714b..dc924132 100644 --- a/test/protocols/bitcoind/bitcoind_setup_fixture.cpp +++ b/test/protocols/bitcoind/bitcoind_setup_fixture.cpp @@ -127,6 +127,18 @@ boost::json::value bitcoind_setup_fixture::rpc(std::string_view method, return test::parse_json(response.body()); } +boost::json::value bitcoind_setup_fixture::rpc_body(std::string_view body) +{ + http::write(socket_, create_post("/", body)); + + flat_buffer buffer{}; + network::boost_code ec{}; + http::response response{}; + http::read(socket_, buffer, response, ec); + return ec ? boost::json::parse(R"({"dropped":true})") : + test::parse_json(response.body()); +} + bitcoind_setup_fixture::status bitcoind_setup_fixture::rest_status(std::string_view target) { diff --git a/test/protocols/bitcoind/bitcoind_setup_fixture.hpp b/test/protocols/bitcoind/bitcoind_setup_fixture.hpp index dc9ca1b7..8d031259 100644 --- a/test/protocols/bitcoind/bitcoind_setup_fixture.hpp +++ b/test/protocols/bitcoind/bitcoind_setup_fixture.hpp @@ -37,6 +37,10 @@ struct bitcoind_setup_fixture // object). Returns the parsed json-rpc response object (with result/error). boost::json::value rpc(std::string_view method, std::string_view params="[]"); + // JSON-RPC over HTTP POST to "/" with a raw body (e.g. a batch). Returns + // the parsed json response, or {"dropped":true} if the channel dropped. + boost::json::value rpc_body(std::string_view body); + // bitcoind REST over HTTP GET (target under "/rest/..."). status rest_status(std::string_view target); boost::json::value rest_json(std::string_view target); diff --git a/test/protocols/electrum/electrum_version.cpp b/test/protocols/electrum/electrum_version.cpp index 897739e9..f59c21b5 100644 --- a/test/protocols/electrum/electrum_version.cpp +++ b/test/protocols/electrum/electrum_version.cpp @@ -154,4 +154,86 @@ BOOST_AUTO_TEST_CASE(electrum__server_version__client_name_overflow__invalid_arg BOOST_REQUIRE_EQUAL(response.at("error").as_object().at("code").as_int64(), invalid_argument.value()); } +// batch +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(electrum__batch__two_requests__two_ordered_responses) +{ + const auto response = get( + R"([{"jsonrpc":"2.0","id":1,"method":"server.version","params":["a","1.4"]},)" + R"({"jsonrpc":"2.0","id":2,"method":"server.version","params":["b","1.4"]}])" "\n"); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 2u); + REQUIRE_NO_THROW_TRUE(batch.at(0).at("id").is_int64()); + BOOST_REQUIRE_EQUAL(batch.at(0).at("id").as_int64(), 1); + BOOST_REQUIRE_EQUAL(batch.at(1).at("id").as_int64(), 2); + REQUIRE_NO_THROW_TRUE(batch.at(0).at("result").is_array()); + REQUIRE_NO_THROW_TRUE(batch.at(1).at("result").is_array()); + BOOST_REQUIRE_EQUAL(batch.at(0).at("result").as_array().at(1).as_string(), "1.4"); + BOOST_REQUIRE_EQUAL(batch.at(1).at("result").as_array().at(1).as_string(), "1.4"); +} + +BOOST_AUTO_TEST_CASE(electrum__batch__single_element__array_of_one) +{ + const auto response = get( + R"([{"jsonrpc":"2.0","id":7,"method":"server.version","params":["a","1.4"]}])" "\n"); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 1u); + REQUIRE_NO_THROW_TRUE(batch.at(0).at("id").is_int64()); + BOOST_REQUIRE_EQUAL(batch.at(0).at("id").as_int64(), 7); + REQUIRE_NO_THROW_TRUE(batch.at(0).at("result").is_array()); +} + +BOOST_AUTO_TEST_CASE(electrum__batch__result_and_error__both_delivered_in_order) +{ + // The second element depends upon the negotiation by the first. + const auto response = get( + R"([{"jsonrpc":"2.0","id":3,"method":"server.version","params":["a","1.4"]},)" + R"({"jsonrpc":"2.0","id":4,"method":"blockchain.transaction.get","params":["",false]}])" "\n"); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 2u); + REQUIRE_NO_THROW_TRUE(batch.at(0).at("result").is_array()); + BOOST_REQUIRE_EQUAL(batch.at(0).at("id").as_int64(), 3); + BOOST_REQUIRE_EQUAL(batch.at(1).at("id").as_int64(), 4); + REQUIRE_NO_THROW_TRUE(batch.at(1).at("error").as_object().at("code").is_int64()); + BOOST_REQUIRE_EQUAL(batch.at(1).at("error").as_object().at("code").as_int64(), invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__batch__failed_negotiation__closed_error_batch) +{ + // Failed first negotiation stops the channel, which closes the open + // batch response (the second element is abandoned). + const auto response = get( + R"([{"jsonrpc":"2.0","id":3,"method":"server.version","params":["a","42"]},)" + R"({"jsonrpc":"2.0","id":4,"method":"server.version","params":["b","1.4"]}])" "\n"); + + BOOST_REQUIRE(response.is_array()); + const auto& batch = response.as_array(); + BOOST_REQUIRE_EQUAL(batch.size(), 1u); + REQUIRE_NO_THROW_TRUE(batch.at(0).at("error").as_object().at("code").is_int64()); + BOOST_REQUIRE_EQUAL(batch.at(0).at("id").as_int64(), 3); + BOOST_REQUIRE_EQUAL(batch.at(0).at("error").as_object().at("code").as_int64(), invalid_argument.value()); +} + +BOOST_AUTO_TEST_CASE(electrum__batch__v1_element__dropped) +{ + // Batch participation of a v1 message (no jsonrpc field) drops. + const auto response = get( + R"([{"id":5,"method":"server.version","params":["a","1.4"]}])" "\n"); + + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + +BOOST_AUTO_TEST_CASE(electrum__batch__empty__dropped) +{ + const auto response = get("[]\n"); + REQUIRE_NO_THROW_TRUE(response.at("dropped").as_bool()); +} + BOOST_AUTO_TEST_SUITE_END()