Skip to content

Commit 532cacb

Browse files
committed
(bugfix) db: change tx_num endianness (LE->BE) to match db comparator
History.get_txnums and History.backup depend on ordering of tx_nums, so we want the lexicographical order (used by leveldb comparator) to match the numerical order.
1 parent 6de95d7 commit 532cacb

4 files changed

Lines changed: 39 additions & 27 deletions

File tree

electrumx/lib/util.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ def protocol_version(client_req, min_tuple, max_tuple):
317317
struct_le_Q = Struct('<Q')
318318
struct_be_H = Struct('>H')
319319
struct_be_I = Struct('>I')
320+
struct_be_Q = Struct('>Q')
320321
structB = Struct('B')
321322

322323
unpack_le_int32_from = struct_le_i.unpack_from
@@ -330,6 +331,7 @@ def protocol_version(client_req, min_tuple, max_tuple):
330331
unpack_le_uint32 = struct_le_I.unpack
331332
unpack_le_uint64 = struct_le_Q.unpack
332333
unpack_be_uint32 = struct_be_I.unpack
334+
unpack_be_uint64 = struct_be_Q.unpack
333335

334336
pack_le_int32 = struct_le_i.pack
335337
pack_le_int64 = struct_le_q.pack
@@ -338,6 +340,7 @@ def protocol_version(client_req, min_tuple, max_tuple):
338340
pack_le_uint64 = struct_le_Q.pack
339341
pack_be_uint16 = struct_be_H.pack
340342
pack_be_uint32 = struct_be_I.pack
343+
pack_be_uint64 = struct_be_Q.pack
341344
pack_byte = structB.pack
342345

343346
hex_to_bytes = bytes.fromhex

electrumx/server/block_processor.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
)
2626
from electrumx.lib.tx import Tx
2727
from electrumx.server.db import FlushData, DB
28-
from electrumx.server.history import TXNUM_LEN, TXOUTIDX_LEN, TXOUTIDX_PADDING
28+
from electrumx.server.history import TXNUM_LEN, TXOUTIDX_LEN, TXOUTIDX_PADDING, pack_txnum
2929

3030
if TYPE_CHECKING:
3131
from electrumx.lib.coins import Coin
@@ -457,11 +457,12 @@ def advance_txs(
457457
put_txo_to_spender_map = txo_to_spender_map.__setitem__
458458
to_le_uint32 = pack_le_uint32
459459
to_le_uint64 = pack_le_uint64
460+
_pack_txnum = pack_txnum
460461

461462
for tx, tx_hash in txs:
462463
hashXs = []
463464
append_hashX = hashXs.append
464-
tx_numb = to_le_uint64(tx_num)[:TXNUM_LEN]
465+
tx_numb = _pack_txnum(tx_num)
465466

466467
# Spend the inputs
467468
for txin in tx.inputs:
@@ -652,7 +653,7 @@ def spend_utxo(self, tx_hash: bytes, txout_idx: int) -> bytes:
652653
if tx_num is None:
653654
raise ChainError(f'UTXO {hash_to_hex_str(tx_hash)} / {txout_idx:,d} has '
654655
f'no corresponding tx_num in DB')
655-
tx_numb = pack_le_uint64(tx_num)[:TXNUM_LEN]
656+
tx_numb = pack_txnum(tx_num)
656657

657658
# Key: b'h' + tx_num + txout_idx
658659
# Value: hashX
@@ -765,10 +766,10 @@ def advance_txs(self, txs, is_unspendable):
765766
update_touched_hashxs = self.touched_hashxs.update
766767
hashx_fundings = defaultdict(bytearray)
767768
to_le_uint32 = pack_le_uint32
768-
to_le_uint64 = pack_le_uint64
769+
_pack_txnum = pack_txnum
769770

770771
for tx, _tx_hash in txs:
771-
tx_numb = to_le_uint64(tx_num)[:TXNUM_LEN]
772+
tx_numb = _pack_txnum(tx_num)
772773
hashXs = []
773774
append_hashX = hashXs.append
774775

@@ -813,13 +814,14 @@ def advance_txs(self, txs, is_unspendable):
813814
put_txo_to_spender_map = txo_to_spender_map.__setitem__
814815
to_le_uint32 = pack_le_uint32
815816
to_le_uint64 = pack_le_uint64
817+
_pack_txnum = pack_txnum
816818

817819
hashXs_by_tx = [set() for _ in txs]
818820

819821
# Add the new UTXOs
820822
for (tx, tx_hash), hashXs in zip(txs, hashXs_by_tx):
821823
add_hashXs = hashXs.add
822-
tx_numb = to_le_uint64(tx_num)[:TXNUM_LEN]
824+
tx_numb = _pack_txnum(tx_num)
823825

824826
for idx, txout in enumerate(tx.outputs):
825827
# Ignore unspendable outputs

electrumx/server/db.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
)
3131
from electrumx.server.storage import db_class, Storage
3232
from electrumx.server.history import (
33-
History, TXNUM_LEN, TXNUM_PADDING, TXOUTIDX_LEN, TXOUTIDX_PADDING,
33+
History, TXNUM_LEN, TXNUM_PADDING, TXOUTIDX_LEN, TXOUTIDX_PADDING, pack_txnum, unpack_txnum,
3434
)
3535

