Skip to content

Commit

Permalink
Merge pull request #109 from pipermerriam/piper/spurious-dragon
Browse files Browse the repository at this point in the history
Spurious dragon fork rules: WIP
  • Loading branch information
pipermerriam authored Oct 19, 2017
2 parents 8ce420e + 948e6b6 commit 4fa0f23
Show file tree
Hide file tree
Showing 31 changed files with 632 additions and 87 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ env:
- TOX_POSARGS="-e py35-state-frontier"
- TOX_POSARGS="-e py35-state-homestead"
- TOX_POSARGS="-e py35-state-eip150"
#- TOX_POSARGS="-e py35-state-eip158"
- TOX_POSARGS="-e py35-state-eip158"
#- TOX_POSARGS="-e py35-state-byzantium"
#- TOX_POSARGS="-e py35-state-constantinople"
#- TOX_POSARGS="-e py35-state-metropolis"
Expand Down
2 changes: 2 additions & 0 deletions evm/chains/mainnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
EIP150VM,
FrontierVM,
HomesteadVM,
SpuriousDragonVM,
)


MAINNET_VM_CONFIGURATION = (
(0, FrontierVM),
(constants.HOMESTEAD_MAINNET_BLOCK, HomesteadVM),
(constants.EIP150_MAINNET_BLOCK, EIP150VM),
(constants.SPURIOUS_DRAGON_MAINNET_BLOCK, SpuriousDragonVM),
)


Expand Down
31 changes: 27 additions & 4 deletions evm/chains/tester/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
FrontierVM as BaseFrontierVM,
HomesteadVM as BaseHomesteadVM,
EIP150VM as BaseEIP150VM,
SpuriousDragonVM as BaseSpuriousDragonVM,
)

from evm.utils.chain import (
Expand Down Expand Up @@ -44,6 +45,10 @@ class EIP150TesterVM(MaintainGasLimitMixin, BaseEIP150VM):
pass


class SpuriousDragonTesterVM(MaintainGasLimitMixin, BaseSpuriousDragonVM):
pass


INVALID_FORK_ACTIVATION_MSG = (
"The {0}-fork activation block may not be null if the {1}-fork block "
"is non null"
Expand All @@ -53,11 +58,27 @@ class EIP150TesterVM(MaintainGasLimitMixin, BaseEIP150VM):
@reversed_return
def _generate_vm_configuration(homestead_start_block=None,
dao_start_block=None,
eip150_start_block=None):
eip150_start_block=None,
spurious_dragon_block=None):
# If no explicit configuration has been passed, configure the vm to start
# with the latest fork rules at block 0
if eip150_start_block is None and homestead_start_block is None:
yield (0, EIP150TesterVM)
no_declared_blocks = (
spurious_dragon_block is None and
eip150_start_block is None and
homestead_start_block is None
)
if no_declared_blocks:
yield (0, SpuriousDragonTesterVM)

if spurious_dragon_block is not None:
yield (spurious_dragon_block, SpuriousDragonTesterVM)

remaining_blocks_not_declared = (
homestead_start_block is None and
eip150_start_block is None
)
if spurious_dragon_block > 0 and remaining_blocks_not_declared:
yield (0, EIP150TesterVM)

if eip150_start_block is not None:
yield (eip150_start_block, EIP150TesterVM)
Expand Down Expand Up @@ -111,13 +132,15 @@ def validate_seal(self, block):
def configure_forks(self,
homestead_start_block=None,
dao_start_block=None,
eip150_start_block=0):
eip150_start_block=None,
spurious_dragon_block=None):
"""
TODO: add support for state_cleanup
"""
vm_configuration = _generate_vm_configuration(
homestead_start_block=homestead_start_block,
dao_start_block=dao_start_block,
eip150_start_block=eip150_start_block,
spurious_dragon_block=spurious_dragon_block,
)
self.vms_by_range = generate_vms_by_range(vm_configuration)
13 changes: 13 additions & 0 deletions evm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,16 @@
# EIP150
#
EIP150_MAINNET_BLOCK = 2463000


#
# Spurious Dragon
#
SPURIOUS_DRAGON_MAINNET_BLOCK = 2675000

# https://github.com/ethereum/EIPs/issues/160
GAS_EXP_EIP160 = 10
GAS_EXPBYTE_EIP160 = 50

