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
4 changes: 2 additions & 2 deletions include/bitcoin/server/interfaces/bitcoind_blockchain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ struct bitcoind_blockchain_methods
method<"getblockstats", value_t, optional<empty::array>>{ "hash_or_height", "stats" },
method<"getchaintxstats", optional<-1.0>, optional<""_t>>{ "nblocks", "blockhash" },
method<"gettxout", string_t, number_t, optional<true>>{ "txid", "n", "include_mempool" },
method<"gettxoutsetinfo">{ unimplemented },
method<"gettxoutsetinfo", optional<"none"_t>>{ "hash_type" },
method<"pruneblockchain", number_t>{ unimplemented, "height" },
method<"savemempool">{ unimplemented },
method<"scantxoutset", string_t, optional<empty::array>>{ unimplemented, "action", "scanobjects" },
method<"scantxoutset", string_t, optional<empty::array>>{ "action", "scanobjects" },
method<"verifychain", optional<4.0>, optional<288.0>>{ "checklevel", "nblocks" },
method<"dumptxoutset">{ unimplemented },
method<"loadtxoutset">{ unimplemented },
Expand Down
3 changes: 1 addition & 2 deletions include/bitcoin/server/parsers/block_stats.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ namespace server {

/// All bitcoind getblockstats statistics of a prevout-populated block.
BCS_API network::rpc::object_t block_stats(const system::chain::block& block,
size_t height, uint32_t median_time_past, uint64_t subsidy,
bool repeat) NOEXCEPT;
size_t height, uint32_t median_time_past, uint64_t subsidy) NOEXCEPT;

} // namespace server
} // namespace libbitcoin
Expand Down
8 changes: 8 additions & 0 deletions include/bitcoin/server/protocols/protocol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,12 @@ class BCS_API protocol
// For use with secondary (e.g. notification) strands.
#define POST_NOTIFY(method, ...) notify<CLASS>(&CLASS::method, __VA_ARGS__)

// Qualified, as http protocols alias post as the request type.
#undef POST
#undef PARALLEL
#define POST(method, ...) \
network::protocol::post<CLASS>(&CLASS::method __VA_OPT__(,) __VA_ARGS__)
#define PARALLEL(method, ...) \
network::protocol::parallel<CLASS>(&CLASS::method __VA_OPT__(,) __VA_ARGS__)

#endif
8 changes: 8 additions & 0 deletions include/bitcoin/server/protocols/protocol_bitcoind.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ class BCS_API protocol_bitcoind
const network::rpc::array_t& keys,
const std::string& address_type) const NOEXCEPT;

/// The address of a singular output script (empty if unaddressable).
std::string to_address(
const system::chain::script& script) const NOEXCEPT;

/// Inferred where a pattern is expressible, otherwise raw.
std::string infer_descriptor(
const system::chain::script& script) const NOEXCEPT;

/// Senders. close_reason (if truthy) stops the channel only once the
/// write has completed, so the error reaches the client first.
void send_error(const code& ec) NOEXCEPT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class BCS_API protocol_bitcoind_blockchain
bool handle_get_tx_out(const code& ec,
rpc_interface::get_tx_out, const std::string&, double, bool) NOEXCEPT;
bool handle_get_tx_out_set_info(const code& ec,
rpc_interface::get_tx_out_set_info) NOEXCEPT;
rpc_interface::get_tx_out_set_info, const std::string&) NOEXCEPT;
bool handle_prune_block_chain(const code& ec,
rpc_interface::prune_block_chain, double) NOEXCEPT;
bool handle_save_mempool(const code& ec,
Expand Down Expand Up @@ -162,6 +162,15 @@ class BCS_API protocol_bitcoind_blockchain
bool wait_done() const NOEXCEPT;
void send_tip() NOEXCEPT;

void do_get_tx_out_set_info() NOEXCEPT;
void do_scan_tx_out_set(
const std::shared_ptr<system::chain::scripts>& scripts) NOEXCEPT;
void complete_scan(const code& ec, network::rpc::object_t& result,
size_t size) NOEXCEPT;

// This is thread safe.
std::atomic_bool stopping_{};

// These are protected by strand.
wait wait_{ wait::none };
size_t wait_height_{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ class BCS_API protocol_bitcoind_utility
rpc_interface::get_index_info, const std::string& index_name) NOEXCEPT;
bool handle_estimate_smart_fee(const code& ec,
rpc_interface::estimate_smart_fee) NOEXCEPT;

/// The address of a singular output script (empty if unaddressable).
std::string to_address(
const system::chain::script& script) const NOEXCEPT;
};

} // namespace server
Expand Down
9 changes: 4 additions & 5 deletions src/parsers/block_stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ static std::array<uint64_t, 5> fee_rate_percentiles(
}

