Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spurious dragon fork rules: WIP #109

Merged
merged 4 commits into from
Oct 19, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're going to have to change this every time we implement rules for a new fork, right? Is there any way we could keep a list of existing forks (in order) and always pick the last one here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I've been punting on writing a generic algorithm for this. Soon.


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) // 2
else:
return (v - EIP155_CHAIN_ID_OFFSET) // 2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now you return the same value in both cases. I don't think that's what you intended?



def extract_signature_v(v):
if is_even(v):
return 28
else:
return 27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/27/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