# https://github.com/ethereum/EIPs/issues/170
EIP170_CODE_SIZE_LIMIT = 24577
13 changes: 12 additions & 1 deletion evm/db/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ def get_code_hash(self, address):
def delete_code(self, address):
validate_canonical_address(address, title="Storage Address")
account = self._get_account(address)
del self.db[account.code_hash]
account.code_hash = EMPTY_SHA3
self._set_account(address, account)

Expand All @@ -185,6 +184,18 @@ def account_has_code_or_nonce(self, address):
else:
return False

def account_is_empty(self, address):
validate_canonical_address(address, title="Storage Address")
account = self._get_account(address)
if account.code_hash != EMPTY_SHA3:
return False
elif account.balance != 0:
return False
elif account.nonce != 0:
return False
else:
return True

def touch_account(self, address):
account = self._get_account(address)
self._set_account(address, account)
Expand Down
9 changes: 7 additions & 2 deletions evm/logic/arithmetic.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from cytoolz import (
curry,
)

from evm import constants

from evm.utils.numeric import (
Expand Down Expand Up @@ -133,7 +137,8 @@ def sdiv(computation):
computation.stack.push(signed_to_unsigned(result))


def exp(computation):
@curry
def exp(computation, gas_per_byte):
"""
Exponentiation
"""
Expand All @@ -148,7 +153,7 @@ def exp(computation):
result = pow(base, exponent, constants.UINT_256_CEILING)

computation.gas_meter.consume_gas(
constants.GAS_EXPBYTE * byte_size,
gas_per_byte * byte_size,
reason="EXP: exponent bytes",
)

Expand Down
20 changes: 19 additions & 1 deletion evm/logic/call.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ def get_call_params(self, computation):
)


#
# EIP150
#
class CallEIP150(Call):
def compute_msg_gas(self, computation, gas, to, value):
extra_gas = self.compute_msg_extra_gas(computation, gas, to, value)
Expand Down Expand Up @@ -257,7 +260,7 @@ def compute_eip150_msg_gas(computation, gas, extra_gas, value, mnemonic, callsti
# It feels wrong to raise an OutOfGas exception outside of GasMeter,
# but I don't see an easy way around it.
raise OutOfGas("Out of gas: Needed {0} - Remaining {1} - Reason: {2}".format(
gas,
extra_gas,
computation.gas_meter.gas_remaining,
mnemonic,
))
Expand All @@ -267,3 +270,18 @@ def compute_eip150_msg_gas(computation, gas, extra_gas, value, mnemonic, callsti
total_fee = gas + extra_gas
child_msg_gas = gas + (callstipend if value else 0)
return child_msg_gas, total_fee


#
# EIP161
#
class CallEIP161(CallEIP150):
def compute_msg_extra_gas(self, computation, gas, to, value):
with computation.vm.state_db(read_only=True) as state_db:
account_is_dead = (
not state_db.account_exists(to) or
state_db.account_is_empty(to)
)
transfer_gas_fee = constants.GAS_CALLVALUE if value else 0
create_gas_fee = constants.GAS_NEWACCOUNT if (account_is_dead and value) else 0
return transfer_gas_fee + create_gas_fee
32 changes: 28 additions & 4 deletions evm/logic/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,24 @@ def suicide_eip150(computation):
with computation.vm.state_db(read_only=True) as state_db:
if not state_db.account_exists(beneficiary):
computation.gas_meter.consume_gas(
constants.GAS_SUICIDE_NEWACCOUNT, reason=mnemonics.SUICIDE)
constants.GAS_SUICIDE_NEWACCOUNT,
reason=mnemonics.SUICIDE,
)
_suicide(computation, beneficiary)


def suicide_eip161(computation):
beneficiary = force_bytes_to_address(computation.stack.pop(type_hint=constants.BYTES))
with computation.vm.state_db(read_only=True) as state_db:
is_dead = (
not state_db.account_exists(beneficiary) or
state_db.account_is_empty(beneficiary)
)
if is_dead and state_db.get_balance(computation.msg.storage_address):
computation.gas_meter.consume_gas(
constants.GAS_SUICIDE_NEWACCOUNT,
reason=mnemonics.SUICIDE,
)
_suicide(computation, beneficiary)


Expand All @@ -50,8 +67,15 @@ def _suicide(computation, beneficiary):
# beneficiary.
state_db.set_balance(computation.msg.storage_address, 0)

computation.vm.logger.debug(
"SUICIDE: %s (%s) -> %s",
encode_hex(computation.msg.storage_address),
local_balance,
encode_hex(beneficiary),
)

# 3rd: Register the account to be deleted
computation.register_account_for_deletion(computation.msg.storage_address)
computation.register_account_for_deletion(beneficiary)


class Create(Opcode):
Expand Down Expand Up @@ -80,7 +104,8 @@ def __call__(self, computation):
call_data = computation.memory.read(start_position, size)

create_msg_gas = self.max_child_gas_modifier(
computation.gas_meter.gas_remaining)
computation.gas_meter.gas_remaining
)
computation.gas_meter.consume_gas(create_msg_gas, reason="CREATE")

