Skip to content

Commit

Permalink
Merge pull request #223 from jannikluhn/new-tx-format
Browse files Browse the repository at this point in the history
New Transaction Format
  • Loading branch information
jannikluhn authored Dec 16, 2017
2 parents 395c8d4 + 31e8e4c commit 0b9e4fb
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 82 deletions.
41 changes: 41 additions & 0 deletions evm/rlp/sedes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
from rlp.sedes import (
BigEndianInt,
Binary,
CountableList,
)
from rlp.exceptions import (
ListSerializationError,
ListDeserializationError,
)


class AccessListElement(CountableList):

def __init__(self):
super().__init__(Binary(max_length=32))

def serialize(self, obj):
result = super().serialize(obj)
if not obj:
raise ListSerializationError(
"Access list elements need to specify at least an address"
)
elif len(obj[0]) != 20:
raise ListSerializationError(
"Access list elements need to start with a 20 byte address (got {0} bytes)".format(
len(obj[0])
)
)
return result

def deserialize(self, serial):
result = super().deserialize(serial)
if not result:
raise ListDeserializationError(
"Access list elements need to specify at least an address"
)
elif len(result[0]) != 20:
raise ListDeserializationError(
"Access list elements need to start with a 20 byte address (got {0} bytes)".format(
len(result[0])
)
)
return result


