Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions src/protocols/electrum/protocol_electrum_headers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,10 @@ void protocol_electrum::blockchain_block_headers(size_t starting,
size_t quantity, size_t waypoint, bool single) NOEXCEPT
{
const auto prove = !is_zero(quantity) && !is_zero(waypoint);
const auto target = starting + sub1(quantity);
const auto& query = archive();
const auto top = query.get_top_confirmed();
using namespace system;

// The documented requirement: `start_height + (count - 1) <= cp_height` is
// ambiguous at count = 0 so guard must be applied to both args and prover.
if (is_add_overflow(starting, quantity))
{
send_code(error::electrum::bad_request);
Expand All @@ -218,11 +215,6 @@ void protocol_electrum::blockchain_block_headers(size_t starting,
send_code(error::electrum::bad_request);
return;
}
else if (prove && target > waypoint)
{
send_code(error::electrum::bad_request);
return;
}

// Recommended to be at least one difficulty retarget period, e.g. 2016.
// The maximum number of headers the server will return in single request.
Expand All @@ -232,14 +224,24 @@ void protocol_electrum::blockchain_block_headers(size_t starting,
// No headers may be returned, which implies start > confirmed top block.
const auto count = limit(quantity, maximum_headers);
const auto links = query.get_confirmed_headers(starting, count);
auto size = two * chain::header::serialized_size() * links.size();

if (single && !is_one(links.size()))
{
send_code(error::electrum::daemon_error);
return;
}

// The proof is over the last returned header, which is the last requested
// header only when the returned count is not reduced.
const auto proving = prove && !is_zero(links.size());
const auto proof_height = proving ? starting + sub1(links.size()) : zero;

if (proving && proof_height > waypoint)
{
send_code(error::electrum::bad_request);
return;
}

value_t value{};

if (single && !prove)
Expand Down Expand Up @@ -288,6 +290,8 @@ void protocol_electrum::blockchain_block_headers(size_t starting,
else
{
// Stream headers into single buffer.
const auto size = two * chain::header::serialized_size() *
links.size();
std::string headers(size, '\0');
stream::out::fast sink{ headers };
write::base16::fast writer{ sink };
Expand All @@ -312,15 +316,15 @@ void protocol_electrum::blockchain_block_headers(size_t starting,
}
}

if (prove)
if (proving)
{
// A very slim chance of inconsistency given an intervening reorg
// because of get_merkle_root_and_proof() and height-based calcs.
// This is acceptable as must be verified by caller in any case.
hashes proof{};
hash_digest root{};
if (const auto code = query.get_merkle_root_and_proof(root, proof,
target, waypoint))
proof_height, waypoint))
{
using namespace error::electrum;
send_code(translate(code, daemon_error));
Expand All @@ -333,7 +337,6 @@ void protocol_electrum::blockchain_block_headers(size_t starting,

result["branch"] = std::move(branch);
result["root"] = encode_hash(root);
size += two * hash_size * add1(proof.size());
}

value = std::move(result);
Expand Down Expand Up @@ -379,7 +382,6 @@ void protocol_electrum::handle_blockchain_headers_subscribe(const code& ec,
return;
}

size_t size{};
boost::json::value value{};
if (raw)
{
Expand All @@ -390,7 +392,6 @@ void protocol_electrum::handle_blockchain_headers_subscribe(const code& ec,
return;
}

size = two * chain::header::serialized_size();
value =
{
{ "height", top },
Expand All @@ -414,7 +415,6 @@ void protocol_electrum::handle_blockchain_headers_subscribe(const code& ec,
return;
}

size = 256;
auto& object = value.as_object();
object["block_height"] = top;
}
Expand Down
216 changes: 216 additions & 0 deletions test/protocols/electrum/electrum_headers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,222 @@ BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__proof_offset__expected)
BOOST_REQUIRE_EQUAL(branch.at(3).as_string(), expected_branch[3]);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__proof_count_exceeds_max__proves_last_returned)
{
BOOST_REQUIRE(handshake(electrum::version::v1_6));

using namespace test;
const auto expected_root = encode_hash(merkle_root(
{
block0_hash,
block1_hash,
block2_hash,
block3_hash,
block4_hash,
block5_hash,
block6_hash,
block7_hash,
block8_hash
}));

const string_list expected_branch
{
encode_hash(block5_hash),
encode_hash(root67),
encode_hash(root03),
encode_hash(root88)
};

const auto response = get(R"({"id":73,"method":"blockchain.block.headers","params":[0,6,8]})" "\n");
BOOST_REQUIRE_MESSAGE(response.is_object() && response.as_object().contains("result"), serialize(response));
REQUIRE_NO_THROW_TRUE(response.at("result").is_object());

const auto& result = response.at("result").as_object();
REQUIRE_NO_THROW_TRUE(result.at("max").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("count").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("headers").is_array());
REQUIRE_NO_THROW_TRUE(result.at("root").is_string());
BOOST_REQUIRE_EQUAL(result.at("max").as_int64(), 5);
BOOST_REQUIRE_EQUAL(result.at("count").as_int64(), 5);
BOOST_REQUIRE_EQUAL(result.at("headers").as_array().size(), 5u);
BOOST_REQUIRE_EQUAL(result.at("root").as_string(), expected_root);

const auto& branch = result.at("branch").as_array();
BOOST_REQUIRE(branch.at(0).is_string());
BOOST_REQUIRE_EQUAL(branch.size(), expected_branch.size());
BOOST_REQUIRE_EQUAL(branch.at(0).as_string(), expected_branch[0]);
BOOST_REQUIRE_EQUAL(branch.at(1).as_string(), expected_branch[1]);
BOOST_REQUIRE_EQUAL(branch.at(2).as_string(), expected_branch[2]);
BOOST_REQUIRE_EQUAL(branch.at(3).as_string(), expected_branch[3]);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__proof_count_exceeds_max_v1_4__proves_last_returned)
{
BOOST_REQUIRE(handshake(electrum::version::v1_4));

using namespace test;
const auto expected_root = encode_hash(merkle_root(
{
block0_hash,
block1_hash,
block2_hash,
block3_hash,
block4_hash,
block5_hash,
block6_hash,
block7_hash,
block8_hash
}));

const string_list expected_branch
{
encode_hash(block5_hash),
encode_hash(root67),
encode_hash(root03),
encode_hash(root88)
};

const auto response = get(R"({"id":74,"method":"blockchain.block.headers","params":[0,6,8]})" "\n");
BOOST_REQUIRE_MESSAGE(response.is_object() && response.as_object().contains("result"), serialize(response));
REQUIRE_NO_THROW_TRUE(response.at("result").is_object());

const auto& result = response.at("result").as_object();
REQUIRE_NO_THROW_TRUE(result.at("max").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("count").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("hex").is_string());
REQUIRE_NO_THROW_TRUE(result.at("root").is_string());
BOOST_REQUIRE_EQUAL(result.at("max").as_int64(), 5);
BOOST_REQUIRE_EQUAL(result.at("count").as_int64(), 5);
BOOST_REQUIRE_EQUAL(result.at("root").as_string(), expected_root);

// "hex" prior to v1.6
const auto expected = encode_base16(header0_data) + encode_base16(header1_data) + encode_base16(header2_data) + encode_base16(header3_data) + encode_base16(header4_data);
BOOST_REQUIRE_EQUAL(result.at("hex").as_string(), expected);

const auto& branch = result.at("branch").as_array();
BOOST_REQUIRE(branch.at(0).is_string());
BOOST_REQUIRE_EQUAL(branch.size(), expected_branch.size());
BOOST_REQUIRE_EQUAL(branch.at(0).as_string(), expected_branch[0]);
BOOST_REQUIRE_EQUAL(branch.at(1).as_string(), expected_branch[1]);
BOOST_REQUIRE_EQUAL(branch.at(2).as_string(), expected_branch[2]);
BOOST_REQUIRE_EQUAL(branch.at(3).as_string(), expected_branch[3]);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__proof_requested_exceeds_checkpoint__proves_last_returned)
{
BOOST_REQUIRE(handshake(electrum::version::v1_6));

using namespace test;
const auto expected_root = encode_hash(merkle_root(
{
block0_hash,
block1_hash,
block2_hash,
block3_hash,
block4_hash,
block5_hash,
block6_hash,
block7_hash,
block8_hash
}));

const string_list expected_branch
{
encode_hash(block5_hash),
encode_hash(root67),
encode_hash(root03),
encode_hash(root88)
};

const auto response = get(R"({"id":76,"method":"blockchain.block.headers","params":[0,20,8]})" "\n");
BOOST_REQUIRE_MESSAGE(response.is_object() && response.as_object().contains("result"), serialize(response));
REQUIRE_NO_THROW_TRUE(response.at("result").is_object());

const auto& result = response.at("result").as_object();
REQUIRE_NO_THROW_TRUE(result.at("max").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("count").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("headers").is_array());
REQUIRE_NO_THROW_TRUE(result.at("root").is_string());
BOOST_REQUIRE_EQUAL(result.at("max").as_int64(), 5);
BOOST_REQUIRE_EQUAL(result.at("count").as_int64(), 5);
BOOST_REQUIRE_EQUAL(result.at("headers").as_array().size(), 5u);
BOOST_REQUIRE_EQUAL(result.at("root").as_string(), expected_root);

const auto& branch = result.at("branch").as_array();
BOOST_REQUIRE(branch.at(0).is_string());
BOOST_REQUIRE_EQUAL(branch.size(), expected_branch.size());
BOOST_REQUIRE_EQUAL(branch.at(0).as_string(), expected_branch[0]);
BOOST_REQUIRE_EQUAL(branch.at(1).as_string(), expected_branch[1]);
BOOST_REQUIRE_EQUAL(branch.at(2).as_string(), expected_branch[2]);
BOOST_REQUIRE_EQUAL(branch.at(3).as_string(), expected_branch[3]);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__proof_count_exceeds_top__proves_last_returned)
{
BOOST_REQUIRE(handshake(electrum::version::v1_6));

using namespace test;
const auto expected_root = encode_hash(merkle_root(
{
block0_hash,
block1_hash,
block2_hash,
block3_hash,
block4_hash,
block5_hash,
block6_hash,
block7_hash,
block8_hash,
block9_hash
}));

const auto root89 = sha256::double_hash(block8_hash, block9_hash);
const string_list expected_branch
{
encode_hash(block8_hash),
encode_hash(root89),
encode_hash(sha256::double_hash(root89, root89)),
encode_hash(root07)
};

const auto response = get(R"({"id":77,"method":"blockchain.block.headers","params":[6,5,9]})" "\n");
BOOST_REQUIRE_MESSAGE(response.is_object() && response.as_object().contains("result"), serialize(response));
REQUIRE_NO_THROW_TRUE(response.at("result").is_object());

const auto& result = response.at("result").as_object();
REQUIRE_NO_THROW_TRUE(result.at("count").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("headers").is_array());
REQUIRE_NO_THROW_TRUE(result.at("root").is_string());
BOOST_REQUIRE_EQUAL(result.at("count").as_int64(), 4);
BOOST_REQUIRE_EQUAL(result.at("headers").as_array().size(), 4u);
BOOST_REQUIRE_EQUAL(result.at("root").as_string(), expected_root);

const auto& branch = result.at("branch").as_array();
BOOST_REQUIRE(branch.at(0).is_string());
BOOST_REQUIRE_EQUAL(branch.size(), expected_branch.size());
BOOST_REQUIRE_EQUAL(branch.at(0).as_string(), expected_branch[0]);
BOOST_REQUIRE_EQUAL(branch.at(1).as_string(), expected_branch[1]);
BOOST_REQUIRE_EQUAL(branch.at(2).as_string(), expected_branch[2]);
BOOST_REQUIRE_EQUAL(branch.at(3).as_string(), expected_branch[3]);
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__count_zero_checkpoint__no_proof)
{
BOOST_REQUIRE(handshake(electrum::version::v1_6));

const auto response = get(R"({"id":75,"method":"blockchain.block.headers","params":[5,0,8]})" "\n");
BOOST_REQUIRE_MESSAGE(response.is_object() && response.as_object().contains("result"), serialize(response));
REQUIRE_NO_THROW_TRUE(response.at("result").is_object());

const auto& result = response.at("result").as_object();
REQUIRE_NO_THROW_TRUE(result.at("count").is_int64());
REQUIRE_NO_THROW_TRUE(result.at("headers").is_array());
BOOST_REQUIRE_EQUAL(result.at("count").as_int64(), 0);
BOOST_REQUIRE(result.at("headers").as_array().empty());
BOOST_REQUIRE(!result.contains("root"));
BOOST_REQUIRE(!result.contains("branch"));
}

BOOST_AUTO_TEST_CASE(electrum__blockchain_block_headers__start_above_top__not_found)
{
BOOST_REQUIRE(handshake(electrum::version::v1_6));
Expand Down
Loading