Browse Source

Speed up script parsing for ~3% faster throughput

Also improves the coin abstraction
master
Neil Booth 8 years ago
parent
commit
3ab07c1fb6
  1. 45
      lib/coins.py
  2. 64
      lib/script.py
  3. 9
      lib/util.py
  4. 11
      server/block_processor.py

45
lib/coins.py

@ -17,9 +17,9 @@ import struct
import sys
from lib.hash import Base58, hash160, double_sha256, hash_to_str
from lib.script import ScriptPubKey
from lib.script import ScriptPubKey, Script
from lib.tx import Deserializer
from lib.util import subclasses
from lib.util import cachedproperty, subclasses
class CoinError(Exception):
@ -47,6 +47,19 @@ class Coin(object):
raise CoinError('unknown coin {} and network {} combination'
.format(name, net))
@cachedproperty
def hash168_handlers(cls):
return ScriptPubKey.PayToHandlers(
address = cls.P2PKH_hash168_from_hash160,
script_hash = cls.P2SH_hash168_from_hash160,
pubkey = cls.P2PKH_hash168_from_pubkey,
unknown = lambda x : None,
)
@classmethod
def hash168_from_script(cls, script):
return ScriptPubKey.pay_to(script, cls.hash168_handlers)
@staticmethod
def lookup_xverbytes(verbytes):
'''Return a (is_xpub, coin_class) pair given xpub/xprv verbytes.'''
@ -75,11 +88,18 @@ class Coin(object):
return Base58.encode_check(hash168)
@classmethod
def P2PKH_address_from_hash160(cls, hash_bytes):
def P2PKH_hash168_from_hash160(cls, hash160):
assert len(hash160) == 20
return bytes([cls.P2PKH_VERBYTE]) + hash160
@classmethod
def P2PKH_hash168_from_pubkey(cls, pubkey):
return cls.P2PKH_hash168_from_hash160(hash160(pubkey))
@classmethod
def P2PKH_address_from_hash160(cls, hash160):
'''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)
return Base58.encode_check(cls.P2PKH_hash168_from_hash160(hash160))
@classmethod
def P2PKH_address_from_pubkey(cls, pubkey):
@ -87,11 +107,14 @@ class Coin(object):
return cls.P2PKH_address_from_hash160(hash160(pubkey))
@classmethod
def P2SH_address_from_hash160(cls, pubkey_bytes):
'''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)
def P2SH_hash168_from_hash160(cls, hash160):
assert len(hash160) == 20
return bytes([cls.P2SH_VERBYTE]) + hash160
@classmethod
def P2SH_address_from_hash160(cls, hash160):
'''Return a coin address given a hash160.'''
return Base58.encode_check(cls.P2SH_hash168_from_hash160(hash160))
@classmethod
def multisig_address(cls, m, pubkeys):

64
lib/script.py