with computation.vm.state_db() as state_db:
Expand Down Expand Up @@ -112,7 +137,6 @@ def __call__(self, computation):
)

child_computation = computation.vm.apply_create_message(child_msg)

computation.children.append(child_computation)

if child_computation.error:
Expand Down
4 changes: 3 additions & 1 deletion evm/utils/fixture_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
pad_left,
remove_0x_prefix,
to_canonical_address,
to_dict,
to_normalized_address,
to_tuple,
to_dict,
)

from evm.constants import (
Expand Down Expand Up @@ -291,6 +291,7 @@ def normalize_unsigned_transaction(transaction, indexes):
yield 'gasLimit', to_int(transaction['gasLimit'][indexes['gas']])
yield 'gasPrice', to_int(transaction['gasPrice'])
yield 'nonce', to_int(transaction['nonce'])

if 'secretKey' in transaction:
yield 'secretKey', decode_hex(transaction['secretKey'])
elif 'v' in transaction and 'r' in transaction and 's' in transaction:
Expand All @@ -301,6 +302,7 @@ def normalize_unsigned_transaction(transaction, indexes):
)
else:
raise KeyError("transaction missing secret key or vrs values")

yield 'to', normalize_to_address(transaction['to'])
yield 'value', to_int(transaction['value'][indexes['value']])

Expand Down
69 changes: 63 additions & 6 deletions evm/utils/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,68 @@
from evm.exceptions import (
ValidationError,
)
from evm.utils.numeric import (
is_even,
int_to_big_endian,
)


EIP155_CHAIN_ID_OFFSET = 35
V_OFFSET = 27


def is_eip_155_signed_transaction(transaction):
if transaction.v >= EIP155_CHAIN_ID_OFFSET:
return True
else:
return False


def extract_chain_id(v):
if is_even(v):
return (v - EIP155_CHAIN_ID_OFFSET - 1) // 2
else:
return (v - EIP155_CHAIN_ID_OFFSET) // 2


def extract_signature_v(v):
if is_even(v):
return V_OFFSET + 1
else:
return V_OFFSET


def create_transaction_signature(unsigned_txn, private_key):
signature = private_key.sign_msg(rlp.encode(unsigned_txn))
def create_transaction_signature(unsigned_txn, private_key, chain_id=None):
transaction_parts = rlp.decode(rlp.encode(unsigned_txn))

if chain_id:
transaction_parts_for_signature = (
transaction_parts + [int_to_big_endian(chain_id), b'', b'']
)
else:
transaction_parts_for_signature = transaction_parts

message = rlp.encode(transaction_parts_for_signature)
signature = private_key.sign_msg(message)

canonical_v, r, s = signature.vrs
v = canonical_v + 27

if chain_id:
v = canonical_v + chain_id * 2 + EIP155_CHAIN_ID_OFFSET
else:
v = canonical_v + V_OFFSET

return v, r, s


def validate_transaction_signature(transaction):
v, r, s = transaction.v, transaction.r, transaction.s
if is_eip_155_signed_transaction(transaction):
v = extract_signature_v(transaction.v)
else:
v = transaction.v

canonical_v = v - 27
vrs = (canonical_v, r, s)
vrs = (canonical_v, transaction.r, transaction.s)
signature = keys.Signature(vrs=vrs)
message = transaction.get_message_for_signing()
try:
Expand All @@ -34,7 +83,15 @@ def validate_transaction_signature(transaction):


def extract_transaction_sender(transaction):
v, r, s = transaction.v, transaction.r, transaction.s
if is_eip_155_signed_transaction(transaction):
if is_even(transaction.v):
v = 28
else:
v = 27
else:
v = transaction.v

r, s = transaction.r, transaction.s

canonical_v = v - 27
vrs = (canonical_v, r, s)
Expand Down
Loading

0 comments on commit 4fa0f23

Please sign in to comment.