# See the file "LICENSE" for information about the copyright # and warranty status of this software. import array import itertools import os import struct import time from binascii import hexlify, unhexlify from bisect import bisect_right from collections import defaultdict, namedtuple from functools import partial import logging import plyvel from lib.coins import Bitcoin from lib.script import ScriptPubKey ADDR_TX_HASH_LEN=6 UTXO_TX_HASH_LEN=4 HIST_ENTRY_LEN=256*4 # Admits 65536 * HIST_ENTRY_LEN/4 entries UTXO = namedtuple("UTXO", "tx_num tx_pos tx_hash height value") def to_4_bytes(value): return struct.pack(' HIST_ENTRY_LEN: # must be big-endian (idx, ) = struct.unpack('>H', key[-2:]) for n in range(HIST_ENTRY_LEN, len(v), HIST_ENTRY_LEN): idx += 1 key = prefix + struct.pack('>H', idx) if idx % 500 == 0: addr = self.coin.P2PKH_address_from_hash160(hash160) self.logger.info('address {} hist moving to idx {:d}' .format(addr, idx)) self.db.put(key, v[n:n + HIST_ENTRY_LEN]) self.history = defaultdict(list) def get_hash160(self, tx_hash, idx, delete=True): key = b'h' + tx_hash[:ADDR_TX_HASH_LEN] + struct.pack(' self.flush_size: self.flush() def process_tx(self, tx_hash, tx): hash160s = set() if not tx.is_coinbase: for txin in tx.inputs: hash160s.add(self.spend_utxo(txin.prevout)) for idx, txout in enumerate(tx.outputs): hash160s.add(self.put_utxo(tx_hash, idx, txout)) for hash160 in hash160s: self.history[hash160].append(self.tx_count) self.tx_count += 1 def get_tx_hash(self, tx_num): '''Returns the tx_hash and height of a tx number.''' height = bisect_right(self.tx_counts, tx_num) # Is this on disk or unflushed? if height >= self.db_height: tx_hashes = self.tx_hashes[height - self.db_height] tx_hash = tx_hashes[tx_num - self.tx_counts[height - 1]] else: file_pos = tx_num * 32 file_num, offset = divmod(file_pos, self.tx_hash_file_size) filename = 'hashes{:05d}'.format(file_num) with self.open_file(filename) as f: f.seek(offset) tx_hash = f.read(32) return tx_hash, height def get_balance(self, hash160): '''Returns the confirmed balance of an address.''' utxos = self.get_utxos(hash_160) return sum(utxo.value for utxo in utxos) def get_history(self, hash160): '''Returns an unpruned, sorted list of (tx_hash, height) tuples of transactions that touched the address, earliest in the blockchain first. Includes both spending and receiving transactions. ''' prefix = b'H' + hash160 a = array.array('I') for key, hist in self.db.iterator(prefix=prefix): a.frombytes(hist) return [self.get_tx_hash(tx_num) for tx_num in a] def get_utxos(self, hash160): '''Returns all UTXOs for an address sorted such that the earliest in the blockchain comes first. ''' unpack = struct.unpack prefix = b'u' + hash160 utxos = [] for k, v in self.db.iterator(prefix=prefix): (tx_pos, ) = unpack('