From 66441024417e324b41b951ee6a8622e0d8aa6850 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Mon, 10 Oct 2016 11:17:26 +0900 Subject: [PATCH] Use hash168 to distinguish address types in DB --- lib/coins.py | 46 ++++++++++++++++++------------- lib/script.py | 17 +++++++----- lib/util.py | 18 ------------ query.py | 8 +++--- server/db.py | 76 +++++++++++++++++++++++++-------------------------- 5 files changed, 79 insertions(+), 86 deletions(-) diff --git a/lib/coins.py b/lib/coins.py index 6c5c7b6..8afed4d 100644 --- a/lib/coins.py +++ b/lib/coins.py @@ -16,7 +16,7 @@ class CoinError(Exception): class Coin(object): - '''Base class of coin hierarchy''' + '''Base class of coin hierarchy.''' # Not sure if these are coin-specific HEADER_LEN = 80 @@ -52,59 +52,67 @@ class Coin(object): raise CoinError("version bytes unrecognised") @classmethod - def address_to_hash160(cls, addr): - '''Returns a hash160 given an address''' + def address_to_hash168(cls, addr): + '''Return a 21-byte hash given an address. + + This is the hash160 prefixed by the address version byte. + ''' result = Base58.decode_check(addr) if len(result) != 21: raise CoinError('invalid address: {}'.format(addr)) - return result[1:] + return result @classmethod def P2PKH_address_from_hash160(cls, hash_bytes): - '''Returns a P2PKH address given a public key''' + '''Return a P2PKH address given a public key.''' assert len(hash_bytes) == 20 payload = bytes([cls.P2PKH_VERBYTE]) + hash_bytes return Base58.encode_check(payload) @classmethod def P2PKH_address_from_pubkey(cls, pubkey): - '''Returns a coin address given a public key''' + '''Return a coin address given a public key.''' return cls.P2PKH_address_from_hash160(hash160(pubkey)) @classmethod def P2SH_address_from_hash160(cls, pubkey_bytes): - '''Returns a coin address given a public key''' + '''Return a coin address given a public key.''' assert len(hash_bytes) == 20 payload = bytes([cls.P2SH_VERBYTE]) + hash_bytes return Base58.encode_check(payload) @classmethod def multisig_address(cls, m, pubkeys): - '''Returns the P2SH address for an M of N multisig transaction. Pass - the N pubkeys of which M are needed to sign it. If generating - an address for a wallet, it is the caller's responsibility to - sort them to ensure order does not matter for, e.g., wallet - recovery.''' + '''Return the P2SH address for an M of N multisig transaction. + + Pass the N pubkeys of which M are needed to sign it. If + generating an address for a wallet, it is the caller's + responsibility to sort them to ensure order does not matter + for, e.g., wallet recovery. + ''' script = cls.pay_to_multisig_script(m, pubkeys) payload = bytes([cls.P2SH_VERBYTE]) + hash160(pubkey_bytes) return Base58.encode_check(payload) @classmethod def pay_to_multisig_script(cls, m, pubkeys): - '''Returns a P2SH multisig script for an M of N multisig - transaction.''' + '''Return a P2SH script for an M of N multisig transaction.''' return ScriptPubKey.multisig_script(m, pubkeys) @classmethod def pay_to_pubkey_script(cls, pubkey): - '''Returns a pubkey script that pays to pubkey. The input is the - raw pubkey bytes (length 33 or 65).''' + '''Return a pubkey script that pays to a pubkey. + + Pass the raw pubkey bytes (length 33 or 65). + ''' return ScriptPubKey.P2PK_script(pubkey) @classmethod def pay_to_address_script(cls, address): - '''Returns a pubkey script that pays to pubkey hash. Input is the - address (either P2PKH or P2SH) in base58 form.''' + '''Return a pubkey script that pays to a pubkey hash. + + Pass the address (either P2PKH or P2SH) in base58 form. + ''' raw = Base58.decode_check(address) # Require version byte plus hash160. @@ -121,7 +129,7 @@ class Coin(object): @classmethod def prvkey_WIF(privkey_bytes, compressed): - "The private key encoded in Wallet Import Format" + "Return the private key encoded in Wallet Import Format." payload = bytearray([cls.WIF_BYTE]) + privkey_bytes if compressed: payload.append(0x01) diff --git a/lib/script.py b/lib/script.py index adfb23f..0239a1d 100644 --- a/lib/script.py +++ b/lib/script.py @@ -131,20 +131,20 @@ class ScriptPubKey(object): TO_P2SH_OPS = [OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL] TO_PUBKEY_OPS = [-1, OpCodes.OP_CHECKSIG] - def __init__(self, script, coin, kind, hash160, pubkey=None): + def __init__(self, script, coin, kind, hash168, pubkey=None): self.script = script self.coin = coin self.kind = kind - self.hash160 = hash160 + self.hash168 = hash168 if pubkey: self.pubkey = pubkey @cachedproperty def address(self): if self.kind == ScriptPubKey.TO_P2SH: - return self.coin.P2SH_address_from_hash160(self.hash160) + return self.coin.P2SH_address_from_hash160(self.hash168[1:]) if self.hash160: - return self.coin.P2PKH_address_from_hash160(self.hash160) + return self.coin.P2PKH_address_from_hash160(self.hash168[1:]) return '' @classmethod @@ -163,14 +163,17 @@ class ScriptPubKey(object): ops, datas = Script.get_ops(script) if Script.match_ops(ops, cls.TO_ADDRESS_OPS): - return cls(script, coin, cls.TO_ADDRESS, datas[2]) + return cls(script, coin, cls.TO_ADDRESS, + bytes([coin.P2PKH_VERBYTE]) + datas[2]) if Script.match_ops(ops, cls.TO_P2SH_OPS): - return cls(script, coin, cls.TO_P2SH, datas[1]) + return cls(script, coin, cls.TO_P2SH, + bytes([coin.P2SH_VERBYTE]) + datas[1]) if Script.match_ops(ops, cls.TO_PUBKEY_OPS): pubkey = datas[0] - return cls(script, coin, cls.TO_PUBKEY, hash160(pubkey), pubkey) + return cls(script, coin, cls.TO_PUBKEY, + bytes([coin.P2PKH_VERBYTE]) + hash160(pubkey), pubkey) raise ScriptError('unknown script pubkey pattern') diff --git a/lib/util.py b/lib/util.py index d23598a..40f9e2f 100644 --- a/lib/util.py +++ b/lib/util.py @@ -5,24 +5,6 @@ import sys -class Log(object): - '''Logging base class''' - - VERBOSE = True - - def diagnostic_name(self): - return self.__class__.__name__ - - def log(self, *msgs): - if Log.VERBOSE: - print('[{}]: '.format(self.diagnostic_name()), *msgs, - file=sys.stdout, flush=True) - - def log_error(self, *msg): - print('[{}]: ERROR: {}'.format(self.diagnostic_name()), *msgs, - file=sys.stderr, flush=True) - - # Method decorator. To be used for calculations that will always # deliver the same result. The method cannot take any arguments # and should be accessed as an attribute. diff --git a/query.py b/query.py index 63cdc72..23ed768 100644 --- a/query.py +++ b/query.py @@ -23,21 +23,21 @@ def main(): limit = 10 for addr in sys.argv[argc:]: print('Address: ', addr) - hash160 = coin.address_to_hash160(addr) + hash168 = coin.address_to_hash168(addr) n = None - for n, (tx_hash, height) in enumerate(db.get_history(hash160, limit)): + for n, (tx_hash, height) in enumerate(db.get_history(hash168, limit)): print('History #{:d}: hash: {} height: {:d}' .format(n + 1, bytes(reversed(tx_hash)).hex(), height)) if n is None: print('No history') n = None - for n, utxo in enumerate(db.get_utxos(hash160, limit)): + for n, utxo in enumerate(db.get_utxos(hash168, limit)): print('UTXOs #{:d}: hash: {} pos: {:d} height: {:d} value: {:d}' .format(n, bytes(reversed(utxo.tx_hash)).hex(), utxo.tx_pos, utxo.height, utxo.value)) if n is None: print('No UTXOs') - balance = db.get_balance(hash160) + balance = db.get_balance(hash168) print('Balance: {} {}'.format(coin.decimal_value(balance), coin.SHORTNAME)) diff --git a/server/db.py b/server/db.py index a166cac..d053975 100644 --- a/server/db.py +++ b/server/db.py @@ -256,56 +256,56 @@ class DB(object): self.history.pop(None, None) flush_id = struct.pack('>H', self.flush_count) - for hash160, hist in self.history.items(): - key = b'H' + hash160 + flush_id + for hash168, hist in self.history.items(): + key = b'H' + hash168 + flush_id batch.put(key, array.array('I', hist).tobytes()) self.history = defaultdict(list) - def get_hash160(self, tx_hash, idx, delete=True): + def get_hash168(self, tx_hash, idx, delete=True): key = b'h' + tx_hash[:ADDR_TX_HASH_LEN] + struct.pack('= 0 return limit - def get_history(self, hash160, limit=1000): + def get_history(self, hash168, limit=1000): '''Generator that 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 @@ -466,7 +466,7 @@ class DB(object): Set limit to None to get them all. ''' limit = self.resolve_limit(limit) - prefix = b'H' + hash160 + prefix = b'H' + hash168 for key, hist in self.db.iterator(prefix=prefix): a = array.array('I') a.frombytes(hist) @@ -476,18 +476,18 @@ class DB(object): yield self.get_tx_hash(tx_num) limit -= 1 - def get_balance(self, hash160): + def get_balance(self, hash168): '''Returns the confirmed balance of an address.''' - return sum(utxo.value for utxo in self.get_utxos(hash160, limit=None)) + return sum(utxo.value for utxo in self.get_utxos(hash168, limit=None)) - def get_utxos(self, hash160, limit=1000): + def get_utxos(self, hash168, limit=1000): '''Generator that yields all UTXOs for an address sorted in no particular order. By default yields at most 1000 entries. Set limit to None to get them all. ''' limit = self.resolve_limit(limit) unpack = struct.unpack - prefix = b'u' + hash160 + prefix = b'u' + hash168 utxos = [] for k, v in self.db.iterator(prefix=prefix): (tx_pos, ) = unpack('