Speed up script parsing for ~3% faster throughput
Also improves the coin abstraction
This commit is contained in:
parent
3d41738a00
commit
3ab07c1fb6
45
lib/coins.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):
|
||||
|
||||
@ -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 ''
|
||||
PayToHandlers = namedtuple('PayToHandlers',
|
||||
'address script_hash pubkey unknown')
|
||||
|
||||
@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)
|
||||
def pay_to(cls, script, handlers):
|
||||
'''Parse a script, invoke the appropriate handler and
|
||||
return the result.
|
||||
|
||||
@classmethod
|
||||
def parse_script(cls, script, coin):
|
||||
'''Returns an instance of this class. Raises on unrecognised
|
||||
scripts.'''
|
||||
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)))
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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…
Reference in New Issue
Block a user