Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC server: thread safety (+ small fix to on_getblockhash) #7936

Closed
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
50 changes: 36 additions & 14 deletions src/rpc/core_rpc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ namespace cryptonote
}

CHECK_PAYMENT_MIN1(req, res, COST_PER_GET_INFO, false);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());

const bool restricted = m_restricted && ctx;

Expand Down Expand Up @@ -549,6 +550,7 @@ namespace cryptonote
}

CHECK_PAYMENT(req, res, 1);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());

// quick check for noop
if (!req.block_ids.empty())
Expand All @@ -559,7 +561,7 @@ namespace cryptonote
if (last_block_hash == req.block_ids.front())
{
res.start_height = 0;
res.current_height = m_core.get_current_blockchain_height();
res.current_height = last_block_height + 1;
res.status = CORE_RPC_STATUS_OK;
return true;
}
Expand Down Expand Up @@ -680,6 +682,7 @@ namespace cryptonote
res.blocks.clear();
res.blocks.reserve(req.heights.size());
CHECK_PAYMENT_MIN1(req, res, req.heights.size() * COST_PER_BLOCK, false);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
for (uint64_t height : req.heights)
{
block blk;
Expand Down Expand Up @@ -1526,6 +1529,7 @@ namespace cryptonote
return r;

CHECK_PAYMENT(req, res, 1);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());

const bool restricted = m_restricted && ctx;
const bool request_has_rpc_origin = ctx != NULL;
Expand All @@ -1550,6 +1554,7 @@ namespace cryptonote
return r;

CHECK_PAYMENT(req, res, 1);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());

const bool restricted = m_restricted && ctx;
const bool request_has_rpc_origin = ctx != NULL;
Expand Down Expand Up @@ -1652,11 +1657,14 @@ namespace cryptonote
error_resp.message = "Wrong parameters, expected height";
return false;
}
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
uint64_t h = req[0];
if(m_core.get_current_blockchain_height() <= h)
uint64_t blockchain_height = m_core.get_current_blockchain_height();
if(blockchain_height <= h)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1);
error_resp.message = std::string("Requested block height: ") + std::to_string(h) + " greater than current top block height: " + std::to_string(blockchain_height - 1);
return false;
}
res = string_tools::pod_to_hex(m_core.get_block_id_by_height(h));
return true;
Expand Down Expand Up @@ -1806,6 +1814,7 @@ namespace cryptonote
return false;
}
}
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
uint64_t seed_height;
crypto::hash seed_hash, next_seed_hash;
if (!get_block_template(info.address, req.prev_block.empty() ? NULL : &prev_block, blob_reserve, reserved_offset, wdiff, res.height, res.expected_reward, b, res.seed_height, seed_hash, next_seed_hash, error_resp))
Expand Down Expand Up @@ -2086,6 +2095,7 @@ namespace cryptonote

CHECK_CORE_READY();
CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
uint64_t last_block_height;
crypto::hash last_block_hash;
m_core.get_blockchain_top(last_block_height, last_block_hash);
Expand Down Expand Up @@ -2126,6 +2136,8 @@ namespace cryptonote
return false;
}

db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());

auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool {
crypto::hash block_hash;
bool hash_parsed = parse_hash256(hash, block_hash);
Expand Down Expand Up @@ -2185,13 +2197,6 @@ namespace cryptonote
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADERS_RANGE>(invoke_http_mode::JON_RPC, "getblockheadersrange", req, res, r))
return r;

const uint64_t bc_height = m_core.get_current_blockchain_height();
if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = "Invalid start/end heights.";
return false;
}
const bool restricted = m_restricted && ctx;
if (restricted && req.end_height - req.start_height > RESTRICTED_BLOCK_HEADER_RANGE)
{
Expand All @@ -2201,6 +2206,16 @@ namespace cryptonote
}

CHECK_PAYMENT_MIN1(req, res, (req.end_height - req.start_height + 1) * COST_PER_BLOCK_HEADER, false);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());