address = Binary.fixed_length(20, allow_empty=True)
hash32 = Binary.fixed_length(32)
int32 = BigEndianInt(32)
int256 = BigEndianInt(256)
trie_root = Binary.fixed_length(32, allow_empty=True)
access_list_element = AccessListElement()
access_list = CountableList(access_list_element)
18 changes: 18 additions & 0 deletions evm/rlp/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from evm.rlp.sedes import (
address,
access_list as access_list_sedes,
)
from evm.utils.keccak import (
keccak,
Expand Down Expand Up @@ -142,3 +143,20 @@ def as_signed_transaction(self, private_key):
provided `private_key`
"""
raise NotImplementedError("Must be implemented by subclasses")


class BaseShardingTransaction(rlp.Serializable):
fields = [
('chain_id', big_endian_int),
('shard_id', big_endian_int),
('target', address),
('data', binary),
('start_gas', big_endian_int),
('gas_price', big_endian_int),
('access_list', access_list_sedes),
('code', binary),
]

@property
def hash(self):
return keccak(rlp.encode(self))
15 changes: 9 additions & 6 deletions evm/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,18 +229,21 @@ def validate_header_params_for_configuration(header_params):
)


def validate_transaction_access_list(access_list):
def validate_transaction_access_list(access_list, title="Access List"):
for item in access_list:
if len(item) == 0:
raise ValidationError("Access list entry must at least specify an account address.")
raise ValidationError(
"{0} entry must at least specify an account address.".format(title)
)
address, *prefixes = item
validate_canonical_address(address, title="Access list address")
validate_canonical_address(address, title="Address in {0}".format(title))
for prefix in prefixes:
validate_is_bytes(prefix, title="Access list storage prefix")
validate_is_bytes(prefix, title="Storage prefix in {0}".format(title))
if len(prefix) > 32:
raise ValidationError(
"Access list storage prefix must be 32 bytes or shorter. Got: {}".format(
prefix
"Storage prefix in {0} must be 32 bytes or shorter. Got: {1}".format(
title,
prefix,
)
)

Expand Down
29 changes: 29 additions & 0 deletions evm/vm/forks/sharding/transactions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from evm.validation import (
validate_uint256,
validate_is_bytes,
validate_canonical_address,
validate_transaction_access_list,
)

from evm.rlp.transactions import (
BaseShardingTransaction,
)


class ShardingTransaction(BaseShardingTransaction):

def validate(self):
validate_uint256(self.chain_id, title="Transaction.chain_id")
validate_uint256(self.shard_id, title="Transaction.shard_id")

validate_canonical_address(self.target, title="Transaction.target")
validate_is_bytes(self.data, title="Transaction.data")

validate_uint256(self.start_gas, title="Transaction.start_gas")
validate_uint256(self.gas_price, title="Transaction.gas_price")

validate_transaction_access_list(self.access_list, title="Transaction.access_list")

validate_is_bytes(self.code, title="Transaction.code")

super().validate()
166 changes: 166 additions & 0 deletions tests/core/access-restrictions-utils/test_access_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import pytest

import rlp
from rlp.exceptions import (
ListSerializationError,
ListDeserializationError,
)

from evm.constants import (
STORAGE_TRIE_PREFIX,
)
from evm.validation import (
validate_transaction_access_list,
)

from evm.rlp.sedes import (
access_list as access_list_sedes,
)
from evm.utils.keccak import (
keccak,
)
from evm.utils.state_access_restriction import (
is_accessible,
remove_redundant_prefixes,
to_prefix_list_form,
)


TEST_ADDRESS1 = b'\xaa' * 20
TEST_ADDRESS2 = b'\xbb' * 20
TEST_PREFIX_LIST = to_prefix_list_form([
[TEST_ADDRESS1, b'\x00'],
[TEST_ADDRESS1, b'\x01\x00'],
[TEST_ADDRESS2, b'\xff' * 32]
])


@pytest.mark.parametrize(
"prefix_list,address,slot,accessible",
(
[[], TEST_ADDRESS1, b'\x00' * 32, False],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\x00' * 32, True],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\x00' + b'\xff' * 31, True],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\xff' + b'\x00' * 31, False],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\xff' * 32, False],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\x01\x00' + b'\x00' * 30, True],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\x01\x00' + b'\xff' * 30, True],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\x01\x00' + b'\x00' * 30, True],
[TEST_PREFIX_LIST, TEST_ADDRESS1, b'\x01\x01' + b'\x00' * 30, False],
[TEST_PREFIX_LIST, TEST_ADDRESS2, b'\xff' * 32, True],
[TEST_PREFIX_LIST, TEST_ADDRESS2, b'\xff' * 31 + b'\x00', False],
[TEST_PREFIX_LIST, TEST_ADDRESS2, b'\x00' + b'\xff' * 31, False],
[to_prefix_list_form([[TEST_ADDRESS1, b'']]), TEST_ADDRESS1, b'\x00' * 32, True],
)
)
def test_accessibility(prefix_list, address, slot, accessible):
assert len(slot) == 32
key = keccak(address) + STORAGE_TRIE_PREFIX + slot
if accessible:
assert is_accessible(key, prefix_list)
else:
assert not is_accessible(key, prefix_list)


@pytest.mark.parametrize(
'prefixes,expected',
(
(
(b'', b'something'),
{b''},
),
(
(b'ethereum', b'eth', b'ether', b'england', b'eng'),
{b'eth', b'eng'},
),
(
(b'ethereum', b'ethereua'),
{b'ethereum', b'ethereua'},
),
(
(b'a', b'aa', b'b', b'bb', b'ab', b'ba'),
{b'a', b'b'},
),
),
)
def test_remove_redundant_prefixes(prefixes, expected):
actual = remove_redundant_prefixes(prefixes)
assert actual == expected


@pytest.mark.parametrize(
'access_list,expected',
[
(
(),
b'\xc0'
),
(
((TEST_ADDRESS1,),),
b'\xd6' + b'\xd5' + b'\x94' + TEST_ADDRESS1
),
(
((TEST_ADDRESS1, b''),),
b'\xd7' + b'\xd6' + b'\x94' + TEST_ADDRESS1 + b'\x80'
),
(
((TEST_ADDRESS1, b'\xaa'),),
b'\xd8' + b'\xd7' + b'\x94' + TEST_ADDRESS1 + b'\x81\xaa'
),
(
((TEST_ADDRESS1, b'\xaa' * 32),),
b'\xf7' + b'\xf6' + b'\x94' + TEST_ADDRESS1 + b'\xa0' + b'\xaa' * 32
),
(
(
(TEST_ADDRESS1, b'\xaa' * 20, b'\xbb' * 2, b'\xcc' * 32),
(TEST_ADDRESS2, b'\xaa\xbb')
),
b'\xf8\x69' + (
b'\xf8\x4e' + (
b'\x94' + TEST_ADDRESS1 +
b'\x94' + b'\xaa' * 20 +
b'\x82\xbb\xbb' +
b'\xa0' + b'\xcc' * 32
) +
b'\xd8' + (
b'\x94' + TEST_ADDRESS2 +
b'\x82\xaa\xbb'
)
)
),
]
)
def test_rlp_encoding(access_list, expected):
validate_transaction_access_list(access_list)
encoded = rlp.encode(access_list, access_list_sedes)
print(encoded)
print(expected)
assert encoded == expected

decoded = rlp.decode(encoded, access_list_sedes)
assert decoded == access_list


@pytest.mark.parametrize(
'invalid_access_list',
[
b'',
[[]],
[[[]]],
[[b'']],
[[b'\xaa' * 40]],
[[b'\xaa' * 19]],
[[b'\xaa' * 21]],
[[b'\xaa' * 32]],
[[b'\xaa' * 20, b'\xaa' * 33]],
[[], [b'\xaa']],
]
)
def test_invalid_rlp_encoding(invalid_access_list):
with pytest.raises(ListSerializationError):
rlp.encode(invalid_access_list, access_list_sedes)

encoded = rlp.encode(invalid_access_list)
with pytest.raises(ListDeserializationError):
rlp.decode(encoded, access_list_sedes)
76 changes: 0 additions & 76 deletions tests/core/access-restrictions-utils/test_checks_and_conversion.py

This file was deleted.

0 comments on commit 0b9e4fb

Please sign in to comment.