Skip to content

Commit 0b2c93b

Browse files
committed
Merge bitcoin#28590: assumeutxo: change getchainstates RPC to return a list of chainstates
a9ef702 assumeutxo: change getchainstates RPC to return a list of chainstates (Ryan Ofsky) Pull request description: Current `getchainstates` RPC returns "normal" and "snapshot" fields which are not ideal because it requires new "normal" and "snapshot" terms to be defined, and the definitions are not really consistent with internal code. (In the RPC interface, the "snapshot" chainstate becomes the "normal" chainstate after it is validated, while in internal code there is no "normal chainstate" and the "snapshot chainstate" is still called that temporarily after it is validated). The current `getchainstates` RPC is also awkward to use if you to want information about the most-work chainstate, because you have to look at the "snapshot" field if it exists, and otherwise fall back to the "normal" field. Fix these issues by having `getchainstates` just return a flat list of chainstates ordered by work, and adding a new chainstate "validated" field alongside the existing "snapshot_blockhash" field so it is explicit if a chainstate was originally loaded from a snapshot, and whether the snapshot has been validated. This change was motivated by comment thread in bitcoin#28562 (comment) ACKs for top commit: Sjors: re-ACK a9ef702 jamesob: re-ACK a9ef702 achow101: ACK a9ef702 Tree-SHA512: b364e2e96675fb7beaaee60c4dff4b69e6bc2d8a30dea1ba094265633d1cddf9dbf1c5ce20c07d6e23222cf1e92a195acf6227e4901f3962e81a1e53a43490aa
2 parents 6e5cf8e + a9ef702 commit 0b2c93b

File tree

2 files changed

+32
-37
lines changed

2 files changed

+32
-37
lines changed

src/rpc/blockchain.cpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2807,6 +2807,7 @@ const std::vector<RPCResult> RPCHelpForChainstate{
28072807
{RPCResult::Type::STR_HEX, "snapshot_blockhash", /*optional=*/true, "the base block of the snapshot this chainstate is based on, if any"},
28082808
{RPCResult::Type::NUM, "coins_db_cache_bytes", "size of the coinsdb cache"},
28092809
{RPCResult::Type::NUM, "coins_tip_cache_bytes", "size of the coinstip cache"},
2810+
{RPCResult::Type::BOOL, "validated", "whether the chainstate is fully validated. True if all blocks in the chainstate were validated, false if the chain is based on a snapshot and the snapshot has not yet been validated."},
28102811
};
28112812