object_t block_stats(const chain::block& block, size_t height,
uint32_t median_time_past, uint64_t subsidy, bool repeat) NOEXCEPT
uint32_t median_time_past, uint64_t subsidy) NOEXCEPT
{
const auto& txs = *block.transactions_ptr();

Expand All @@ -95,10 +95,9 @@ object_t block_stats(const chain::block& block, size_t height,
{
tx_total_output += out->value();

// Genesis and repeated coinbases do not add to the utxo set,
// and unspendable outputs are not included in it.
if (is_zero(height) || (repeat && tx->is_coinbase()) ||
out->script().is_unspendable())
// The genesis coinbase does not add to the utxo set, and
// unspendable outputs are not included in it.
if (is_zero(height) || out->script().is_unspendable())
continue;

++utxos;
Expand Down
210 changes: 193 additions & 17 deletions src/protocols/bitcoind/protocol_bitcoind_blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void protocol_bitcoind_blockchain::start() NOEXCEPT
SUBSCRIBE_BITCOIND(handle_get_block_stats, _1, _2, _3, _4);
SUBSCRIBE_BITCOIND(handle_get_chain_tx_stats, _1, _2, _3, _4);
SUBSCRIBE_BITCOIND(handle_get_tx_out, _1, _2, _3, _4, _5);
SUBSCRIBE_BITCOIND(handle_get_tx_out_set_info, _1, _2);
SUBSCRIBE_BITCOIND(handle_get_tx_out_set_info, _1, _2, _3);
SUBSCRIBE_BITCOIND(handle_prune_block_chain, _1, _2, _3);
SUBSCRIBE_BITCOIND(handle_save_mempool, _1, _2);
SUBSCRIBE_BITCOIND(handle_scan_tx_out_set, _1, _2, _3, _4);
Expand Down Expand Up @@ -123,6 +123,7 @@ void protocol_bitcoind_blockchain::start() NOEXCEPT
void protocol_bitcoind_blockchain::stopping(const code& ec) NOEXCEPT
{
BC_ASSERT(stranded());
stopping_.store(true);
unsubscribe_chase();
wait_timer_->stop();
protocol_bitcoind_dispatch<rpc_interface>::stopping(ec);
Expand Down Expand Up @@ -399,13 +400,8 @@ bool protocol_bitcoind_blockchain::handle_get_block_stats(const code& ec,
settings.subsidy_interval_blocks, settings.initial_subsidy(),
settings.forks.bip42);

// The duplicated-coinbase blocks (bip30 exceptions) do not add to the
// utxo set.
const auto repeat = chain::chain_state::is_bip30_exception(block->hash(),
height);

auto result = block_stats(*block, height, median_time_past(query, link),
subsidy, repeat);
subsidy);

// An empty selection returns all statistics, otherwise the named subset.
if (stats.empty())
Expand Down Expand Up @@ -549,14 +545,14 @@ bool protocol_bitcoind_blockchain::handle_get_tx_out(const code& ec,
// (is_spent would also count unconfirmed/conflicting/invalid-block spenders).
if (output_link.is_terminal() || query.is_confirmed_spent(output_link))
{
send_result({}, 42);
send_result(null_t{}, 42);
return true;
}

const auto output = query.get_output(output_link);
if (!output)
{
send_result({}, 42);
send_result(null_t{}, 42);
return true;
}

Expand Down Expand Up @@ -589,14 +585,78 @@ bool protocol_bitcoind_blockchain::handle_get_tx_out(const code& ec,
return true;
}

// The response defers to completion of the store scan (see dispatch). This is
// an administrative query, expected to run long, performed off the strand.
bool protocol_bitcoind_blockchain::handle_get_tx_out_set_info(const code& ec,
rpc_interface::get_tx_out_set_info) NOEXCEPT
rpc_interface::get_tx_out_set_info, const std::string& hash_type) NOEXCEPT
{
if (stopped(ec)) return false;
send_error(error::not_implemented);
if (stopped(ec))
return false;

// Utxo set commitments have no consumer here (no assumeutxo).
if (hash_type != "none")
{
send_error(error::invalid_argument);
return true;
}

monitor(true);
PARALLEL(do_get_tx_out_set_info);
return true;
}

void protocol_bitcoind_blockchain::do_get_tx_out_set_info() NOEXCEPT
{
BC_ASSERT(!stranded());

object_t result{};
const auto& query = archive();
const auto top = query.get_top_confirmed();
const auto link = query.to_confirmed(top);

// The pinned ancestry excludes genesis, absent from the set (as bitcoind).
database::header_links branch{};
if (!query.get_ancestry(branch, link, top))
{
POST(complete_scan, database::error::integrity, std::move(result),
zero);
return;
}

database::unspent_totals totals{};
const auto ec = query.get_unspent_totals(stopping_, totals, branch,
database_settings().turbo);
if (ec)
{
POST(complete_scan, ec,
std::move(result), zero);
return;
}

// A reorganization across the pinned top voids the scan.
if (!query.is_confirmed_block(link))
{
POST(complete_scan, error::server_error, std::move(result), zero);
return;
}

result = object_t
{
{ "height", top },
{ "bestblock", encode_hash(query.get_header_key(link)) },
{ "transactions", totals.transactions },
{ "txouts", totals.outputs },

// bitcoind's per-utxo accounting fiction (50 byte overhead).
{ "bogosize", 50u * totals.outputs + totals.script_bytes },
{ "total_amount", to_floating(totals.value) /
chain::satoshi_per_bitcoin }
};

POST(complete_scan, code{},
std::move(result), 512);
}

bool protocol_bitcoind_blockchain::handle_prune_block_chain(const code& ec,
rpc_interface::prune_block_chain, double) NOEXCEPT
{
Expand All @@ -613,15 +673,131 @@ bool protocol_bitcoind_blockchain::handle_save_mempool(const code& ec,
return true;
}

// The response defers to completion of the indexed scan (see dispatch).
bool protocol_bitcoind_blockchain::handle_scan_tx_out_set(const code& ec,
rpc_interface::scan_tx_out_set, const std::string&,
const array_t&) NOEXCEPT
rpc_interface::scan_tx_out_set, const std::string& action,
const array_t& scanobjects) NOEXCEPT
{
if (stopped(ec)) return false;
send_error(error::not_implemented);
if (stopped(ec))
return false;

// Each scan completes with its response, so there is never one to report.
if (action == "status")
{
send_result(null_t{}, 8);
return true;
}

if (action == "abort")
{
send_result(value{ false }, 8);
return true;
}

if (action != "start" || scanobjects.empty())
{
send_error(error::invalid_argument);
return true;
}

if (!archive().address_enabled())
{
send_error(error::not_implemented);
return true;
}

const auto scripts = std::make_shared<chain::scripts>();
for (const auto& item: scanobjects)
{
if (!expand_scan_object(*scripts, item))
{
send_error(error::invalid_argument);
return true;
}
}

monitor(true);
PARALLEL(do_scan_tx_out_set, scripts);
return true;
}

void protocol_bitcoind_blockchain::do_scan_tx_out_set(
const std::shared_ptr<chain::scripts>& scripts) NOEXCEPT
{
BC_ASSERT(!stranded());

uint64_t amount{};
array_t unspents{};
object_t result{};
const auto& query = archive();
const auto top = query.get_top_confirmed();

for (const auto& script: *scripts)
{
database::unspents outs{};
const auto data = script.to_data(false);
auto ec = query.get_confirmed_unspent(stopping_, outs,
sha256_hash(data));
if (ec)
{
POST(complete_scan, ec,
std::move(result), zero);
return;
}

const auto hex = encode_base16(data);
const auto desc = infer_descriptor(script);
for (const auto& utxo: outs)
{
const auto& target = utxo.out.point();
unspents.emplace_back(object_t
{
{ "txid", encode_hash(target.hash()) },
{ "vout", target.index() },
{ "scriptPubKey", hex },
{ "desc", desc },
{ "amount", to_floating(utxo.out.value()) /
chain::satoshi_per_bitcoin },
{ "coinbase", query.is_coinbase(query.to_tx(target.hash())) },
{ "height", utxo.height },
{ "blockhash", encode_hash(query.get_header_key(
query.to_confirmed(utxo.height))) },
{ "confirmations", add1(floored_subtract(top, utxo.height)) }
});

amount += utxo.out.value();
}
}

const auto size = add1(unspents.size()) * 384u;
result = object_t
{
{ "success", true },
{ "height", top },
{ "bestblock", encode_hash(query.get_header_key(
query.to_confirmed(top))) },
{ "unspents", std::move(unspents) },
{ "total_amount", to_floating(amount) / chain::satoshi_per_bitcoin }
};

POST(complete_scan, code{},
std::move(result), size);
}

void protocol_bitcoind_blockchain::complete_scan(const code& ec,
object_t& result, size_t size) NOEXCEPT
{
BC_ASSERT(stranded());
monitor(false);
if (stopped())
return;

if (ec)
send_error(ec);
else
send_result(std::move(result), size);
}

bool protocol_bitcoind_blockchain::handle_verify_chain(const code& ec,
rpc_interface::verify_chain, double, double) NOEXCEPT
{
Expand Down Expand Up @@ -1422,7 +1598,7 @@ bool protocol_bitcoind_blockchain::handle_chase(const code&,
case node::chase::organized:
case node::chase::reorganized:
{
network::protocol::post<CLASS>(&CLASS::do_wait_event);
POST(do_wait_event);
break;
}
default:
Expand Down
Loading
Loading