Browse Source

Share more code for name-prefix handling. (#873)

Namecoin and Emercoin both have special treatment for name prefixes on
scripts.  Through the NameMixin, they were already sharing some code for
that.  There was, however, still a lot of duplicated logic.

This refactors the code so that more logic is shared in the common
NameMixin.  In particular, there is now a general function for matching
a potential name prefix against the allowed structures of name operations.
Thus, the coin-specific code only has to define the valid structures of
name operations in the coin and handle coin-specific extra work (like
building the normalised script that is used for indexing names in
Namecoin).
patch-2
Daniel Kraft 6 years ago
committed by Neil
parent
commit
444dab8b25
  1. 132
      electrumx/lib/coins.py

132
electrumx/lib/coins.py

@ -40,7 +40,8 @@ from functools import partial
import electrumx.lib.util as util import electrumx.lib.util as util
from electrumx.lib.hash import Base58, hash160, double_sha256, hash_to_hex_str from electrumx.lib.hash import Base58, hash160, double_sha256, hash_to_hex_str
from electrumx.lib.hash import HASHX_LEN, hex_str_to_hash from electrumx.lib.hash import HASHX_LEN, hex_str_to_hash
from electrumx.lib.script import ScriptPubKey, OpCodes from electrumx.lib.script import (_match_ops, Script, ScriptError,
ScriptPubKey, OpCodes)
import electrumx.lib.tx as lib_tx import electrumx.lib.tx as lib_tx
import electrumx.lib.tx_dash as lib_tx_dash import electrumx.lib.tx_dash as lib_tx_dash
import electrumx.lib.tx_axe as lib_tx_axe import electrumx.lib.tx_axe as lib_tx_axe
@ -344,7 +345,10 @@ class NameMixin(object):
@staticmethod @staticmethod
def find_end_position_of_name(script, length): def find_end_position_of_name(script, length):
"""Find the end position of the name data""" """Finds the end position of the name data
Given the number of opcodes in the name prefix (length), returns the
index into the byte array of where the name prefix ends."""
n = 0 n = 0
for _i in range(length): for _i in range(length):
# Content of this loop is copied from Script.get_ops's loop # Content of this loop is copied from Script.get_ops's loop
@ -370,6 +374,56 @@ class NameMixin(object):
return n return n
@classmethod
def interpret_name_prefix(cls, script, possible_ops):
"""Interprets a potential name prefix
Checks if the given script has a name prefix. If it has, the
name prefix is split off the actual address script, and its parsed
fields (e.g. the name) returned.
possible_ops must be an array of arrays, defining the structures
of name prefixes to look out for. Each array can consist of
actual opcodes, -1 for ignored data placeholders and strings for
named placeholders. Whenever a data push matches a named placeholder,
the corresponding value is put into a dictionary the placeholder name
as key, and the dictionary of matches is returned."""
try:
ops = Script.get_ops(script)
except ScriptError:
return None, script
name_op_count = None
for pops in possible_ops:
n = len(pops)
# Start by translating named placeholders to -1 values, and
# keeping track of which op they corresponded to.
template = []
named_index = {}
for i in range(n):
if type(pops[i]) == str:
template.append(-1)
named_index[pops[i]] = i
else:
template.append(pops[i])
if not _match_ops(ops[:n], template):
continue
name_op_count = n
named_values = {key: ops[named_index[key]] for key in named_index}
break
if name_op_count is None:
return None, script
name_end_pos = cls.find_end_position_of_name(script, name_op_count)
address_script = script[name_end_pos:]
return named_values, address_script
class HOdlcoin(Coin): class HOdlcoin(Coin):
NAME = "HOdlcoin" NAME = "HOdlcoin"
@ -586,15 +640,6 @@ class Emercoin(NameMixin, Coin):
@classmethod @classmethod
def address_script_from_script(cls, script): def address_script_from_script(cls, script):
from electrumx.lib.script import _match_ops, Script, ScriptError
try:
ops = Script.get_ops(script)
except ScriptError:
return script
match = _match_ops
# Name opcodes # Name opcodes
OP_NAME_NEW = OpCodes.OP_1 OP_NAME_NEW = OpCodes.OP_1
OP_NAME_UPDATE = OpCodes.OP_2 OP_NAME_UPDATE = OpCodes.OP_2
@ -609,22 +654,13 @@ class Emercoin(NameMixin, Coin):
NAME_DELETE_OPS = [OP_NAME_DELETE, OpCodes.OP_DROP, -1, NAME_DELETE_OPS = [OP_NAME_DELETE, OpCodes.OP_DROP, -1,
OpCodes.OP_DROP] OpCodes.OP_DROP]
name_script_op_count = None ops = [
NAME_NEW_OPS,
# Detect name operations; determine count of opcodes. NAME_UPDATE_OPS,
for name_ops in [NAME_NEW_OPS, NAME_UPDATE_OPS, NAME_DELETE_OPS]: NAME_DELETE_OPS,
if match(ops[:len(name_ops)], name_ops): ]
name_script_op_count = len(name_ops)
break
if name_script_op_count is None:
return script
name_end_pos = cls.find_end_position_of_name(script, name_script_op_count)
# Strip the name data to yield the address script
address_script = script[name_end_pos:]
_, address_script = cls.interpret_name_prefix(script, ops)
return address_script return address_script
@ -966,49 +1002,27 @@ class Namecoin(NameMixin, AuxPowMixin, Coin):
@classmethod @classmethod
def split_name_script(cls, script): def split_name_script(cls, script):
from electrumx.lib.script import _match_ops, Script, ScriptError from electrumx.lib.script import Script
try:
ops = Script.get_ops(script)
except ScriptError:
return None, script
match = _match_ops
# Opcode sequences for name operations # Opcode sequences for name operations
NAME_NEW_OPS = [cls.OP_NAME_NEW, -1, OpCodes.OP_2DROP] NAME_NEW_OPS = [cls.OP_NAME_NEW, -1, OpCodes.OP_2DROP]
NAME_FIRSTUPDATE_OPS = [cls.OP_NAME_FIRSTUPDATE, -1, -1, -1, NAME_FIRSTUPDATE_OPS = [cls.OP_NAME_FIRSTUPDATE, "name", -1, -1,
OpCodes.OP_2DROP, OpCodes.OP_2DROP] OpCodes.OP_2DROP, OpCodes.OP_2DROP]
NAME_UPDATE_OPS = [cls.OP_NAME_UPDATE, -1, -1, OpCodes.OP_2DROP, NAME_UPDATE_OPS = [cls.OP_NAME_UPDATE, "name", -1, OpCodes.OP_2DROP,
OpCodes.OP_DROP] OpCodes.OP_DROP]
name_script_op_count = None ops = [
name_pushdata = None NAME_NEW_OPS,
NAME_FIRSTUPDATE_OPS,
# Detect name operations; determine count of opcodes. NAME_UPDATE_OPS,
# Also extract the name field -- we might use that for something in a ]
# future version.
if match(ops[:len(NAME_NEW_OPS)], NAME_NEW_OPS):
name_script_op_count = len(NAME_NEW_OPS)
elif match(ops[:len(NAME_FIRSTUPDATE_OPS)], NAME_FIRSTUPDATE_OPS):
name_script_op_count = len(NAME_FIRSTUPDATE_OPS)
name_pushdata = ops[1]
elif match(ops[:len(NAME_UPDATE_OPS)], NAME_UPDATE_OPS):
name_script_op_count = len(NAME_UPDATE_OPS)
name_pushdata = ops[1]
if name_script_op_count is None:
return None, script
name_end_pos = cls.find_end_position_of_name(script, name_script_op_count) named_values, address_script = cls.interpret_name_prefix(script, ops)
# Strip the name data to yield the address script
address_script = script[name_end_pos:]
if name_pushdata is None: if named_values is None or "name" not in named_values:
return None, address_script return None, address_script
normalized_name_op_script = cls.build_name_index_script(name_pushdata[1]) normalized_name_op_script = cls.build_name_index_script(named_values["name"][1])
return bytes(normalized_name_op_script), address_script return bytes(normalized_name_op_script), address_script
@classmethod @classmethod

Loading…
Cancel
Save