3636
if TYPE_CHECKING:
@@ -744,7 +744,7 @@ def read_utxos():
744744
for db_key, db_value in self.utxo_db.iterator(prefix=prefix):
745745
txout_idx, = unpack_le_uint32(db_key[-TXOUTIDX_LEN:] + TXOUTIDX_PADDING)
746746
tx_numb = db_key[-TXOUTIDX_LEN-TXNUM_LEN:-TXOUTIDX_LEN]
747-
tx_num, = unpack_le_uint64(tx_numb + TXNUM_PADDING)
747+
tx_num = unpack_txnum(tx_numb)
748748
value, = unpack_le_uint64(db_value)
749749
tx_hash, height = self.fs_tx_hash(tx_num)
750750
utxos_append(UTXO(tx_num, txout_idx, tx_hash, height, value))
@@ -765,7 +765,7 @@ def _get_hashX_for_utxo(
765765
tx_num = self.fs_txnum_for_txhash(tx_hash)
766766
if tx_num is None:
767767
return None, None
768-
tx_numb = pack_le_uint64(tx_num)[:TXNUM_LEN]
768+
tx_numb = pack_txnum(tx_num)
769769

770770
# Key: b'h' + tx_num + txout_idx
771771
# Value: hashX

electrumx/server/history.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@
99
'''History by script hash (address).'''
1010

1111
import ast
12-
import bisect
1312
import time
14-
from array import array
1513
from collections import defaultdict
1614
from typing import TYPE_CHECKING, Type, Optional, Dict, Sequence, Tuple
1715
import itertools
1816

1917
import electrumx.lib.util as util
2018
from electrumx.lib.hash import HASHX_LEN, hash_to_hex_str
21-
from electrumx.lib.util import (pack_be_uint16, pack_le_uint64,
22-
unpack_be_uint16_from, unpack_le_uint64,
23-
pack_le_uint32, unpack_le_uint32)
19+
from electrumx.lib.util import (pack_le_uint64, unpack_le_uint64,
20+
pack_le_uint32, unpack_le_uint32,
21+
pack_be_uint64, unpack_be_uint64)
2422

2523
if TYPE_CHECKING:
2624
from electrumx.server.storage import Storage
@@ -32,6 +30,14 @@
3230
TXOUTIDX_PADDING = bytes(4 - TXOUTIDX_LEN)
3331

3432

33+
def unpack_txnum(tx_numb: bytes) -> int:
34+
return unpack_be_uint64(TXNUM_PADDING + tx_numb)[0]
35+
36+
37+
def pack_txnum(tx_num: int) -> bytes:
38+
return pack_be_uint64(tx_num)[-TXNUM_LEN:]
39+
40+
3541
class History:
3642

3743
DB_VERSIONS = (3, )
@@ -115,23 +121,23 @@ def clear_excess(self, utxo_db_tx_count: int) -> None:
115121
hkeys = []
116122
for db_key, db_val in self.db.iterator(prefix=b'H'):
117123
tx_numb = db_key[1+HASHX_LEN: 1+HASHX_LEN+TXNUM_LEN]
118-
tx_num, = unpack_le_uint64(tx_numb + TXNUM_PADDING)
124+
tx_num = unpack_txnum(tx_numb)
119125
if tx_num >= utxo_db_tx_count:
120126
hkeys.append(db_key)
121127

122128
tkeys = []
123129
for db_key, db_val in self.db.iterator(prefix=b't'):
124130
tx_numb = db_val
125-
tx_num, = unpack_le_uint64(tx_numb + TXNUM_PADDING)
131+
tx_num = unpack_txnum(tx_numb)
126132
if tx_num >= utxo_db_tx_count:
127133
tkeys.append(db_key)
128134

129135
skeys = []
130136
for db_key, db_val in self.db.iterator(prefix=b's'):
131137
tx_numb1 = db_key[1:1+TXNUM_LEN]
132138
tx_numb2 = db_val
133-
tx_num1, = unpack_le_uint64(tx_numb1 + TXNUM_PADDING)
134-
tx_num2, = unpack_le_uint64(tx_numb2 + TXNUM_PADDING)
139+
tx_num1 = unpack_txnum(tx_numb1)
140+
tx_num2 = unpack_txnum(tx_numb2)
135141
if max(tx_num1, tx_num2) >= utxo_db_tx_count:
136142
skeys.append(db_key)
137143

@@ -183,7 +189,7 @@ def add_unflushed(
183189
spender_txnum = get_txnum_for_txhash(spender_hash)
184190
assert spender_txnum is not None
185191
prev_idx_packed = pack_le_uint32(prev_idx)[:TXOUTIDX_LEN]
186-
prev_txnumb = pack_le_uint64(prev_txnum)[:TXNUM_LEN]
192+
prev_txnumb = pack_txnum(prev_txnum)
187193
unflushed_spenders[prev_txnumb+prev_idx_packed] = spender_txnum
188194

189195
def unflushed_memsize(self):
@@ -210,11 +216,11 @@ def flush(self):
210216
batch.put(db_key, b'')
211217
for tx_hash, tx_num in sorted(self._unflushed_txhash_to_txnum_map.items()):
212218
db_key = b't' + tx_hash
213-
tx_numb = pack_le_uint64(tx_num)[:TXNUM_LEN]
219+
tx_numb = pack_txnum(tx_num)
214220
batch.put(db_key, tx_numb)
215221
for prevout, spender_txnum in sorted(self._unflushed_txo_to_spender.items()):
216222
db_key = b's' + prevout
217-
db_val = pack_le_uint64(spender_txnum)[:TXNUM_LEN]
223+
db_val = pack_txnum(spender_txnum)
218224
batch.put(db_key, db_val)
219225
self.hist_db_tx_count = self.hist_db_tx_count_next
220226
self.write_state(batch)
@@ -242,11 +248,12 @@ def backup(self, *, hashXs, tx_count, tx_hashes: Sequence[bytes], spends: Sequen
242248
prefix = b'H' + hashX
243249
for db_key, db_val in self.db.iterator(prefix=prefix, reverse=True):
244250
tx_numb = db_key[1+HASHX_LEN: 1+HASHX_LEN+TXNUM_LEN]
245-
tx_num, = unpack_le_uint64(tx_numb + TXNUM_PADDING)
251+
tx_num = unpack_txnum(tx_numb)
246252
if tx_num >= tx_count:
247253
nremoves_addr += 1
248254
deletes.append(db_key)
249255
else:
256+
# note: we can break now, due to 'reverse=True' and txnums being big endian
250257
break
251258
for key in deletes:
252259
batch.delete(key)
@@ -256,7 +263,7 @@ def backup(self, *, hashXs, tx_count, tx_hashes: Sequence[bytes], spends: Sequen
256263
assert len(prev_idx) == TXOUTIDX_LEN
257264
prev_txnum = get_txnum_for_txhash(prev_hash)
258265
assert prev_txnum is not None
259-
prev_txnumb = pack_le_uint64(prev_txnum)[:TXNUM_LEN]
266+
prev_txnumb = pack_txnum(prev_txnum)
260267
db_key = b's' + prev_txnumb + prev_idx
261268
batch.delete(db_key)
262269
for tx_hash in sorted(tx_hashes):
@@ -282,7 +289,7 @@ def get_txnums(self, hashX: bytes, limit: Optional[int] = 1000) -> Sequence[int]
282289
break
283290
prev_txnumb = db_key[1+HASHX_LEN: 1+HASHX_LEN+TXNUM_LEN]
284291
prev_idx_packed = db_key[-TXOUTIDX_LEN:]
285-
prev_txnum, = unpack_le_uint64(prev_txnumb + TXNUM_PADDING)
292+
prev_txnum = unpack_txnum(prev_txnumb)
286293
prev_idx, = unpack_le_uint32(prev_idx_packed + TXOUTIDX_PADDING)
287294
txnum_set.add(prev_txnum)
288295
spender_txnum = self.get_spender_txnum_for_txo(prev_txnum, prev_idx)
@@ -299,20 +306,20 @@ def get_txnum_for_txhash(self, tx_hash: bytes) -> Optional[int]:
299306
db_key = b't' + tx_hash
300307
tx_numb = self.db.get(db_key)
301308
if tx_numb:
302-
tx_num, = unpack_le_uint64(tx_numb + TXNUM_PADDING)
309+
tx_num = unpack_txnum(tx_numb)
303310
return tx_num
304311

305312
def get_spender_txnum_for_txo(self, prev_txnum: int, txout_idx: int) -> Optional[int]:
306313
'''For an outpoint, returns the tx_num that spent it.
307314
If the outpoint is unspent, or even if it never existed (!), returns None.
308315
'''
309316
prev_idx_packed = pack_le_uint32(txout_idx)[:TXOUTIDX_LEN]
310-
prev_txnumb = pack_le_uint64(prev_txnum)[:TXNUM_LEN]
317+
prev_txnumb = pack_txnum(prev_txnum)
311318
prevout = prev_txnumb + prev_idx_packed
312319
spender_txnum = self._unflushed_txhash_to_txnum_map.get(prevout)
313320
if spender_txnum is None:
314321
db_key = b's' + prevout
315322
spender_txnumb = self.db.get(db_key)
316323
if spender_txnumb:
317-
spender_txnum, = unpack_le_uint64(spender_txnumb + TXNUM_PADDING)
324+
spender_txnum = unpack_txnum(spender_txnumb)
318325
return spender_txnum

0 commit comments

Comments
 (0)