17
17
from dataclasses import dataclass
18
18
from glob import glob
19
19
from typing import Dict , List , Sequence , Tuple , Optional , TYPE_CHECKING , Union
20
+ from functools import partial
20
21
21
22
import attr
22
23
from aiorpcx import run_in_thread , sleep
@@ -54,6 +55,7 @@ class FlushData:
54
55
headers = attr .ib ()
55
56
block_tx_hashes = attr .ib () # type: List[bytes]
56
57
undo_block_tx_hashes = attr .ib () # type: List[bytes]
58
+ block_wtxids = attr .ib () # type: List[bytes]
57
59
undo_historical_spends = attr .ib () # type: List[bytes]
58
60
# The following are flushed to the UTXO DB if undo_infos is not None
59
61
undo_infos = attr .ib () # type: List[Tuple[Sequence[bytes], int]]
@@ -132,6 +134,8 @@ def __init__(self, env: 'Env'):
132
134
self .tx_counts_file = util .LogicalFile ('meta/txcounts' , 2 , 2000000 )
133
135
# on-disk: 32 byte txids in chain order, allows (tx_num -> txid) map
134
136
self .hashes_file = util .LogicalFile ('meta/hashes' , 4 , 16000000 )
137
+ # on-disk: 32 byte wtxids in chain order, allows (tx_num -> wtxid) map
138
+ self .wtxids_file = util .LogicalFile ('meta/wtxids' , 4 , 16000000 )
135
139
if not self .coin .STATIC_BLOCK_HEADERS :
136
140
self .headers_offsets_file = util .LogicalFile (
137
141
'meta/headers_offsets' , 2 , 16000000 )
@@ -221,6 +225,7 @@ def assert_flushed(self, flush_data: FlushData):
221
225
assert not flush_data .headers
222
226
assert not flush_data .block_tx_hashes
223
227
assert not flush_data .undo_block_tx_hashes
228
+ assert not flush_data .block_wtxids
224
229
assert not flush_data .undo_historical_spends
225
230
assert not flush_data .adds
226
231
assert not flush_data .deletes
@@ -280,15 +285,21 @@ def flush_fs(self, flush_data: FlushData):
280
285
prior_tx_count = (self .tx_counts [self .fs_height ]
281
286
if self .fs_height >= 0 else 0 )
282
287
assert len (flush_data .block_tx_hashes ) == len (flush_data .headers )
288
+ assert len (flush_data .block_wtxids ) == len (flush_data .headers )
283
289
assert flush_data .height == self .fs_height + len (flush_data .headers )
284
290
assert flush_data .tx_count == (self .tx_counts [- 1 ] if self .tx_counts
285
291
else 0 )
286
292
assert len (self .tx_counts ) == flush_data .height + 1
293
+
287
294
hashes = b'' .join (flush_data .block_tx_hashes )
288
295
flush_data .block_tx_hashes .clear ()
289
296
assert len (hashes ) % 32 == 0
290
297
assert len (hashes ) // 32 == flush_data .tx_count - prior_tx_count
291
298
299
+ wtxids = b'' .join (flush_data .block_wtxids )
300
+ flush_data .block_wtxids .clear ()
301
+ assert len (wtxids ) == len (hashes )
302
+
292
303
# Write the headers, tx counts, and tx hashes
293
304
start_time = time .monotonic ()
294
305
height_start = self .fs_height + 1
@@ -302,6 +313,8 @@ def flush_fs(self, flush_data: FlushData):
302
313
self .tx_counts [height_start :].tobytes ())
303
314
offset = prior_tx_count * 32
304
315
self .hashes_file .write (offset , hashes )
316
+ offset = prior_tx_count * 32
317
+ self .wtxids_file .write (offset , wtxids )
305
318
306
319
self .fs_height = flush_data .height
307
320
self .fs_tx_count = flush_data .tx_count
@@ -370,6 +383,7 @@ def flush_backup(self, flush_data: FlushData, touched_hashxs):
370
383
'''Like flush_dbs() but when backing up. All UTXOs are flushed.'''
371
384
assert not flush_data .headers
372
385
assert not flush_data .block_tx_hashes
386
+ assert not flush_data .block_wtxids
373
387
assert flush_data .height < self .db_height
374
388
self .history .assert_flushed ()
375
389
assert len (flush_data .undo_block_tx_hashes ) == self .db_height - flush_data .height
@@ -461,18 +475,19 @@ def read_headers():
461
475
462
476
return await run_in_thread (read_headers )
463
477
464
- def fs_tx_hash (self , tx_num : int ) -> Tuple [Optional [bytes ], int ]:
478
+ def fs_tx_hash (self , tx_num : int , * , wtxid : bool = False ) -> Tuple [Optional [bytes ], int ]:
465
479
'''Return a pair (tx_hash, tx_height) for the given tx number.
466
480
467
481
If the tx_height is not on disk, returns (None, tx_height).'''
482
+ file = self .wtxids_file if wtxid else self .hashes_file
468
483
tx_height = bisect_right (self .tx_counts , tx_num )
469
484
if tx_height > self .db_height :
470
485
tx_hash = None
471
486
else :
472
- tx_hash = self . hashes_file .read (tx_num * 32 , 32 )
487
+ tx_hash = file .read (tx_num * 32 , 32 )
473
488
return tx_hash , tx_height
474
489
475
- def fs_tx_hashes_at_blockheight (self , block_height ) :
490
+ def fs_tx_hashes_at_blockheight (self , block_height , * , wtxid : bool = False ) -> Sequence [ bytes ] :
476
491
'''Return a list of tx_hashes at given block height,
477
492
in the same order as in the block.
478
493
'''
@@ -484,12 +499,16 @@ def fs_tx_hashes_at_blockheight(self, block_height):
484
499
else :
485
500
first_tx_num = 0
486
501
num_txs_in_block = self .tx_counts [block_height ] - first_tx_num
487
- tx_hashes = self .hashes_file .read (first_tx_num * 32 , num_txs_in_block * 32 )
502
+ file = self .wtxids_file if wtxid else self .hashes_file
503
+ tx_hashes = file .read (first_tx_num * 32 , num_txs_in_block * 32 )
488
504
assert num_txs_in_block == len (tx_hashes ) // 32
489
505
return [tx_hashes [idx * 32 : (idx + 1 ) * 32 ] for idx in range (num_txs_in_block )]
490
506
491
- async def tx_hashes_at_blockheight (self , block_height ):
492
- return await run_in_thread (self .fs_tx_hashes_at_blockheight , block_height )
507
+ async def tx_hashes_at_blockheight (
508
+ self , block_height , * , wtxid : bool = False ,
509
+ ) -> Sequence [bytes ]:
510
+ func = partial (self .fs_tx_hashes_at_blockheight , block_height , wtxid = wtxid )
511
+ return await run_in_thread (func )
493
512
494
513
async def fs_block_hashes (self , height , count ):
495
514
headers_concat , headers_count = await self .read_headers (height , count )
0 commit comments