From 85786b87a2e32972fe42ffe8f088d68f731ba2b3 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Sun, 13 Nov 2016 19:17:50 +0900 Subject: [PATCH] Save all UTXOs Change the DB version --- lib/coins.py | 19 ++++++++- lib/script.py | 84 ++++++++++++++++++++------------------ server/block_processor.py | 86 +++++++++++++++++---------------------- server/db.py | 2 +- 4 files changed, 100 insertions(+), 91 deletions(-) diff --git a/lib/coins.py b/lib/coins.py index b9b9688..cd0bbb2 100644 --- a/lib/coins.py +++ b/lib/coins.py @@ -12,6 +12,7 @@ necessary for appropriate handling. ''' from decimal import Decimal +from functools import partial import inspect import struct import sys @@ -34,6 +35,7 @@ class Coin(object): DEFAULT_RPC_PORT = 8332 VALUE_PER_COIN = 100000000 CHUNK_SIZE=2016 + STRANGE_VERBYTE = 0xff @classmethod def lookup_coin_class(cls, name, net): @@ -53,11 +55,14 @@ class Coin(object): address = cls.P2PKH_hash168_from_hash160, script_hash = cls.P2SH_hash168_from_hash160, pubkey = cls.P2PKH_hash168_from_pubkey, + unspendable = cls.hash168_from_unspendable, + strange = cls.hash168_from_strange, ) @classmethod - def hash168_from_script(cls, script): - return ScriptPubKey.pay_to(script, cls.hash168_handlers) + def hash168_from_script(cls): + '''Returns a function that is passed a script to return a hash168.''' + return partial(ScriptPubKey.pay_to, cls.hash168_handlers) @staticmethod def lookup_xverbytes(verbytes): @@ -86,6 +91,16 @@ class Coin(object): '''Return an address given a 21-byte hash.''' return Base58.encode_check(hash168) + @classmethod + def hash168_from_unspendable(cls): + '''Return a hash168 for an unspendable script.''' + return None + + @classmethod + def hash168_from_strange(cls, script): + '''Return a hash168 for a strange script.''' + return bytes([cls.STRANGE_VERBYTE]) + hash160(script) + @classmethod def P2PKH_hash168_from_hash160(cls, hash160): '''Return a hash168 if hash160 is 160 bits otherwise None.''' diff --git a/lib/script.py b/lib/script.py index 98c8fef..e73bf56 100644 --- a/lib/script.py +++ b/lib/script.py @@ -56,6 +56,19 @@ assert OpCodes.OP_CHECKSIG == 0xac assert OpCodes.OP_CHECKMULTISIG == 0xae +def _match_ops(ops, pattern): + if len(ops) != len(pattern): + return False + for op, pop in zip(ops, pattern): + if pop != op: + # -1 means 'data push', whose op is an (op, data) tuple + if pop == -1 and isinstance(op, tuple): + continue + return False + + return True + + class ScriptPubKey(object): '''A class for handling a tx output script that gives conditions necessary for spending. @@ -66,10 +79,11 @@ class ScriptPubKey(object): TO_P2SH_OPS = [OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL] TO_PUBKEY_OPS = [-1, OpCodes.OP_CHECKSIG] - PayToHandlers = namedtuple('PayToHandlers', 'address script_hash pubkey') + PayToHandlers = namedtuple('PayToHandlers', 'address script_hash pubkey ' + 'unspendable strange') @classmethod - def pay_to(cls, script, handlers): + def pay_to(cls, handlers, script): '''Parse a script, invoke the appropriate handler and return the result. @@ -77,21 +91,25 @@ class ScriptPubKey(object): handlers.address(hash160) handlers.script_hash(hash160) handlers.pubkey(pubkey) - or None is returned if the script is invalid or unregonised. + handlers.unspendable() + handlers.strange(script) ''' try: - ops, datas = Script.get_ops(script) + ops = Script.get_ops(script) except ScriptError: - return None + return handlers.unspendable() - if Script.match_ops(ops, cls.TO_ADDRESS_OPS): - return handlers.address(datas[2]) - if Script.match_ops(ops, cls.TO_P2SH_OPS): - return handlers.script_hash(datas[1]) - if Script.match_ops(ops, cls.TO_PUBKEY_OPS): - return handlers.pubkey(datas[0]) + match = _match_ops - return None + if match(ops, cls.TO_ADDRESS_OPS): + return handlers.address(ops[2][-1]) + if match(ops, cls.TO_P2SH_OPS): + return handlers.script_hash(ops[1][-1]) + if match(ops, cls.TO_PUBKEY_OPS): + return handlers.pubkey(ops[0][-1]) + if OpCodes.OP_RETURN in ops: + return handlers.unspendable() + return handlers.strange(script) @classmethod def P2SH_script(cls, hash160): @@ -141,54 +159,40 @@ class Script(object): @classmethod def get_ops(cls, script): - opcodes, datas = [], [] + ops = [] # The unpacks or script[n] below throw on truncated scripts try: n = 0 while n < len(script): - opcode, data = script[n], None + op = script[n] n += 1 - if opcode <= OpCodes.OP_PUSHDATA4: + if op <= OpCodes.OP_PUSHDATA4: # Raw bytes follow - if opcode < OpCodes.OP_PUSHDATA1: - dlen = opcode - elif opcode == OpCodes.OP_PUSHDATA1: + if op < OpCodes.OP_PUSHDATA1: + dlen = op + elif op == OpCodes.OP_PUSHDATA1: dlen = script[n] n += 1 - elif opcode == OpCodes.OP_PUSHDATA2: - (dlen,) = struct.unpack(' len(script): + raise IndexError + op = (op, script[n:n + dlen]) n += dlen - opcodes.append(opcode) - datas.append(data) + ops.append(op) except: # Truncated script; e.g. tx_hash # ebc9fa1196a59e192352d76c0f6e73167046b9d37b8302b6bb6968dfd279b767 raise ScriptError('truncated script') - return opcodes, datas - - @classmethod - def match_ops(cls, ops, pattern): - if len(ops) != len(pattern): - return False - for op, pop in zip(ops, pattern): - if pop != op: - # -1 Indicates data push expected - if pop == -1 and OpCodes.OP_0 <= op <= OpCodes.OP_PUSHDATA4: - continue - return False - - return True + return ops @classmethod def push_data(cls, data): diff --git a/server/block_processor.py b/server/block_processor.py index ad12bb5..ad98888 100644 --- a/server/block_processor.py +++ b/server/block_processor.py @@ -28,8 +28,6 @@ from server.storage import open_db # Limits single address history to ~ 65536 * HIST_ENTRIES_PER_KEY entries HIST_ENTRIES_PER_KEY = 1024 HIST_VALUE_BYTES = HIST_ENTRIES_PER_KEY * 4 -NO_HASH_168 = bytes([255]) * 21 -NO_CACHE_ENTRY = NO_HASH_168 + bytes(12) def formatted_time(t): @@ -209,7 +207,7 @@ class MemPool(LoggedClass): # The mempool is unordered, so process all outputs first so # that looking for inputs has full info. - script_hash168 = self.bp.coin.hash168_from_script + script_hash168 = self.bp.coin.hash168_from_script() db_utxo_lookup = self.bp.db_utxo_lookup def txout_pair(txout): @@ -658,8 +656,6 @@ class BlockProcessor(server.db.DB): self.logger.info('backing up history to height {:,d} tx_count {:,d}' .format(self.height, self.tx_count)) - # Drop any NO_CACHE entry - hash168s.discard(NO_CACHE_ENTRY) assert not self.history nremoves = 0 @@ -765,7 +761,7 @@ class BlockProcessor(server.db.DB): # Use local vars for speed in the loops history = self.history tx_num = self.tx_count - script_hash168 = self.coin.hash168_from_script + script_hash168 = self.coin.hash168_from_script() s_pack = pack for tx, tx_hash in zip(txs, tx_hashes): @@ -781,15 +777,13 @@ class BlockProcessor(server.db.DB): # Add the new UTXOs for idx, txout in enumerate(tx.outputs): - # Get the hash168. Ignore scripts we can't grok. + # Get the hash168. Ignore unspendable outputs hash168 = script_hash168(txout.pk_script) if hash168: hash168s.add(hash168) put_utxo(tx_hash + s_pack('