Skip to content

Commit

Permalink
rpc: Block until synced if coinstatsindex is used in gettxoutsetinfo
Browse files Browse the repository at this point in the history
During initial sync after startup the gettxoutsetinfo RPC will still return an error while catching up. However, after the initial sync the index will not error immediately anymore when it's in the process of syncing to the tip while being called. Instead it will block until synced and then return the response.

Github-Pull: bitcoin#22047
Rebased-From: d4356d4
  • Loading branch information
fjahr authored and luke-jr committed Aug 29, 2021
1 parent 9a4bf4d commit 19a40fd
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 20 deletions.
19 changes: 12 additions & 7 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,18 @@ static RPCHelpMan gettxoutsetinfo()
pindex = ParseHashOrHeight(request.params[1], chainman);
}

if (stats.index_requested && g_coin_stats_index) {
if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
const IndexSummary summary{g_coin_stats_index->GetSummary()};

// If a specific block was requested and the index has already synced past that height, we can return the
// data already even though the index is not fully synced yet.
if (pindex->nHeight > summary.best_block_height) {
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
}
}
}

if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) {
ret.pushKV("height", (int64_t)stats.nHeight);
ret.pushKV("bestblock", stats.hashBlock.GetHex());
Expand Down Expand Up @@ -1218,13 +1230,6 @@ static RPCHelpMan gettxoutsetinfo()
ret.pushKV("block_info", block_info);
}
} else {
if (g_coin_stats_index) {
const IndexSummary summary{g_coin_stats_index->GetSummary()};

if (!summary.synced) {
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to read UTXO set because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
}
}
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
return ret;
Expand Down
14 changes: 1 addition & 13 deletions test/functional/feature_coinstatsindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
try_rpc,
)

class CoinStatsIndexTest(BitcoinTestFramework):
Expand Down Expand Up @@ -76,13 +75,11 @@ def _test_coin_stats_index(self):
self.sync_blocks(timeout=120)

self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo))
res0 = node.gettxoutsetinfo('none')

# The fields 'disk_size' and 'transactions' do not exist on the index
del res0['disk_size'], res0['transactions']

self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
res1 = index_node.gettxoutsetinfo(hash_option)
# The fields 'block_info' and 'total_unspendable_amount' only exist on the index
Expand All @@ -97,7 +94,6 @@ def _test_coin_stats_index(self):
# Generate a new tip
node.generate(5)

self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
# Fetch old stats by height
res2 = index_node.gettxoutsetinfo(hash_option, 102)
Expand Down Expand Up @@ -176,7 +172,6 @@ def _test_coin_stats_index(self):
self.nodes[0].generate(1)
self.sync_all()

self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
# Check all amounts were registered correctly
res6 = index_node.gettxoutsetinfo(hash_option, 108)
Expand Down Expand Up @@ -209,7 +204,6 @@ def _test_coin_stats_index(self):
self.nodes[0].submitblock(block.serialize().hex())
self.sync_all()

self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
res7 = index_node.gettxoutsetinfo(hash_option, 109)
assert_equal(res7['total_unspendable_amount'], Decimal('80.98999999'))
Expand All @@ -235,7 +229,6 @@ def _test_coin_stats_index(self):
assert_equal(res8, res9)

index_node.generate(1)
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res10 = index_node.gettxoutsetinfo('muhash')
assert(res8['txouts'] < res10['txouts'])

Expand All @@ -256,14 +249,12 @@ def _test_reorg_index(self):
index_node = self.nodes[1]
reorg_blocks = index_node.generatetoaddress(2, index_node.getnewaddress())
reorg_block = reorg_blocks[1]
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res_invalid = index_node.gettxoutsetinfo('muhash')
index_node.invalidateblock(reorg_blocks[0])
assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110)

# Add two new blocks
block = index_node.generate(2)[1]
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)

# Test that the result of the reorged block is not returned for its old block height
Expand All @@ -285,9 +276,7 @@ def _test_reorg_index(self):
# Ensure that removing and re-adding blocks yields consistent results
block = index_node.getblockhash(99)
index_node.invalidateblock(block)
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
index_node.reconsiderblock(block)
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res3 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
assert_equal(res2, res3)

Expand All @@ -297,8 +286,7 @@ def _test_reorg_index(self):
node.getblock(reorg_block)

self.restart_node(0, ["-coinstatsindex"])
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash'))
assert_raises_rpc_error(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash', reorg_block)
assert_raises_rpc_error(-32603, "Unable to get data because coinstatsindex is still syncing.", node.gettxoutsetinfo, 'muhash', reorg_block)

def _test_index_rejects_hash_serialized(self):
self.log.info("Test that the rpc raises if the legacy hash is passed with the index")
Expand Down

0 comments on commit 19a40fd

Please sign in to comment.