const uint64_t bc_height = m_core.get_current_blockchain_height();
if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = "Invalid start/end heights.";
return false;
}

for (uint64_t h = req.start_height; h <= req.end_height; ++h)
{
crypto::hash block_hash = m_core.get_block_id_by_height(h);
Expand Down Expand Up @@ -2245,10 +2260,12 @@ namespace cryptonote
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT>(invoke_http_mode::JON_RPC, "getblockheaderbyheight", req, res, r))
return r;

if(m_core.get_current_blockchain_height() <= req.height)
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
uint64_t blockchain_height = m_core.get_current_blockchain_height();
if(blockchain_height <= req.height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1);
error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1);
return false;
}
CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false);
Expand Down Expand Up @@ -2281,6 +2298,7 @@ namespace cryptonote
return r;

CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK, false);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());

crypto::hash block_hash;
if (!req.hash.empty())
Expand All @@ -2295,10 +2313,11 @@ namespace cryptonote
}
else
{
if(m_core.get_current_blockchain_height() <= req.height)
uint64_t blockchain_height = m_core.get_current_blockchain_height();
if(blockchain_height <= req.height)
{
error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT;
error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1);
error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(blockchain_height - 1);
return false;
}
block_hash = m_core.get_block_id_by_height(req.height);
Expand Down Expand Up @@ -2606,6 +2625,7 @@ namespace cryptonote
bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
RPC_TRACKER(get_coinbase_tx_sum);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could get the blockchain db locked for an awfully long time, which might hold up all other things quite a bit, but at least the sum would be correct to the piconero :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's using a read-only LMDB transaction to ensure consistent reads of the db, which doesn't lock out writers. From the LMDB docs:

as long as a transaction is open, a consistent view of the database is kept alive, which requires storage. A read-only transaction that no longer requires this consistent view should be terminated (committed or aborted) when the view is no longer needed (but see below for an optimization).

There can be multiple simultaneously active read-only transactions but only one that can write. Once a single read-write transaction is opened, all further attempts to begin one will block until the first one is committed or aborted. This has no effect on read-only transactions, however, and they may continue to be opened at any time.

I also tested behavior in this PR generally by using usleep + a local test env so I can mine blocks quickly. Can test behavior like this for example:

bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
  {
    RPC_TRACKER(get_coinbase_tx_sum);
    db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
    const uint64_t bc_height = m_core.get_current_blockchain_height();

    printf("Height before sleep: %lu\n", bc_height);
    // sleep, then mine some blocks while asleep, then make sure height still reads the same after waking up
    usleep(1000 * 1000 * 30);

    // can even call status (or other RPC calls) from the daemon while asleep and see *those* would return latest height before this next line prints
    printf("Height after sleep: %lu\n", m_core.get_current_blockchain_height());

I could even make an RPC call to on_get_coinbase_sum again and grab another read txn guard and the daemon proceeds to fall asleep, while the first on_get_coinbase_sum was still asleep.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. So experience from other databases does not carry over 1-to-1 to LMDB, as it can do more in parallel as databases can usually do.

And yeah, your test here really settles the issue.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, LMDB is rather unique in its capabilities.

const uint64_t bc_height = m_core.get_current_blockchain_height();
if (req.height >= bc_height || req.count > bc_height)
{
Expand Down Expand Up @@ -2637,6 +2657,7 @@ namespace cryptonote
bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx)
{
RPC_TRACKER(get_alternate_chains);
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
try
{
std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains();
Expand Down Expand Up @@ -2937,6 +2958,7 @@ namespace cryptonote
bool r;
if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG>(invoke_http_mode::JON_RPC, "get_txpool_backlog", req, res, r))
return r;
db_rtxn_guard rtxn_guard(&m_core.get_blockchain_storage().get_db());
size_t n_txes = m_core.get_pool_transactions_count();
CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS * n_txes, false);

Expand Down