Skip to content

Commit

Permalink
Merge pull request #238 from NIC619/account_abstraction_ENTRY_POINT
Browse files Browse the repository at this point in the history
Sharding[VM,Block,Transaction,Message] and sharding transaction/message execution logic
  • Loading branch information
NIC619 committed Jan 7, 2018
2 parents 185b029 + 2646a10 commit 5e1f216
Show file tree
Hide file tree
Showing 15 changed files with 523 additions and 20 deletions.
6 changes: 6 additions & 0 deletions evm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,9 @@
BALANCE_TRIE_PREFIX = b'\x01'
CODE_TRIE_PREFIX = b'\x02'
STORAGE_TRIE_PREFIX = b'\x03'


#
# Account Abstraction
#
ENTRY_POINT = 20 * b'\xff'
43 changes: 36 additions & 7 deletions evm/rlp/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ def sender(self):
return self.get_sender()

@property
def intrensic_gas(self):
def intrinsic_gas(self):
"""
Convenience property for the return value of `get_intrensic_gas`
Convenience property for the return value of `get_intrinsic_gas`
"""
return self.get_intrensic_gas()
return self.get_intrinsic_gas()

# +-------------------------------------------------------------+
# | API that must be implemented by all Transaction subclasses. |
Expand All @@ -64,7 +64,7 @@ def validate(self):
Hook called during instantiation to ensure that all transaction
parameters pass validation rules.
"""
if self.intrensic_gas > self.gas:
if self.intrinsic_gas > self.gas:
raise ValidationError("Insufficient gas")
self.check_signature_validity()

Expand Down Expand Up @@ -96,7 +96,7 @@ def get_sender(self):
#
# Base gas costs
#
def get_intrensic_gas(self):
def get_intrinsic_gas(self):
"""
Compute the baseline gas cost for this transaction. This is the amount
of gas needed to send this transaction (but that is not actually used
Expand Down Expand Up @@ -153,9 +153,9 @@ class BaseShardingTransaction(rlp.Serializable):
fields = [
('chain_id', big_endian_int),
('shard_id', big_endian_int),
('target', address),
('to', address),
('data', binary),
('start_gas', big_endian_int),
('gas', big_endian_int),
('gas_price', big_endian_int),
('access_list', access_list_sedes),
('code', binary),
Expand All @@ -164,3 +164,32 @@ class BaseShardingTransaction(rlp.Serializable):
@property
def hash(self):
return keccak(rlp.encode(self))

#
# Validation
#
def validate(self):
"""
Hook called during instantiation to ensure that all transaction
parameters pass validation rules.
"""
if self.intrinsic_gas > self.gas:
raise ValidationError("Insufficient gas")

@property
def intrinsic_gas(self):
"""
Convenience property for the return value of `get_intrinsic_gas`
"""
return self.get_intrinsic_gas()

#
# Base gas costs
#
def get_intrinsic_gas(self):
"""
Compute the baseline gas cost for this transaction. This is the amount
of gas needed to send this transaction (but that is not actually used
for computation).
"""
raise NotImplementedError("Must be implemented by subclasses")
16 changes: 16 additions & 0 deletions evm/utils/address.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import rlp

from evm.validation import (
validate_is_bytes,
validate_length_lte,
)
from .keccak import (
keccak,
)
Expand All @@ -16,3 +20,15 @@ def force_bytes_to_address(value):

def generate_contract_address(address, nonce):
return keccak(rlp.encode([address, nonce]))[-20:]


def generate_create2_contract_address(salt, code):
"""
If contract is created by transaction, `salt` should be empty.
If contract is created by contract, `salt` is set by the creator contract.
"""
validate_length_lte(salt, 32, title="Salt")
validate_is_bytes(salt)
validate_is_bytes(code)

return keccak(pad_left(salt, 32, b'\x00') + code)[-20:]
3 changes: 3 additions & 0 deletions evm/vm/forks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
from .byzantium import ( # noqa: F401
ByzantiumVM,
)
from .sharding import ( # noqa: F401
ShardingVM,
)
2 changes: 1 addition & 1 deletion evm/vm/forks/frontier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def _execute_frontier_transaction(vm, transaction):
state_db.increment_nonce(transaction.sender)

# Setup VM Message
message_gas = transaction.gas - transaction.intrensic_gas
message_gas = transaction.gas - transaction.intrinsic_gas

if transaction.to == constants.CREATE_CONTRACT_ADDRESS:
contract_address = generate_contract_address(
Expand Down
6 changes: 3 additions & 3 deletions evm/vm/forks/frontier/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def check_signature_validity(self):
def get_sender(self):
return extract_transaction_sender(self)

def get_intrensic_gas(self):
return _get_frontier_intrensic_gas(self.data)
def get_intrinsic_gas(self):
return _get_frontier_intrinsic_gas(self.data)

def get_message_for_signing(self):
return rlp.encode(FrontierUnsignedTransaction(
Expand Down Expand Up @@ -104,7 +104,7 @@ def as_signed_transaction(self, private_key):
)


def _get_frontier_intrensic_gas(transaction_data):
def _get_frontier_intrinsic_gas(transaction_data):
num_zero_bytes = transaction_data.count(b'\x00')
num_non_zero_bytes = len(transaction_data) - num_zero_bytes
return (
Expand Down
6 changes: 3 additions & 3 deletions evm/vm/forks/homestead/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def validate(self):
super(HomesteadTransaction, self).validate()
validate_lt_secpk1n2(self.s, title="Transaction.s")

def get_intrensic_gas(self):
return _get_homestead_intrensic_gas(self)
def get_intrinsic_gas(self):
return _get_homestead_intrinsic_gas(self)

def get_message_for_signing(self):
return rlp.encode(HomesteadUnsignedTransaction(
Expand Down Expand Up @@ -60,7 +60,7 @@ def as_signed_transaction(self, private_key):
)


def _get_homestead_intrensic_gas(transaction):
def _get_homestead_intrinsic_gas(transaction):
num_zero_bytes = transaction.data.count(b'\x00')
num_non_zero_bytes = len(transaction.data) - num_zero_bytes
if transaction.to == CREATE_CONTRACT_ADDRESS:
Expand Down
173 changes: 173 additions & 0 deletions evm/vm/forks/sharding/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
from evm.constants import (
ENTRY_POINT,
)

from evm.exceptions import (
ContractCreationCollision,
)

from evm.vm.message import (
ShardingMessage,
)
from evm.vm.vm_state import (
VMState,
)

from evm.utils.address import (
generate_create2_contract_address,
)
from evm.utils.hexadecimal import (
encode_hex,
)
from evm.utils.keccak import (
keccak,
)

from ..byzantium import ByzantiumVM
from ..frontier.constants import (
REFUND_SELFDESTRUCT,
)
from .computation import ShardingComputation
from .validation import validate_sharding_transaction
from .blocks import ShardingBlock


def _execute_sharding_transaction(vm, transaction):
#
# 1) Pre Computation
#

# Validate the transaction
transaction.validate()

vm.validate_transaction(transaction)

gas_fee = transaction.gas * transaction.gas_price
with vm.state.state_db() as state_db:
# Buy Gas
state_db.delta_balance(transaction.to, -1 * gas_fee)

# Setup VM Message
message_gas = transaction.gas - transaction.intrinsic_gas

if transaction.code:
contract_address = generate_create2_contract_address(
b'',
transaction.code,
)
data = b''
code = transaction.code
is_create = True
else:
contract_address = None
data = transaction.data
code = state_db.get_code(transaction.to)
is_create = False

vm.logger.info(
(
"TRANSACTION: to: %s | gas: %s | "
"gas-price: %s | data-hash: %s | code-hash: %s"
),
encode_hex(transaction.to),
transaction.gas,
transaction.gas_price,
encode_hex(keccak(transaction.data)),
encode_hex(keccak(transaction.code)),
)

message = ShardingMessage(
gas=message_gas,
gas_price=transaction.gas_price,
to=transaction.to,
sender=ENTRY_POINT,
value=0,
data=data,
code=code,
is_create=is_create,
)

#
# 2) Apply the message to the VM.
#
if message.is_create:
with vm.state.state_db(read_only=True) as state_db:
is_collision = state_db.account_has_code_or_nonce(contract_address)

if is_collision:
# The address of the newly created contract has collided
# with an existing contract address.
computation = vm.get_computation(message)
computation._error = ContractCreationCollision(
"Address collision while creating contract: {0}".format(
encode_hex(contract_address),
)
)
vm.logger.debug(
"Address collision while creating contract: %s",
encode_hex(contract_address),
)
else:
computation = vm.get_computation(message).apply_create_message()
else:
computation = vm.get_computation(message).apply_message()

#
# 2) Post Computation
#
# Self Destruct Refunds
num_deletions = len(computation.get_accounts_for_deletion())
if num_deletions:
computation.gas_meter.refund_gas(REFUND_SELFDESTRUCT * num_deletions)

# Gas Refunds
gas_remaining = computation.get_gas_remaining()
gas_refunded = computation.get_gas_refund()
gas_used = transaction.gas - gas_remaining
gas_refund = min(gas_refunded, gas_used // 2)
gas_refund_amount = (gas_refund + gas_remaining) * transaction.gas_price

if gas_refund_amount:
vm.logger.debug(
'TRANSACTION REFUND: %s -> %s',
gas_refund_amount,
encode_hex(message.to),
)

with vm.state.state_db() as state_db:
state_db.delta_balance(message.to, gas_refund_amount)

# Miner Fees
transaction_fee = (transaction.gas - gas_remaining - gas_refund) * transaction.gas_price
vm.logger.debug(
'TRANSACTION FEE: %s -> %s',
transaction_fee,
encode_hex(vm.block.header.coinbase),
)
with vm.state.state_db() as state_db:
state_db.delta_balance(vm.block.header.coinbase, transaction_fee)

# Process Self Destructs
with vm.state.state_db() as state_db:
for account, beneficiary in computation.get_accounts_for_deletion():
# TODO: need to figure out how we prevent multiple selfdestructs from
# the same account and if this is the right place to put this.
vm.logger.debug('DELETING ACCOUNT: %s', encode_hex(account))

# TODO: this balance setting is likely superflous and can be
# removed since `delete_account` does this.
state_db.set_balance(account, 0)
state_db.delete_account(account)

return computation


ShardingVM = ByzantiumVM.configure(
name='ShardingVM',
_computation_class=ShardingComputation,
_block_class=ShardingBlock,
_state_class=VMState,
# Method overrides
validate_transaction=validate_sharding_transaction,
execute_transaction=_execute_sharding_transaction,
)
21 changes: 21 additions & 0 deletions evm/vm/forks/sharding/blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from rlp.sedes import (
CountableList,
)
from evm.rlp.headers import (
BlockHeader,
)
from evm.vm.forks.homestead.blocks import (
HomesteadBlock,
)
from .transactions import (
ShardingTransaction,
)


class ShardingBlock(HomesteadBlock):
transaction_class = ShardingTransaction
fields = [
('header', BlockHeader),
('transactions', CountableList(transaction_class)),
('uncles', CountableList(BlockHeader))
]
Loading

0 comments on commit 5e1f216

Please sign in to comment.