Skip to content
Open
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
6 changes: 5 additions & 1 deletion include/bitcoin/server/protocols/protocol_bitcoind_rpc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,12 @@ class BCS_API protocol_bitcoind_rpc
const database::header_link& link) NOEXCEPT;
static bool parse_verbosity(double& verbosity,
const network::rpc::value_t& value, double missing) NOEXCEPT;
static std::string to_chain_work(const node::query& query,
const system::settings& settings,
const system::hash_digest& hash) NOEXCEPT;
static void inject_block_context(boost::json::object& out,
const node::query& query, const database::header_link& link,
const node::query& query, const system::settings& settings,
const database::header_link& link,
const system::chain::header& header) NOEXCEPT;
static void inject_tx_context(boost::json::object& out,
const node::query& query, const database::tx_link& link) NOEXCEPT;
Expand Down
15 changes: 13 additions & 2 deletions src/protocols/bitcoind/protocol_bitcoind_rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ bool protocol_bitcoind_rpc::handle_get_block(const code& ec,
value_from(bitcoind_hashed(*block)) :
value_from(bitcoind_verbose(*block));

inject_block_context(model.as_object(), query, link, block->header());
inject_block_context(model.as_object(), query, system_settings(),
link, block->header());
send_result(std::move(model), two * block->serialized_size(witness));
return true;
}
Expand Down Expand Up @@ -277,6 +278,14 @@ bool protocol_bitcoind_rpc::handle_get_block_chain_info(const code& ec,

// TODO: blocks/headers is a misnomer (off-by-one), intended?
using namespace chain;
const auto chain_work = to_chain_work(query, system_settings(),
query.get_header_key(link));
if (chain_work.empty())
{
send_error(database::error::integrity);
return true;
}

send_result(object_t
{
{ "chain", chain_name(query) },
Expand All @@ -290,6 +299,8 @@ bool protocol_bitcoind_rpc::handle_get_block_chain_info(const code& ec,
{ "mediantime", median_time_past(query, link) },
{ "verificationprogress", progress },
{ "initialblockdownload", !is_current_chain(true) },
{ "chainwork", chain_work },
{ "size_on_disk", query.archive_size() },
{ "pruned", false },
{ "warnings", std::string{} }
}, 512);
Expand Down Expand Up @@ -402,7 +413,7 @@ bool protocol_bitcoind_rpc::handle_get_block_header(const code& ec,

auto out = header_to_bitcoind(*header);
out["nTx"] = query.get_tx_count(link);
inject_block_context(out, query, link, *header);
inject_block_context(out, query, system_settings(), link, *header);
send_result(value{ std::move(out) }, 512);
return true;
}
Expand Down
43 changes: 41 additions & 2 deletions src/protocols/bitcoind/protocol_bitcoind_rpc_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
#include <bitcoin/server/protocols/protocol_bitcoind_rpc.hpp>

#include <memory>
#include <mutex>
#include <utility>
#include <variant>
#include <vector>
#include <bitcoin/server/define.hpp>
Expand Down Expand Up @@ -55,9 +58,41 @@ bool protocol_bitcoind_rpc::parse_verbosity(double& verbosity,
return true;
}

// Sum of work from genesis to the identified header, as bitcoind encodes
// it (chain_state spans the chain to accumulate, as with organization).
// Work to a given header is immutable, so a single-entry cache requires no
// invalidation and absorbs the dominant access pattern (repeated queries
// at the tip). Distinct historical queries still pay the span computation.
std::string protocol_bitcoind_rpc::to_chain_work(const node::query& query,
const system::settings& settings, const hash_digest& hash) NOEXCEPT
{
using cache_t = std::pair<hash_digest, std::string>;
static std::mutex mutex{};
static std::shared_ptr<const cache_t> cache{};

// The span computation runs outside the lock; only access is guarded.
{
const std::lock_guard<std::mutex> lock{ mutex };
if (cache && cache->first == hash)
return cache->second;
}

const auto state = query.get_chain_state(settings, hash);
if (!state)
return {};

const auto work = encode_hash(from_uintx(state->cumulative_work()));
{
const std::lock_guard<std::mutex> lock{ mutex };
cache = std::make_shared<const cache_t>(hash, work);
}

return work;
}

void protocol_bitcoind_rpc::inject_block_context(boost::json::object& out,
const node::query& query, const database::header_link& link,
const chain::header& header) NOEXCEPT
const node::query& query, const system::settings& settings,
const database::header_link& link, const chain::header& header) NOEXCEPT
{
size_t height{};
if (!query.get_height(height, link))
Expand All @@ -69,6 +104,10 @@ void protocol_bitcoind_rpc::inject_block_context(boost::json::object& out,
out["confirmations"] = add1(floored_subtract(top, height));
out["mediantime"] = median_time_past(query, link);

const auto chain_work = to_chain_work(query, settings, header.hash());
if (!chain_work.empty())
out["chainwork"] = chain_work;

if (header.previous_block_hash() != null_hash)
out["previousblockhash"] = encode_hash(header.previous_block_hash());

Expand Down
40 changes: 37 additions & 3 deletions test/protocols/bitcoind/bitcoind_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,21 @@ struct json
{
using protocol_bitcoind_rpc::median_time_past;
using protocol_bitcoind_rpc::parse_verbosity;
using protocol_bitcoind_rpc::to_chain_work;
using protocol_bitcoind_rpc::inject_block_context;
using protocol_bitcoind_rpc::inject_tx_context;
using protocol_bitcoind_rpc::header_to_bitcoind;
using protocol_bitcoind_rpc::chain_name;
};

// Cumulative chain work to genesis/tip, encoded as bitcoind serializes it.
constexpr auto genesis_chain_work =
"0000000000000000000000000000000000000000000000000000000100010001";
constexpr auto block5_chain_work =
"0000000000000000000000000000000000000000000000000000000600060006";
constexpr auto tip_chain_work =
"0000000000000000000000000000000000000000000000000000000a000a000a";

// header_to_bitcoind
// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -179,13 +188,14 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__middle__height_confirm
BOOST_REQUIRE(header);

boost::json::object out{};
json::inject_block_context(out, query_, link, *header);
json::inject_block_context(out, query_, config_.bitcoin, link, *header);

chain::context ctx{};
BOOST_REQUIRE(query_.get_context(ctx, link));
BOOST_REQUIRE_EQUAL(out.at("height").to_number<uint64_t>(), 5u);
BOOST_REQUIRE_EQUAL(out.at("confirmations").to_number<int64_t>(), 5);
BOOST_REQUIRE_EQUAL(out.at("mediantime").to_number<uint32_t>(), ctx.median_time_past);
BOOST_REQUIRE_EQUAL(as_text(out.at("chainwork")), block5_chain_work);
BOOST_REQUIRE_EQUAL(as_text(out.at("previousblockhash")), encode_hash(test::block4_hash));
BOOST_REQUIRE_EQUAL(as_text(out.at("nextblockhash")), encode_hash(test::block6_hash));
}
Expand All @@ -197,10 +207,11 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__genesis__no_previous)
BOOST_REQUIRE(header);

boost::json::object out{};
json::inject_block_context(out, query_, link, *header);
json::inject_block_context(out, query_, config_.bitcoin, link, *header);

BOOST_REQUIRE_EQUAL(out.at("height").to_number<uint64_t>(), 0u);
BOOST_REQUIRE_EQUAL(out.at("confirmations").to_number<int64_t>(), 10);
BOOST_REQUIRE_EQUAL(as_text(out.at("chainwork")), genesis_chain_work);
BOOST_REQUIRE(!out.contains("previousblockhash"));
BOOST_REQUIRE_EQUAL(as_text(out.at("nextblockhash")), encode_hash(test::block1_hash));
}
Expand All @@ -212,14 +223,37 @@ BOOST_AUTO_TEST_CASE(bitcoind_json__inject_block_context__tip__no_next)
BOOST_REQUIRE(header);

