Browse Source

Fix Emercoin nvs values parse (#899)

* Fix nvs values parse for Emercoin

* Add tests
patch-2
yakimka 6 years ago
committed by Neil
parent
commit
d3067278df
  1. 159
      electrumx/lib/coins.py
  2. 98
      tests/lib/test_coins.py
  3. 52
      tests/transactions/emercoin_mainnet_227829.json

159
electrumx/lib/coins.py

@ -344,37 +344,7 @@ class BitcoinMixin(object):
class NameMixin(object):
@staticmethod
def find_end_position_of_name(script, length):
"""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
for _i in range(length):
# Content of this loop is copied from Script.get_ops's loop
op = script[n]
n += 1
if op <= OpCodes.OP_PUSHDATA4:
# Raw bytes follow
if op < OpCodes.OP_PUSHDATA1:
dlen = op
elif op == OpCodes.OP_PUSHDATA1:
dlen = script[n]
n += 1
elif op == OpCodes.OP_PUSHDATA2:
dlen, = struct.unpack('<H', script[n: n + 2])
n += 2
else:
dlen, = struct.unpack('<I', script[n: n + 4])
n += 4
if n + dlen > len(script):
raise IndexError
n += dlen
return n
DATA_PUSH_MULTIPLE = -2
@classmethod
def interpret_name_prefix(cls, script, possible_ops):
@ -386,8 +356,9 @@ class NameMixin(object):
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,
actual opcodes, -1 for ignored data placeholders, -2 for
multiple 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."""
@ -398,18 +369,25 @@ class NameMixin(object):
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:
n = len(pops)
offset = 0
for i, op in enumerate(pops):
if op == cls.DATA_PUSH_MULTIPLE:
# Emercoin stores value in multiple placeholders
# Script structure: https://git.io/fjuRu
added, template = cls._add_data_placeholders_to_template(ops[i:], template)
offset += added - 1 # subtract the "DATA_PUSH_MULTIPLE" opcode
elif type(op) == str:
template.append(-1)
named_index[pops[i]] = i
named_index[op] = i + offset
else:
template.append(pops[i])
template.append(op)
n += offset
if not _match_ops(ops[:n], template):
continue
@ -426,6 +404,64 @@ class NameMixin(object):
address_script = script[name_end_pos:]
return named_values, address_script
@classmethod
def _add_data_placeholders_to_template(cls, opcodes, template):
num_dp = cls._read_data_placeholders_count(opcodes)
num_2drop = num_dp // 2
num_drop = num_dp % 2
two_drops = [OpCodes.OP_2DROP for _ in range(num_2drop)]
one_drops = [OpCodes.OP_DROP for _ in range(num_drop)]
elements_added = num_dp + num_2drop + num_drop
placeholders = [-1 for _ in range(num_dp)]
drops = two_drops + one_drops
return elements_added, template + placeholders + drops
@classmethod
def _read_data_placeholders_count(cls, opcodes):
data_placeholders = 0
for opcode in opcodes:
if type(opcode) == tuple:
data_placeholders += 1
else:
break
return data_placeholders
@staticmethod
def find_end_position_of_name(script, length):
"""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
for _i in range(length):
# Content of this loop is copied from Script.get_ops's loop
op = script[n]
n += 1
if op <= OpCodes.OP_PUSHDATA4:
# Raw bytes follow
if op < OpCodes.OP_PUSHDATA1:
dlen = op
elif op == OpCodes.OP_PUSHDATA1:
dlen = script[n]
n += 1
elif op == OpCodes.OP_PUSHDATA2:
dlen, = struct.unpack('<H', script[n: n + 2])
n += 2
else:
dlen, = struct.unpack('<I', script[n: n + 4])
n += 4
if n + dlen > len(script):
raise IndexError
n += dlen
return n
class NameIndexMixin(NameMixin):
"""Shared definitions for coins that have a name index
@ -672,6 +708,24 @@ class Emercoin(NameMixin, Coin):
PEERS = []
# Name opcodes
OP_NAME_NEW = OpCodes.OP_1
OP_NAME_UPDATE = OpCodes.OP_2
OP_NAME_DELETE = OpCodes.OP_3
# Valid name prefixes.
NAME_NEW_OPS = [OP_NAME_NEW, OpCodes.OP_DROP, "name", "days",
OpCodes.OP_2DROP, NameMixin.DATA_PUSH_MULTIPLE]
NAME_UPDATE_OPS = [OP_NAME_UPDATE, OpCodes.OP_DROP, "name", "days",
OpCodes.OP_2DROP, NameMixin.DATA_PUSH_MULTIPLE]
NAME_DELETE_OPS = [OP_NAME_DELETE, OpCodes.OP_DROP, "name",
OpCodes.OP_DROP]
NAME_OPERATIONS = [
NAME_NEW_OPS,
NAME_UPDATE_OPS,
NAME_DELETE_OPS,
]
@classmethod
def block_header(cls, block, height):
'''Returns the block header given a block and its height.'''
@ -688,35 +742,10 @@ class Emercoin(NameMixin, Coin):
@classmethod
def hashX_from_script(cls, script):
address_script = cls.address_script_from_script(script)
_, address_script = cls.interpret_name_prefix(script, cls.NAME_OPERATIONS)
return super().hashX_from_script(address_script)
@classmethod
def address_script_from_script(cls, script):
# Name opcodes
OP_NAME_NEW = OpCodes.OP_1
OP_NAME_UPDATE = OpCodes.OP_2
OP_NAME_DELETE = OpCodes.OP_3
# Opcode sequences for name operations
# Script structure: https://git.io/fjuRu
NAME_NEW_OPS = [OP_NAME_NEW, OpCodes.OP_DROP, -1, -1,
OpCodes.OP_2DROP, -1, OpCodes.OP_DROP]
NAME_UPDATE_OPS = [OP_NAME_UPDATE, OpCodes.OP_DROP, -1, -1,
OpCodes.OP_2DROP, -1, OpCodes.OP_DROP]
NAME_DELETE_OPS = [OP_NAME_DELETE, OpCodes.OP_DROP, -1,
OpCodes.OP_DROP]
ops = [
NAME_NEW_OPS,
NAME_UPDATE_OPS,
NAME_DELETE_OPS,
]
_, address_script = cls.interpret_name_prefix(script, ops)
return address_script
class BitcoinTestnetMixin(object):
SHORTNAME = "XTN"

98
tests/lib/test_coins.py

@ -2,12 +2,12 @@
import pytest
from electrumx.lib.coins import BitcoinSV
from electrumx.lib.script import OpCodes
from electrumx.lib.coins import BitcoinSV, NameMixin
from electrumx.lib.script import OpCodes, Script
coin = BitcoinSV
@pytest.mark.parametrize("script", (
bytes([OpCodes.OP_RETURN]),
bytes([OpCodes.OP_RETURN]) + bytes([2, 28, 50]),
@ -27,3 +27,95 @@ def test_op_return(script):
))
def test_not_op_return(script):
assert coin.hashX_from_script(script) is not None
NAME = "name".encode("ascii")
DAYS = hex(6).encode("ascii")
VALUE = "value".encode("ascii")
ADDRESS_SCRIPT = "address_script".encode("ascii")
OP_NAME_NEW = OpCodes.OP_1
OP_NAME_UPDATE = OpCodes.OP_2
OP_DROP = OpCodes.OP_DROP
OP_2DROP = OpCodes.OP_2DROP
DP_MULT = NameMixin.DATA_PUSH_MULTIPLE
def create_script(pattern, address_script):
script = bytearray()
for item in pattern:
if type(item) == int:
script.append(item)
else:
script.extend(Script.push_data(item))
script.extend(address_script)
return bytes(script)
@pytest.mark.parametrize("opcode,pattern", (
([OP_NAME_NEW, OP_DROP, -1, -1, OP_2DROP, -1, OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP, VALUE, OP_DROP]),
([OP_NAME_NEW, OP_DROP, -1, -1, OP_2DROP, DP_MULT],
[OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP, VALUE, OP_DROP]),
([OP_NAME_NEW, OP_DROP, -1, -1, OP_2DROP, DP_MULT],
[OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP, VALUE, VALUE, OP_2DROP]),
([OP_NAME_NEW, OP_DROP, -1, OP_2DROP, DP_MULT, -1, OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, OP_2DROP, VALUE, OP_DROP, DAYS, OP_DROP]),
([OP_NAME_NEW, OP_DROP, -1, OP_2DROP, DP_MULT, -1, OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, OP_2DROP, VALUE, VALUE, OP_2DROP, DAYS, OP_DROP]),
))
def test_name_mixin_interpret_name_prefix(opcode, pattern):
ops = [opcode]
script = create_script(pattern, ADDRESS_SCRIPT)
parsed_names, parsed_address_script = NameMixin.interpret_name_prefix(script, ops)
assert len(parsed_names) == 0
assert parsed_address_script == ADDRESS_SCRIPT
@pytest.mark.parametrize("opcode,pattern", (
([OP_NAME_NEW, OP_DROP, "name", "days", OP_2DROP, -1, OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP, VALUE, OP_DROP]),
([OP_NAME_NEW, OP_DROP, "name", OP_DROP, -1, OP_DROP, "days", OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, OP_DROP, VALUE, OP_DROP, DAYS, OP_DROP]),
([OP_NAME_NEW, OP_DROP, "name", "days", OP_2DROP, DP_MULT],
[OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP, VALUE, OP_DROP]),
([OP_NAME_NEW, OP_DROP, "name", "days", OP_2DROP, DP_MULT],
[OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP, VALUE, VALUE, OP_2DROP]),
([OP_NAME_NEW, OP_DROP, "name", "days", OP_2DROP, DP_MULT],
[OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP, VALUE, VALUE, VALUE, OP_2DROP, OP_DROP]),
([OP_NAME_NEW, OP_DROP, "name", OP_2DROP, DP_MULT, "days", OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, OP_2DROP, VALUE, OP_DROP, DAYS, OP_DROP]),
([OP_NAME_NEW, OP_DROP, "name", OP_2DROP, DP_MULT, "days", OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, OP_2DROP, VALUE, VALUE, OP_2DROP, DAYS, OP_DROP]),
([OP_NAME_NEW, OP_DROP, "name", OP_2DROP, DP_MULT, "days", OP_DROP],
[OP_NAME_NEW, OP_DROP, NAME, OP_2DROP, VALUE, VALUE, VALUE, OP_2DROP, OP_DROP, DAYS, OP_DROP]),
))
def test_name_mixin_interpret_name_prefix_with_named_placeholders(opcode, pattern):
ops = [opcode]
script = create_script(pattern, ADDRESS_SCRIPT)
parsed_names, parsed_address_script = NameMixin.interpret_name_prefix(script, ops)
assert parsed_names["name"][1] == NAME
assert parsed_names["days"][1] == DAYS
assert parsed_address_script == ADDRESS_SCRIPT
@pytest.mark.parametrize("opcode", (
[OP_NAME_UPDATE, OP_DROP, -1, -1, OP_2DROP, -1, OP_DROP],
[OP_NAME_NEW, OP_DROP, -1, -1, OP_DROP, OP_DROP, -1, OP_DROP],
[OP_NAME_NEW, OP_DROP, "name", "days", OP_DROP, -1, OP_DROP],
))
def test_name_mixin_interpret_name_prefix_wrong_ops(opcode):
ops = [opcode]
script = create_script([OP_NAME_NEW, OP_DROP, NAME, DAYS, OP_2DROP,
VALUE, OP_DROP], ADDRESS_SCRIPT)
parsed_names, parsed_address_script = NameMixin.interpret_name_prefix(script, ops)
assert parsed_names is None
assert parsed_address_script == script

52
tests/transactions/emercoin_mainnet_227829.json

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save