28122813
static RPCHelpMan getchainstates()
@@ -2818,8 +2819,7 @@ return RPCHelpMan{
28182819
RPCResult{
28192820
RPCResult::Type::OBJ, "", "", {
28202821
{RPCResult::Type::NUM, "headers", "the number of headers seen so far"},
2821-
{RPCResult::Type::OBJ, "normal", /*optional=*/true, "fully validated chainstate containing blocks this node has validated starting from the genesis block", RPCHelpForChainstate},
2822-
{RPCResult::Type::OBJ, "snapshot", /*optional=*/true, "only present if an assumeutxo snapshot is loaded. Partially validated chainstate containing blocks this node has validated starting from the snapshot. After the snapshot is validated (when the 'normal' chainstate advances far enough to validate it), this chainstate will replace and become the 'normal' chainstate.", RPCHelpForChainstate},
2822+
{RPCResult::Type::ARR, "chainstates", "list of the chainstates ordered by work, with the most-work (active) chainstate last", {{RPCResult::Type::OBJ, "", "", RPCHelpForChainstate},}},
28232823
}
28242824
},
28252825
RPCExamples{
@@ -2834,7 +2834,7 @@ return RPCHelpMan{
28342834
NodeContext& node = EnsureAnyNodeContext(request.context);
28352835
ChainstateManager& chainman = *node.chainman;
28362836

2837-
auto make_chain_data = [&](const Chainstate& cs) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
2837+
auto make_chain_data = [&](const Chainstate& cs, bool validated) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
28382838
AssertLockHeld(::cs_main);
28392839
UniValue data(UniValue::VOBJ);
28402840
if (!cs.m_chain.Tip()) {
@@ -2852,20 +2852,18 @@ return RPCHelpMan{
28522852
if (cs.m_from_snapshot_blockhash) {
28532853
data.pushKV("snapshot_blockhash", cs.m_from_snapshot_blockhash->ToString());
28542854
}
2855+
data.pushKV("validated", validated);
28552856
return data;
28562857
};
28572858

2858-
if (chainman.GetAll().size() > 1) {
2859-
for (Chainstate* chainstate : chainman.GetAll()) {
2860-
obj.pushKV(
2861-
chainstate->m_from_snapshot_blockhash ? "snapshot" : "normal",
2862-
make_chain_data(*chainstate));
2863-
}
2864-
} else {
2865-
obj.pushKV("normal", make_chain_data(chainman.ActiveChainstate()));
2866-
}
28672859
obj.pushKV("headers", chainman.m_best_header ? chainman.m_best_header->nHeight : -1);
28682860

2861+
const auto& chainstates = chainman.GetAll();
2862+
UniValue obj_chainstates{UniValue::VARR};
2863+
for (Chainstate* cs : chainstates) {
2864+
obj_chainstates.push_back(make_chain_data(*cs, !cs->m_from_snapshot_blockhash || chainstates.size() == 1));
2865+
}
2866+
obj.pushKV("chainstates", std::move(obj_chainstates));
28692867
return obj;
28702868
}
28712869
};

test/functional/feature_assumeutxo.py

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,13 @@ def no_sync():
128128
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
129129
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
130130

131-
monitor = n1.getchainstates()
132-
assert_equal(monitor['normal']['blocks'], START_HEIGHT)
133-
assert_equal(monitor['snapshot']['blocks'], SNAPSHOT_BASE_HEIGHT)
134-
assert_equal(monitor['snapshot']['snapshot_blockhash'], dump_output['base_hash'])
131+
normal, snapshot = n1.getchainstates()["chainstates"]
132+
assert_equal(normal['blocks'], START_HEIGHT)
133+
assert_equal(normal.get('snapshot_blockhash'), None)
134+
assert_equal(normal['validated'], True)
135+
assert_equal(snapshot['blocks'], SNAPSHOT_BASE_HEIGHT)
136+
assert_equal(snapshot['snapshot_blockhash'], dump_output['base_hash'])
137+
assert_equal(snapshot['validated'], False)
135138

136139
assert_equal(n1.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
137140

@@ -159,20 +162,11 @@ def no_sync():
159162
self.connect_nodes(0, 1)
160163

161164
self.log.info(f"Ensuring snapshot chain syncs to tip. ({FINAL_HEIGHT})")
162-
163-
def check_for_final_height():
164-
chainstates = n1.getchainstates()
165-
# The background validation may have completed before we run our first
166-
# check, so accept a final blockheight from either chainstate type.
167-
cs = chainstates.get('snapshot') or chainstates.get('normal')
168-
return cs['blocks'] == FINAL_HEIGHT
169-
170-
wait_until_helper(check_for_final_height)
165+
wait_until_helper(lambda: n1.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT)
171166
self.sync_blocks(nodes=(n0, n1))
172167

173168
self.log.info("Ensuring background validation completes")
174-
# N.B.: the `snapshot` key disappears once the background validation is complete.
175-
wait_until_helper(lambda: not n1.getchainstates().get('snapshot'))
169+
wait_until_helper(lambda: len(n1.getchainstates()['chainstates']) == 1)
176170

177171
# Ensure indexes have synced.
178172
completed_idx_state = {
@@ -189,8 +183,8 @@ def check_for_final_height():
189183

190184
assert_equal(n.getblockchaininfo()["blocks"], FINAL_HEIGHT)
191185

192-
assert_equal(n.getchainstates()['normal']['blocks'], FINAL_HEIGHT)
193-
assert_equal(n.getchainstates().get('snapshot'), None)
186+
chainstate, = n.getchainstates()['chainstates']
187+
assert_equal(chainstate['blocks'], FINAL_HEIGHT)
194188

195189
if i != 0:
196190
# Ensure indexes have synced for the assumeutxo node
@@ -208,17 +202,20 @@ def check_for_final_height():
208202
assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
209203
assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
210204

211-
monitor = n2.getchainstates()
212-
assert_equal(monitor['normal']['blocks'], START_HEIGHT)
213-
assert_equal(monitor['snapshot']['blocks'], SNAPSHOT_BASE_HEIGHT)
214-
assert_equal(monitor['snapshot']['snapshot_blockhash'], dump_output['base_hash'])
205+
normal, snapshot = n2.getchainstates()['chainstates']
206+
assert_equal(normal['blocks'], START_HEIGHT)
207+
assert_equal(normal.get('snapshot_blockhash'), None)
208+
assert_equal(normal['validated'], True)
209+
assert_equal(snapshot['blocks'], SNAPSHOT_BASE_HEIGHT)
210+
assert_equal(snapshot['snapshot_blockhash'], dump_output['base_hash'])
211+
assert_equal(snapshot['validated'], False)
215212

216213
self.connect_nodes(0, 2)
217-
wait_until_helper(lambda: n2.getchainstates()['snapshot']['blocks'] == FINAL_HEIGHT)
214+
wait_until_helper(lambda: n2.getchainstates()['chainstates'][-1]['blocks'] == FINAL_HEIGHT)
218215
self.sync_blocks()
219216

220217
self.log.info("Ensuring background validation completes")
221-
wait_until_helper(lambda: not n2.getchainstates().get('snapshot'))
218+
wait_until_helper(lambda: len(n2.getchainstates()['chainstates']) == 1)
222219

223220
completed_idx_state = {
224221
'basic block filter index': COMPLETE_IDX,
@@ -234,8 +231,8 @@ def check_for_final_height():
234231

235232
assert_equal(n.getblockchaininfo()["blocks"], FINAL_HEIGHT)
236233

237-
assert_equal(n.getchainstates()['normal']['blocks'], FINAL_HEIGHT)
238-
assert_equal(n.getchainstates().get('snapshot'), None)
234+
chainstate, = n.getchainstates()['chainstates']
235+
assert_equal(chainstate['blocks'], FINAL_HEIGHT)
239236

240237
if i != 0:
241238
# Ensure indexes have synced for the assumeutxo node

0 commit comments

Comments
 (0)