Skip to content

Commit 1d6cb4b

Browse files
author
Neil Booth
committed
Add UTXO count tracking
1 parent ab9024f commit 1d6cb4b

File tree

3 files changed

+40
-18
lines changed

3 files changed

+40
-18
lines changed

electrumx/lib/tx.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from electrumx.lib.hash import double_sha256, hash_to_hex_str
1313
from electrumx.lib.util import (
1414
unpack_le_int32_from, unpack_le_int64_from, unpack_le_uint16_from,
15-
unpack_be_uint16_from,
1615
unpack_le_uint32_from, unpack_le_uint64_from, pack_le_int32, pack_varint,
1716
pack_le_uint32, pack_le_int64, pack_varbytes,
1817
)

electrumx/server/block_processor.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ def advance_block(self, block):
499499
append_tx_hash = tx_hashes.append
500500
to_le_uint32 = pack_le_uint32
501501
to_le_uint64 = pack_le_uint64
502+
utxo_count_delta = 0
502503

503504
with block as block:
504505
if self.coin.header_prevhash(block.header) != self.state.tip:
@@ -515,6 +516,7 @@ def advance_block(self, block):
515516
for txin in tx.inputs:
516517
if txin.is_generation():
517518
continue
519+
utxo_count_delta -= 1
518520
cache_value = spend_utxo(bytes(txin.prev_hash), txin.prev_idx)
519521
undo_info_append(cache_value)
520522
append_hashX(cache_value[:-13])
@@ -524,6 +526,7 @@ def advance_block(self, block):
524526
# Ignore unspendable outputs
525527
if is_unspendable(txout.pk_script):
526528
continue
529+
utxo_count_delta += 1
527530

528531
# Get the hashX
529532
hashX = script_hashX(txout.pk_script)
@@ -548,6 +551,7 @@ def advance_block(self, block):
548551
state.height = block.height
549552
state.tip = self.coin.header_hash(block.header)
550553
state.chain_size += block.size
554+
state.utxo_count += utxo_count_delta
551555
state.tx_count = tx_num
552556
self.ok = True
553557

@@ -574,6 +578,7 @@ def backup_block(self, block):
574578
undo_entry_len = 13 + HASHX_LEN
575579

576580
count = 0
581+
utxo_count_delta = 0
577582
with block as block:
578583
self.ok = False
579584
for tx, tx_hash in block.iter_txs_reversed():
@@ -583,13 +588,15 @@ def backup_block(self, block):
583588
if is_unspendable(txout.pk_script):
584589
continue
585590

591+
utxo_count_delta -= 1
586592
cache_value = spend_utxo(tx_hash, idx)
587593
touched_add(cache_value[:-13])
588594

589595
# Restore the inputs
590596
for txin in reversed(tx.inputs):
591597
if txin.is_generation():
592598
continue
599+
utxo_count_delta += 1
593600
n -= undo_entry_len
594601
undo_item = undo_info[n:n + undo_entry_len]
595602
put_utxo(bytes(txin.prev_hash) + pack_le_uint32(txin.prev_idx), undo_item)
@@ -603,6 +610,7 @@ def backup_block(self, block):
603610
state.height -= 1
604611
state.tip = self.coin.header_prevhash(block.header)
605612
state.chain_size -= block.size
613+
state.utxo_count += utxo_count_delta
606614
state.tx_count -= count
607615

608616
self.db.tx_counts.pop()

electrumx/server/db.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class ChainState:
5555
flush_time = attr.ib() # Time of flush
5656
first_sync = attr.ib()
5757
db_version = attr.ib()
58+
utxo_count = attr.ib()
5859

5960
def copy(self):
6061
return copy.copy(self)
@@ -189,6 +190,18 @@ def assert_flushed(self, flush_data):
189190
assert not flush_data.undo_infos
190191
self.history.assert_flushed()
191192

193+
def log_flush_stats(self, prefix, flush_data, elapsed):
194+
tx_delta = flush_data.state.tx_count - self.last_flush_state.tx_count
195+
size_delta = flush_data.state.chain_size - self.last_flush_state.chain_size
196+
utxo_count_delta = flush_data.state.utxo_count - self.last_flush_state.utxo_count
197+
198+
self.logger.info(f'{prefix} #{self.history.flush_count:,d} took {elapsed:.1f}s. '
199+
f'Height {flush_data.state.height:,d} '
200+
f'txs: {flush_data.state.tx_count:,d} ({tx_delta:+,d}) '
201+
f'utxos: {flush_data.state.utxo_count:,d} ({utxo_count_delta:+,d}) '
202+
f'size: {flush_data.state.chain_size:,d} ({size_delta:+,d})')
203+
return size_delta
204+
192205
def flush_dbs(self, flush_data, flush_utxos, size_remaining):
193206
'''Flush out cached state. History is always flushed; UTXOs are
194207
flushed if flush_utxos.'''
@@ -220,13 +233,7 @@ def flush_dbs(self, flush_data, flush_utxos, size_remaining):
220233
self.state = flush_data.state.copy()
221234
self.write_utxo_state(self.utxo_db)
222235

