def read_utxos(): utxos = [] utxos_append = utxos.append # Key: b'u' + address_hashX + tx_idx + tx_num # Value: the UTXO value as a 64-bit unsigned integer prefix = b'u' + hashX for db_key, db_value in self.utxo_db.iterator(prefix=prefix): tx_pos, = unpack_le_uint32(db_key[-9:-5]) tx_num, = unpack_le_uint64(db_key[-5:] + bytes(3)) value, = unpack_le_uint64(db_value) tx_hash, height = self.fs_tx_hash(tx_num) utxos_append(UTXO(tx_num, tx_pos, tx_hash, height, value)) return utxos
def read_utxos(): utxos = [] utxos_append = utxos.append txnum_padding = bytes(8-TXNUM_LEN) # Key: b'u' + address_hashX + txout_idx + tx_num # Value: the UTXO value as a 64-bit unsigned integer prefix = b'u' + hashX for db_key, db_value in self.utxo_db.iterator(prefix=prefix): txout_idx, = unpack_le_uint32(db_key[-TXNUM_LEN-4:-TXNUM_LEN]) tx_num, = unpack_le_uint64(db_key[-TXNUM_LEN:] + txnum_padding) value, = unpack_le_uint64(db_value) tx_hash, height = self.fs_tx_hash(tx_num) utxos_append(UTXO(tx_num, txout_idx, tx_hash, height, value)) return utxos
def get_txnums(self, hashX, limit=1000): '''Generator that returns an unpruned, sorted list of tx_nums in the history of a hashX. Includes both spending and receiving transactions. By default yields at most 1000 entries. Set limit to None to get them all. ''' limit = util.resolve_limit(limit) chunks = util.chunks for _key, hist in self.db.iterator(prefix=hashX): for tx_numb in chunks(hist, 5): if limit == 0: return tx_num, = unpack_le_uint64(tx_numb + bytes(3)) yield tx_num limit -= 1
def lookup_hashX(tx_hash, tx_idx): idx_packed = pack_le_uint32(tx_idx) # Key: b'h' + compressed_tx_hash + tx_idx + tx_num # Value: hashX prefix = b'h' + tx_hash[:4] + idx_packed # Find which entry, if any, the TX_HASH matches. for db_key, hashX in self.utxo_db.iterator(prefix=prefix): tx_num_packed = db_key[-5:] tx_num, = unpack_le_uint64(tx_num_packed + bytes(3)) fs_hash, _height = self.fs_tx_hash(tx_num) if fs_hash == tx_hash: return hashX, idx_packed + tx_num_packed return None, None
def read_utxos(): utxos = [] utxos_append = utxos.append # Key: b'u' + address_hashX + tx_num + txout_idx # Value: the UTXO value as a 64-bit unsigned integer prefix = b'u' + hashX for db_key, db_value in self.utxo_db.iterator(prefix=prefix): txout_idx, = unpack_le_uint32(db_key[-TXOUTIDX_LEN:] + TXOUTIDX_PADDING) tx_numb = db_key[-TXOUTIDX_LEN - TXNUM_LEN:-TXOUTIDX_LEN] tx_num = unpack_txnum(tx_numb) value, = unpack_le_uint64(db_value) tx_hash, height = self.fs_tx_hash(tx_num) utxos_append(UTXO(tx_num, txout_idx, tx_hash, height, value)) return utxos
def lookup_utxo(hashX, suffix): if not hashX: # This can happen when the daemon is a block ahead # of us and has mempool txs spending outputs from # that new block return None # Key: b'u' + address_hashX + tx_idx + tx_num # Value: the UTXO value as a 64-bit unsigned integer key = b'u' + hashX + suffix db_value = self.utxo_db.get(key) if not db_value: # This can happen if the DB was updated between # getting the hashXs and getting the UTXOs return None value, = unpack_le_uint64(db_value) return hashX, value
def spend_utxo(self, tx_hash: bytes, tx_idx: int, spend_tx_hash) -> bytes: '''Spend a UTXO and return (hashX + tx_num + value_sats). If the UTXO is not in the cache it must be on disk. We store all UTXOs so not finding one indicates a logic error or DB corruption. ''' # Fast track is it being in the cache idx_packed = pack_le_uint32(tx_idx) cache_value = self.utxo_cache.pop(tx_hash + idx_packed, None) if cache_value: return cache_value # Spend it from the DB. txnum_padding = bytes(8-TXNUM_LEN) # Key: b'h' + compressed_tx_hash + tx_idx + tx_num # Value: hashX prefix = b'h' + tx_hash[:COMP_TXID_LEN] + idx_packed candidates = {db_key: hashX for db_key, hashX in self.db.utxo_db.iterator(prefix=prefix)} for hdb_key, hashX in candidates.items(): tx_num_packed = hdb_key[-TXNUM_LEN:] if len(candidates) > 1: tx_num, = unpack_le_uint64(tx_num_packed + txnum_padding) hash, _height = self.db.fs_tx_hash(tx_num) if hash != tx_hash: assert hash is not None # Should always be found continue # Key: b'u' + address_hashX + tx_idx + tx_num # Value: the UTXO value as a 64-bit unsigned integer udb_key = b'u' + hashX + hdb_key[-4-TXNUM_LEN:] utxo_value_packed = self.db.utxo_db.get(udb_key) if utxo_value_packed: # Remove both entries for this UTXO self.db_deletes.append(hdb_key) self.db_deletes.append(udb_key) return hashX + tx_num_packed + utxo_value_packed if spend_tx_hash in self.dup_tx_hashes: return None raise ChainError(f'UTXO {hash_to_hex_str(tx_hash)} / {tx_idx:,d} not ' f'found in "h" table')
def spend_utxo(self, tx_hash, tx_idx): '''Spend a UTXO and return the 33-byte value. If the UTXO is not in the cache it must be on disk. We store all UTXOs so not finding one indicates a logic error or DB corruption. ''' # Fast track is it being in the cache idx_packed = pack_le_uint32(tx_idx) cache_value = self.utxo_cache.pop(tx_hash + idx_packed, None) if cache_value: return cache_value # Spend it from the DB. # Key: b'h' + compressed_tx_hash + tx_idx + tx_num # Value: hashX prefix = b'h' + tx_hash[:4] + idx_packed candidates = { db_key: hashX for db_key, hashX in self.db.utxo_db.iterator(prefix=prefix) } for hdb_key, hashX in candidates.items(): tx_num_packed = hdb_key[-5:] if len(candidates) > 1: tx_num, = unpack_le_uint64(tx_num_packed + bytes(3)) hash, _height = self.db.fs_tx_hash(tx_num) if hash != tx_hash: assert hash is not None # Should always be found continue # Key: b'u' + address_hashX + tx_idx + tx_num # Value: the UTXO value as a 64-bit unsigned integer udb_key = b'u' + hashX + hdb_key[-9:] utxo_value_packed = self.db.utxo_db.get(udb_key) if utxo_value_packed: # Remove both entries for this UTXO self.db_deletes.append(hdb_key) self.db_deletes.append(udb_key) return hashX + tx_num_packed + utxo_value_packed raise ChainError('UTXO {} / {:,d} not found in "h" table'.format( hash_to_hex_str(tx_hash), tx_idx))
def dynamic_header_offset(self, height): assert not self.coin.STATIC_BLOCK_HEADERS offset, = unpack_le_uint64( self.headers_offsets_file.read(height * 8, 8)) return offset