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
|
import sys
|
||||||
|
|
||||||
from lib.hash import Base58, hash160, double_sha256, hash_to_str
|
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.tx import Deserializer
|
||||||
from lib.util import subclasses
|
from lib.util import cachedproperty, subclasses
|
||||||
|
|
||||||
|
|
||||||
class CoinError(Exception):
|
class CoinError(Exception):
|
||||||
@ -47,6 +47,19 @@ class Coin(object):
|
|||||||
raise CoinError('unknown coin {} and network {} combination'
|
raise CoinError('unknown coin {} and network {} combination'
|
||||||
.format(name, net))
|
.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
|
@staticmethod
|
||||||
def lookup_xverbytes(verbytes):
|
def lookup_xverbytes(verbytes):
|
||||||
'''Return a (is_xpub, coin_class) pair given xpub/xprv verbytes.'''
|
'''Return a (is_xpub, coin_class) pair given xpub/xprv verbytes.'''
|
||||||
@ -75,11 +88,18 @@ class Coin(object):
|
|||||||
return Base58.encode_check(hash168)
|
return Base58.encode_check(hash168)
|
||||||
|
|
||||||
@classmethod
|
@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.'''
|
'''Return a P2PKH address given a public key.'''
|
||||||
assert len(hash_bytes) == 20
|
return Base58.encode_check(cls.P2PKH_hash168_from_hash160(hash160))
|
||||||
payload = bytes([cls.P2PKH_VERBYTE]) + hash_bytes
|
|
||||||
return Base58.encode_check(payload)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def P2PKH_address_from_pubkey(cls, pubkey):
|
def P2PKH_address_from_pubkey(cls, pubkey):
|
||||||
@ -87,11 +107,14 @@ class Coin(object):
|
|||||||
return cls.P2PKH_address_from_hash160(hash160(pubkey))
|
return cls.P2PKH_address_from_hash160(hash160(pubkey))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def P2SH_address_from_hash160(cls, pubkey_bytes):
|
def P2SH_hash168_from_hash160(cls, hash160):
|
||||||
'''Return a coin address given a public key.'''
|
assert len(hash160) == 20
|
||||||
assert len(hash_bytes) == 20
|
return bytes([cls.P2SH_VERBYTE]) + hash160
|
||||||
payload = bytes([cls.P2SH_VERBYTE]) + hash_bytes
|
|
||||||
return Base58.encode_check(payload)
|
@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
|
@classmethod
|
||||||
def multisig_address(cls, m, pubkeys):
|
def multisig_address(cls, m, pubkeys):
|
||||||
|
|||||||
@ -8,8 +8,8 @@
|
|||||||
'''Script-related classes and functions.'''
|
'''Script-related classes and functions.'''
|
||||||
|
|
||||||
|
|
||||||
from binascii import hexlify
|
|
||||||
import struct
|
import struct
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
from lib.enum import Enumeration
|
from lib.enum import Enumeration
|
||||||
from lib.hash import hash160
|
from lib.hash import hash160
|
||||||
@ -133,60 +133,38 @@ class ScriptSig(object):
|
|||||||
|
|
||||||
|
|
||||||
class ScriptPubKey(object):
|
class ScriptPubKey(object):
|
||||||
'''A script from a tx output that gives conditions necessary for
|
'''A class for handling a tx output script that gives conditions
|
||||||
spending.'''
|
necessary for spending.
|
||||||
|
'''
|
||||||
|
|
||||||
TO_ADDRESS, TO_P2SH, TO_PUBKEY, TO_UNKNOWN = range(4)
|
|
||||||
TO_ADDRESS_OPS = [OpCodes.OP_DUP, OpCodes.OP_HASH160, -1,
|
TO_ADDRESS_OPS = [OpCodes.OP_DUP, OpCodes.OP_HASH160, -1,
|
||||||
OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG]
|
OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG]
|
||||||
TO_P2SH_OPS = [OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL]
|
TO_P2SH_OPS = [OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL]
|
||||||
TO_PUBKEY_OPS = [-1, OpCodes.OP_CHECKSIG]
|
TO_PUBKEY_OPS = [-1, OpCodes.OP_CHECKSIG]
|
||||||
|
|
||||||
def __init__(self, script, coin, kind, hash168, pubkey=None):
|
PayToHandlers = namedtuple('PayToHandlers',
|
||||||
self.script = script
|
'address script_hash pubkey unknown')
|
||||||
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
|
@classmethod
|
||||||
def from_script(cls, script, coin):
|
def pay_to(cls, script, handlers):
|
||||||
'''Returns an instance of this class. Uncrecognised scripts return
|
'''Parse a script, invoke the appropriate handler and
|
||||||
an object of kind TO_UNKNOWN.'''
|
return the result.
|
||||||
try:
|
|
||||||
return cls.parse_script(script, coin)
|
|
||||||
except ScriptError:
|
|
||||||
return cls(script, coin, cls.TO_UNKNOWN, None)
|
|
||||||
|
|
||||||
@classmethod
|
One of the following handlers is invoked:
|
||||||
def parse_script(cls, script, coin):
|
handlers.address(hash160)
|
||||||
'''Returns an instance of this class. Raises on unrecognised
|
handlers.script_hash(hash160)
|
||||||
scripts.'''
|
handlers.pubkey(pubkey)
|
||||||
|
handlers.unknown(None)
|
||||||
|
'''
|
||||||
ops, datas = Script.get_ops(script)
|
ops, datas = Script.get_ops(script)
|
||||||
|
|
||||||
if Script.match_ops(ops, cls.TO_ADDRESS_OPS):
|
if Script.match_ops(ops, cls.TO_ADDRESS_OPS):
|
||||||
return cls(script, coin, cls.TO_ADDRESS,
|
return handlers.address(datas[2])
|
||||||
bytes([coin.P2PKH_VERBYTE]) + datas[2])
|
|
||||||
|
|
||||||
if Script.match_ops(ops, cls.TO_P2SH_OPS):
|
if Script.match_ops(ops, cls.TO_P2SH_OPS):
|
||||||
return cls(script, coin, cls.TO_P2SH,
|
return handlers.script_hash(datas[1])
|
||||||
bytes([coin.P2SH_VERBYTE]) + datas[1])
|
|
||||||
|
|
||||||
if Script.match_ops(ops, cls.TO_PUBKEY_OPS):
|
if Script.match_ops(ops, cls.TO_PUBKEY_OPS):
|
||||||
pubkey = datas[0]
|
return handlers.pubkey(datas[0])
|
||||||
return cls(script, coin, cls.TO_PUBKEY,
|
return handlers.unknown(None)
|
||||||
bytes([coin.P2PKH_VERBYTE]) + hash160(pubkey), pubkey)
|
|
||||||
|
|
||||||
raise ScriptError('unknown script pubkey pattern')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def P2SH_script(cls, hash160):
|
def P2SH_script(cls, hash160):
|
||||||
@ -317,4 +295,4 @@ class Script(object):
|
|||||||
print(name)
|
print(name)
|
||||||
else:
|
else:
|
||||||
print('{} {} ({:d} bytes)'
|
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
|
self.f = f
|
||||||
|
|
||||||
def __get__(self, obj, type):
|
def __get__(self, obj, type):
|
||||||
if obj is None:
|
obj = obj or type
|
||||||
return self
|
|
||||||
value = self.f(obj)
|
value = self.f(obj)
|
||||||
obj.__dict__[self.f.__name__] = value
|
setattr(obj, self.f.__name__, value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
|
||||||
raise AttributeError('cannot set {} on {}'
|
|
||||||
.format(self.f.__name__, obj))
|
|
||||||
|
|
||||||
|
|
||||||
def deep_getsizeof(obj):
|
def deep_getsizeof(obj):
|
||||||
"""Find the memory footprint of a Python object.
|
"""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.cache import FSCache, UTXOCache, NO_CACHE_ENTRY
|
||||||
from server.daemon import DaemonError
|
from server.daemon import DaemonError
|
||||||
from lib.hash import hash_to_str
|
from lib.hash import hash_to_str
|
||||||
from lib.script import ScriptPubKey
|
|
||||||
from lib.tx import Deserializer
|
from lib.tx import Deserializer
|
||||||
from lib.util import chunks, LoggedClass
|
from lib.util import chunks, LoggedClass
|
||||||
from server.storage import open_db
|
from server.storage import open_db
|
||||||
@ -205,12 +204,11 @@ class MemPool(LoggedClass):
|
|||||||
|
|
||||||
# The mempool is unordered, so process all outputs first so
|
# The mempool is unordered, so process all outputs first so
|
||||||
# that looking for inputs has full info.
|
# that looking for inputs has full info.
|
||||||
parse_script = ScriptPubKey.from_script
|
script_hash168 = self.bp.coin.hash168_from_script
|
||||||
coin = self.bp.coin
|
|
||||||
utxo_lookup = self.bp.utxo_cache.lookup
|
utxo_lookup = self.bp.utxo_cache.lookup
|
||||||
|
|
||||||
def txout_pair(txout):
|
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():
|
for hex_hash, tx in new_txs.items():
|
||||||
txout_pairs = tuple(txout_pair(txout) for txout in tx.outputs)
|
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
|
# Use local vars for speed in the loops
|
||||||
history = self.history
|
history = self.history
|
||||||
tx_num = self.tx_count
|
tx_num = self.tx_count
|
||||||
coin = self.coin
|
script_hash168 = self.coin.hash168_from_script
|
||||||
parse_script = ScriptPubKey.from_script
|
|
||||||
pack = struct.pack
|
pack = struct.pack
|
||||||
|
|
||||||
for tx, tx_hash in zip(txs, tx_hashes):
|
for tx, tx_hash in zip(txs, tx_hashes):
|
||||||
@ -763,7 +760,7 @@ class BlockProcessor(LoggedClass):
|
|||||||
# Add the new UTXOs
|
# Add the new UTXOs
|
||||||
for idx, txout in enumerate(tx.outputs):
|
for idx, txout in enumerate(tx.outputs):
|
||||||
# Get the hash168. Ignore scripts we can't grok.
|
# 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:
|
if hash168:
|
||||||
hash168s.add(hash168)
|
hash168s.add(hash168)
|
||||||
put_utxo(tx_hash + pack('<H', idx),
|
put_utxo(tx_hash + pack('<H', idx),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user