@@ -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+
148171pub 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