223-
tx_delta = flush_data.state.tx_count - self.last_flush_state.tx_count
224-
size_delta = flush_data.state.chain_size - self.last_flush_state.chain_size
225-
226-
self.logger.info(f'flush #{self.history.flush_count:,d} took {elapsed:.1f}s. '
227-
f'Height {flush_data.state.height:,d} '
228-
f'txs: {flush_data.state.tx_count:,d} ({tx_delta:+,d}) '
229-
f'size: {flush_data.state.chain_size:,d} ({size_delta:+,d})')
236+
size_delta = self.log_flush_stats('flush', flush_data, elapsed)
230237

231238
# Catch-up stats
232239
if self.utxo_db.for_sync:
@@ -332,14 +339,7 @@ def flush_backup(self, flush_data, touched):
332339
self.history.backup(touched, flush_data.state.tx_count)
333340
self.flush_utxo_db(flush_data)
334341

335-
elapsed = time.time() - start_time
336-
tx_delta = flush_data.state.tx_count - self.last_flush_state.tx_count
337-
size_delta = flush_data.state.chain_size - self.last_flush_state.chain_size
338-
339-
self.logger.info(f'backup flush #{self.history.flush_count:,d} took '
340-
f'{elapsed:.1f}s. Height {flush_data.state.height:,d} '
341-
f'txs: {flush_data.state.tx_count:,d} ({tx_delta:+,d}) '
342-
f'size: {flush_data.state.chain_size:,d} ({size_delta:+,d})')
342+
self.log_flush_stats('backup flush', flush_data, time.time() - start_time)
343343

344344
self.last_flush_state = flush_data.state.copy()
345345

@@ -482,12 +482,19 @@ def clear_excess_undo_info(self):
482482
# -- UTXO database
483483

484484
def read_utxo_state(self):
485+
def count_utxos():
486+
count = 0
487+
for db_key, db_value in self.utxo_db.iterator(prefix=b'u'):
488+
count += 1
489+
return count
490+
485491
now = time.time()
486492
state = self.utxo_db.get(b'state')
487493
if not state:
488494
state = ChainState(height=-1, tx_count=0, chain_size=0, tip=bytes(32),
489495
flush_count=0, sync_time=0, flush_time=now,
490-
first_sync=True, db_version=max(self.DB_VERSIONS))
496+
first_sync=True, db_version=max(self.DB_VERSIONS),
497+
utxo_count=0)
491498
else:
492499
state = ast.literal_eval(state.decode())
493500
if not isinstance(state, dict):
@@ -506,14 +513,20 @@ def read_utxo_state(self):
506513
flush_time=now,
507514
first_sync=state['first_sync'],
508515
db_version=state['db_version'],
516+
utxo_count=state.get('utxo_count', -1),
509517
)
510518

511519
self.state = state
512-
self.last_flush_state = state.copy()
513520
if state.db_version not in self.DB_VERSIONS:
514521
raise self.DBError(f'your UTXO DB version is {state.db_version} but this '
515522
f'software only handles versions {self.DB_VERSIONS}')
516523

524+
if self.state.utxo_count == -1:
525+
self.logger.info('counting UTXOs, please wait...')
526+
self.state.utxo_count = count_utxos()
527+
528+
self.last_flush_state = state.copy()
529+
517530
# These are as we flush data to disk ahead of DB state
518531
self.fs_height = state.height
519532
self.fs_tx_count = state.tx_count
@@ -525,6 +538,7 @@ def read_utxo_state(self):
525538
self.logger.info(f'height: {state.height:,d}')
526539
self.logger.info(f'tip: {hash_to_hex_str(state.tip)}')
527540
self.logger.info(f'tx count: {state.tx_count:,d}')
541+
self.logger.info(f'utxo count: {state.utxo_count:,d}')
528542
self.logger.info(f'chain size: {state.chain_size // 1_000_000_000} GB '
529543
f'({state.chain_size:,d} bytes)')
530544
if self.utxo_db.for_sync:
@@ -544,6 +558,7 @@ def write_utxo_state(self, batch):
544558
'wall_time': self.state.sync_time,
545559
'first_sync': self.state.first_sync,
546560
'db_version': self.state.db_version,
561+
'utxo_count': self.state.utxo_count,
547562
}
548563
batch.put(b'state', repr(state).encode())
549564

0 commit comments

Comments
 (0)