boost::json::object out{};
json::inject_block_context(out, query_, link, *header);
json::inject_block_context(out, query_, config_.bitcoin, link, *header);

BOOST_REQUIRE_EQUAL(out.at("height").to_number<uint64_t>(), 9u);
BOOST_REQUIRE_EQUAL(out.at("confirmations").to_number<int64_t>(), 1);
BOOST_REQUIRE_EQUAL(as_text(out.at("chainwork")), tip_chain_work);
BOOST_REQUIRE_EQUAL(as_text(out.at("previousblockhash")), encode_hash(test::block8_hash));
BOOST_REQUIRE(!out.contains("nextblockhash"));
}

// to_chain_work

BOOST_AUTO_TEST_CASE(bitcoind_json__to_chain_work__genesis__genesis_proof)
{
const auto work = json::to_chain_work(query_, config_.bitcoin, test::block0_hash);
BOOST_REQUIRE_EQUAL(work, genesis_chain_work);
}

BOOST_AUTO_TEST_CASE(bitcoind_json__to_chain_work__unknown_hash__empty)
{
const auto work = json::to_chain_work(query_, config_.bitcoin, system::null_hash);
BOOST_REQUIRE(work.empty());
}

BOOST_AUTO_TEST_CASE(bitcoind_json__to_chain_work__repeated__cached_value_consistent)
{
const auto first = json::to_chain_work(query_, config_.bitcoin, test::block9_hash);
const auto second = json::to_chain_work(query_, config_.bitcoin, test::block9_hash);
BOOST_REQUIRE_EQUAL(first, tip_chain_work);
BOOST_REQUIRE_EQUAL(first, second);
}

// inject_tx_context

BOOST_AUTO_TEST_CASE(bitcoind_json__inject_tx_context__confirmed_coinbase__block_context)
Expand Down
Loading