Skip to content

Commit c12eef6

Browse files
authored
Merge pull request Blockstream#44 from mempool/junderw/fix-broken-stats
Fix: Fix Stats and Utxo cache
2 parents 3b16c0a + 3615c48 commit c12eef6

File tree

1 file changed

+33
-3
lines changed

1 file changed

+33
-3
lines changed

src/new_index/schema.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,29 @@ pub struct ScriptStats {
145145
pub spent_txo_sum: u64,
146146
}
147147

148+
impl ScriptStats {
149+
#[cfg(feature = "liquid")]
150+
fn is_sane(&self) -> bool {
151+
// See below for comments.
152+
self.spent_txo_count <= self.funded_txo_count
153+
&& self.tx_count <= self.spent_txo_count + self.funded_txo_count
154+
}
155+
#[cfg(not(feature = "liquid"))]
156+
fn is_sane(&self) -> bool {
157+
// There are less or equal spends to funds
158+
self.spent_txo_count <= self.funded_txo_count
159+
// There are less or equal transactions to total spent+funded txo counts
160+
// (Most spread out txo case = N funds in 1 tx each + M spends in 1 tx each = N + M txes)
161+
&& self.tx_count <= self.spent_txo_count + self.funded_txo_count
162+
// There are less or equal spent coins to funded coins
163+
&& self.spent_txo_sum <= self.funded_txo_sum
164+
// If funded and spent txos are equal (0 balance)
165+
// Then funded and spent coins must be equal (0 balance)
166+
&& (self.funded_txo_count == self.spent_txo_count)
167+
== (self.funded_txo_sum == self.spent_txo_sum)
168+
}
169+
}
170+
148171
pub struct Indexer {
149172
store: Arc<Store>,
150173
flush: DBFlush,
@@ -620,6 +643,9 @@ impl ChainQuery {
620643
.map(TxHistoryRow::from_row)
621644
.filter_map(|history| {
622645
self.tx_confirming_block(&history.get_txid())
646+
// drop history entries that were previously confirmed in a re-orged block and later
647+
// confirmed again at a different height
648+
.filter(|blockid| blockid.height == history.key.confirmed_height as usize)
623649
.map(|b| (history, b))
624650
});
625651

@@ -656,12 +682,14 @@ impl ChainQuery {
656682
let _timer = self.start_timer("stats");
657683

658684
// get the last known stats and the blockhash they are updated for.
659-
// invalidates the cache if the block was orphaned.
685+
// invalidates the cache if the block was orphaned or if values are out of sync.
660686
let cache: Option<(ScriptStats, usize)> = self
661687
.store
662688
.cache_db
663689
.get(&StatsCacheRow::key(scripthash))
664-
.map(|c| bincode_util::deserialize_little(&c).unwrap())
690+
.map(|c| bincode_util::deserialize_little::<(ScriptStats, BlockHash)>(&c).unwrap())
691+
// Check that the values are sane (No negative balances or balances with 0 utxos)
692+
.filter(|(stats, _)| stats.is_sane())
665693
.and_then(|(stats, blockhash)| {
666694
self.height_by_hash(&blockhash)
667695
.map(|height| (stats, height))
@@ -696,9 +724,11 @@ impl ChainQuery {
696724
let history_iter = self
697725
.history_iter_scan(b'H', scripthash, start_height)
698726
.map(TxHistoryRow::from_row)
699-
.unique_by(|th| (th.get_txid(), th.get_funded_outpoint()))
700727
.filter_map(|history| {
701728
self.tx_confirming_block(&history.get_txid())
729+
// drop history entries that were previously confirmed in a re-orged block and later
730+
// confirmed again at a different height
731+
.filter(|blockid| blockid.height == history.key.confirmed_height as usize)
702732
.map(|blockid| (history, blockid))
703733
});
704734

0 commit comments

Comments
 (0)