@ -8,8 +8,8 @@
'''Script-related classes and functions.'''
from binascii import hexlify
import struct
from collections import namedtuple
from lib.enum import Enumeration
from lib.hash import hash160
@ -133,60 +133,38 @@ class ScriptSig(object):
class ScriptPubKey(object):
'''A script from a tx output that gives conditions necessary for
spending.'''
'''A class for handling a tx output script that gives conditions
necessary for spending.
'''
TO_ADDRESS, TO_P2SH, TO_PUBKEY, TO_UNKNOWN = range(4)
TO_ADDRESS_OPS = [OpCodes.OP_DUP, OpCodes.OP_HASH160, -1,
OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG]
TO_P2SH_OPS = [OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL]
TO_PUBKEY_OPS = [-1, OpCodes.OP_CHECKSIG]
def __init__(self, script, coin, kind, hash168, pubkey=None):
self.script = script
self.coin = coin
self.kind = kind
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.hash168[1:])
if self.hash160:
return self.coin.P2PKH_address_from_hash160(self.hash168[1:])
return ''
@classmethod
def from_script(cls, script, coin):
'''Returns an instance of this class. Uncrecognised scripts return
an object of kind TO_UNKNOWN.'''
try:
return cls.parse_script(script, coin)
except ScriptError:
return cls(script, coin, cls.TO_UNKNOWN, None)
PayToHandlers = namedtuple('PayToHandlers',
'address script_hash pubkey unknown')
@classmethod
def parse_script(cls, script, coin):
'''Returns an instance of this class. Raises on unrecognised
scripts.'''
def pay_to(cls, script, handlers):
'''Parse a script, invoke the appropriate handler and
return the result.
One of the following handlers is invoked:
handlers.address(hash160)
handlers.script_hash(hash160)
handlers.pubkey(pubkey)
handlers.unknown(None)
'''
ops, datas = Script.get_ops(script)
if Script.match_ops(ops, cls.TO_ADDRESS_OPS):
return cls(script, coin, cls.TO_ADDRESS,
bytes([coin.P2PKH_VERBYTE]) + datas[2])
return handlers.address(datas[2])
if Script.match_ops(ops, cls.TO_P2SH_OPS):
return cls(script, coin, cls.TO_P2SH,
bytes([coin.P2SH_VERBYTE]) + datas[1])
return handlers.script_hash(datas[1])
if Script.match_ops(ops, cls.TO_PUBKEY_OPS):
pubkey = datas[0]
return cls(script, coin, cls.TO_PUBKEY,
bytes([coin.P2PKH_VERBYTE]) + hash160(pubkey), pubkey)
raise ScriptError('unknown script pubkey pattern')
return handlers.pubkey(datas[0])
return handlers.unknown(None)
@classmethod
def P2SH_script(cls, hash160):
@ -317,4 +295,4 @@ class Script(object):
print(name)
else:
print('{} {} ({:d} bytes)'
.format(name, hexlify(data).decode('ascii'), len(data)))
.format(name, data.hex(), len(data)))

9
lib/util.py

@ -31,16 +31,11 @@ class cachedproperty(object):
self.f = f
def __get__(self, obj, type):
if obj is None:
return self
obj = obj or type
value = self.f(obj)
obj.__dict__[self.f.__name__] = value
setattr(obj, self.f.__name__, value)
return value
def __set__(self, obj, value):
raise AttributeError('cannot set {} on {}'
.format(self.f.__name__, obj))
def deep_getsizeof(obj):
"""Find the memory footprint of a Python object.

11
server/block_processor.py

@ -20,7 +20,6 @@ from functools import partial
from server.cache import FSCache, UTXOCache, NO_CACHE_ENTRY
from server.daemon import DaemonError
from lib.hash import hash_to_str
from lib.script import ScriptPubKey
from lib.tx import Deserializer
from lib.util import chunks, LoggedClass
from server.storage import open_db
@ -205,12 +204,11 @@ class MemPool(LoggedClass):
# The mempool is unordered, so process all outputs first so
# that looking for inputs has full info.
parse_script = ScriptPubKey.from_script
coin = self.bp.coin
script_hash168 = self.bp.coin.hash168_from_script
utxo_lookup = self.bp.utxo_cache.lookup
def txout_pair(txout):
return (parse_script(txout.pk_script, coin).hash168, txout.value)
return (script_hash168(txout.pk_script), txout.value)
for hex_hash, tx in new_txs.items():
txout_pairs = tuple(txout_pair(txout) for txout in tx.outputs)
@ -745,8 +743,7 @@ class BlockProcessor(LoggedClass):
# Use local vars for speed in the loops
history = self.history
tx_num = self.tx_count
coin = self.coin
parse_script = ScriptPubKey.from_script
script_hash168 = self.coin.hash168_from_script
pack = struct.pack
for tx, tx_hash in zip(txs, tx_hashes):
@ -763,7 +760,7 @@ class BlockProcessor(LoggedClass):
# Add the new UTXOs
for idx, txout in enumerate(tx.outputs):
# Get the hash168. Ignore scripts we can't grok.
hash168 = parse_script(txout.pk_script, coin).hash168
hash168 = script_hash168(txout.pk_script)
if hash168:
hash168s.add(hash168)
put_utxo(tx_hash + pack('<H', idx),

Loading…
Cancel
Save