Skip to content

Commit d0f71c5

Browse files
shesekphilippem
authored andcommitted
Fix lookup_confirmations() to account for stale entries
Also adds HeaderList::best_height() to help avoid off-by-one errors for the chain length vs tip height (like I initially made when implementing this >.<), and to make getting the tip height of an empty HeaderList an explicit error (previously it over overflow and return usize::MAX).
1 parent 485e039 commit d0f71c5

3 files changed

Lines changed: 24 additions & 8 deletions

File tree

src/bin/db-migrate-v1-to-v2.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ fn main() {
3838
let history_db = store.history_db();
3939
let cache_db = store.cache_db();
4040
let headers = store.headers();
41+
let tip_height = headers.best_height() as u32;
4142

4243
// Check the DB version under `V` matches the expected version
4344
for db in [txstore_db, history_db, cache_db] {
@@ -133,7 +134,7 @@ fn main() {
133134
}
134135

135136
// Lookup the confirmation status for the entire chunk using a MultiGet operation
136-
let confirmations = lookup_confirmations(history_db, spending_txids);
137+
let confirmations = lookup_confirmations(history_db, tip_height, spending_txids);
137138

138139
let mut batch = WriteBatch::default();
139140
for (v1_edge, v1_db_key) in v1_edges {
@@ -186,7 +187,7 @@ fn main() {
186187
}
187188

188189
// Lookup the confirmation status for the entire chunk using a MultiGet operation
189-
let confirmations = lookup_confirmations(history_db, history_txids);
190+
let confirmations = lookup_confirmations(history_db, tip_height, history_txids);
190191

191192
let mut batch = WriteBatch::default();
192193
for (hist, db_key) in history_entries {

src/new_index/schema.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ impl Indexer {
394394
self.from = FetchFrom::Bitcoind;
395395
}
396396

397-
self.tip_metric.set(headers.len() as i64 - 1);
397+
self.tip_metric.set(headers.best_height() as i64);
398398

399399
Ok(tip)
400400
}
@@ -959,8 +959,9 @@ impl ChainQuery {
959959
.map(BlockId::from)
960960
}
961961

962+
/// Get the chain tip height. Panics if called on an empty HeaderList.
962963
pub fn best_height(&self) -> usize {
963-
self.store.indexed_headers.read().unwrap().len() - 1
964+
self.store.indexed_headers.read().unwrap().best_height()
964965
}
965966

966967
pub fn best_hash(&self) -> BlockHash {
@@ -1087,7 +1088,9 @@ impl ChainQuery {
10871088
}
10881089

10891090
pub fn lookup_confirmations(&self, txids: BTreeSet<Txid>) -> HashMap<Txid, u32> {
1090-
lookup_confirmations(&self.store.history_db, txids)
1091+
let _timer = self.start_timer("lookup_confirmations");
1092+
let headers = self.store.indexed_headers.read().unwrap();
1093+
lookup_confirmations(&self.store.history_db, headers.best_height() as u32, txids)
10911094
}
10921095

10931096
pub fn get_block_status(&self, hash: &BlockHash) -> BlockStatus {
@@ -1242,14 +1245,19 @@ fn lookup_txo(txstore_db: &DB, outpoint: &OutPoint) -> Option<TxOut> {
12421245
.map(|val| deserialize(&val).expect("failed to parse TxOut"))
12431246
}
12441247

1245-
pub fn lookup_confirmations(history_db: &DB, txids: BTreeSet<Txid>) -> HashMap<Txid, u32> {
1248+
pub fn lookup_confirmations(
1249+
history_db: &DB,
1250+
tip_height: u32,
1251+
txids: BTreeSet<Txid>,
1252+
) -> HashMap<Txid, u32> {
12461253
history_db
12471254
.multi_get(txids.iter().map(TxConfRow::key))
12481255
.into_iter()
12491256
.zip(txids)
12501257
.filter_map(|(res, txid)| {
12511258
let confirmation_height = u32::from_le_bytes(res.unwrap()?.try_into().unwrap());
1252-
Some((txid, confirmation_height))
1259+
// skip over entries that point to non-existing heights (may happen during reorg handling)
1260+
(confirmation_height <= tip_height).then_some((txid, confirmation_height))
12531261
})
12541262
.collect()
12551263
}

src/util/block.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,13 @@ impl HeaderList {
270270
self.headers.len()
271271
}
272272

273+
/// Get the chain tip height. Panics if called on an empty HeaderList.
274+
pub fn best_height(&self) -> usize {
275+
self.len()
276+
.checked_sub(1)
277+
.expect("best_height() on empty HeaderList")
278+
}
279+
273280
pub fn is_empty(&self) -> bool {
274281
self.headers.is_empty()
275282
}
@@ -284,7 +291,7 @@ impl HeaderList {
284291
// Matches bitcoind's behaviour: bitcoin-cli getblock `bitcoin-cli getblockhash 0` | jq '.time == .mediantime'
285292
if height == 0 {
286293
self.headers.get(0).unwrap().header.time
287-
} else if height > self.len() - 1 {
294+
} else if height > self.best_height() {
288295
0
289296
} else {
290297
let mut timestamps = (height.saturating_sub(MTP_SPAN - 1)..=height)

0 commit comments

Comments
 (0)