Skip to content
This repository has been archived by the owner on Jul 14, 2020. It is now read-only.

EIP161 State Trie Clearing #5

Merged
merged 18 commits into from
Nov 16, 2016
2 changes: 1 addition & 1 deletion fixtures
Submodule fixtures updated 47 files
+1 −0 BlockchainTests/EIP150/README.md
+1 −0 BlockchainTests/Homestead/README.md
+6 −6 StateTests/EIP150/Homestead/stDelegatecallTest.json
+12 −12 StateTests/EIP150/Homestead/stPreCompiledContracts.json
+89 −90 StateTests/EIP150/Homestead/stSystemOperationsTest.json
+2,974 −0 StateTests/EIP158/EIP150/stChangedTests.json
+1,065 −0 StateTests/EIP158/EIP150/stEIPSpecificTest.json
+2,228 −0 StateTests/EIP158/EIP150/stEIPsingleCodeGasPrices.json
+688 −0 StateTests/EIP158/EIP150/stMemExpandingEIPCalls.json
+3,310 −0 StateTests/EIP158/Homestead/stBoundsTest.json
+7,159 −0 StateTests/EIP158/Homestead/stCallCodes.json
+2,649 −0 StateTests/EIP158/Homestead/stCallCreateCallCodeTest.json
+5,861 −0 StateTests/EIP158/Homestead/stCallDelegateCodes.json
+5,842 −0 StateTests/EIP158/Homestead/stCallDelegateCodesCallCode.json
+2,561 −0 StateTests/EIP158/Homestead/stDelegatecallTest.json
+320 −0 StateTests/EIP158/Homestead/stHomeSteadSpecific.json
+1,086 −0 StateTests/EIP158/Homestead/stInitCodeTest.json
+3,855 −0 StateTests/EIP158/Homestead/stLogTests.json
+4,091 −0 StateTests/EIP158/Homestead/stMemoryTest.json
+5,683 −0 StateTests/EIP158/Homestead/stPreCompiledContracts.json
+8,066 −0 StateTests/EIP158/Homestead/stQuadraticComplexityTest.json
+3,100 −0 StateTests/EIP158/Homestead/stRecursiveCreate.json
+1,307 −0 StateTests/EIP158/Homestead/stRefundTest.json
+527 −0 StateTests/EIP158/Homestead/stSpecialTest.json
+8,693 −0 StateTests/EIP158/Homestead/stSystemOperationsTest.json
+2,583 −0 StateTests/EIP158/Homestead/stTransactionTest.json
+3,286 −0 StateTests/EIP158/Homestead/stWalletTest.json
+105 −0 StateTests/EIP158/stCodeSizeLimit.json
+1,281 −0 StateTests/EIP158/stCreateTest.json
+361 −0 StateTests/EIP158/stEIP158SpecificTest.json
+1,677 −0 StateTests/EIP158/stNonZeroCallsTest.json
+1,591 −0 StateTests/EIP158/stZeroCallsTest.json
+6 −6 StateTests/Homestead/stDelegatecallTest.json
+12 −12 StateTests/Homestead/stPreCompiledContracts.json
+15 −92 StateTests/Homestead/stSystemOperationsTest.json
+12 −12 StateTests/stPreCompiledContracts.json
+152 −153 StateTests/stSystemOperationsTest.json
+615 −0 TransactionTests/EIP155/ttTransactionTest.json
+189 −0 TransactionTests/EIP155/ttTransactionTestEip155VitaliksTests.json
+591 −0 TransactionTests/EIP155/ttTransactionTestVRule.json
+28 −0 TransactionTests/Homestead/ttTransactionTest.json
+46 −0 TransactionTests/Homestead/ttTransactionTestEip155VitaliksTests.json
+30 −1 TransactionTests/ttTransactionTest.json
+4 −4 VMTests/vmEnvironmentalInfoTest.json
+2 −2 VMTests/vmIOandFlowOperationsTest.json
+43 −0 VMTests/vmPushDupSwapTest.json
+44 −18 VMTests/vmSystemOperationsTest.json
55 changes: 51 additions & 4 deletions lib/ethereum/block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Block
def_delegators :header, *HeaderGetters, *HeaderSetters

attr :env, :db, :config
attr_accessor :state, :transactions, :receipts, :refunds, :suicides, :ether_delta, :ancestor_hashes, :logs, :log_listeners
attr_accessor :state, :transactions, :receipts, :refunds, :suicides, :touched, :ether_delta, :ancestor_hashes, :logs, :log_listeners

class <<self
##
Expand Down Expand Up @@ -248,6 +248,7 @@ def initialize(*args)
@get_transactions_cache = []

self.suicides = []
self.touched = {}
self.logs = []
self.log_listeners = []

Expand Down Expand Up @@ -288,6 +289,10 @@ def initialize(*args)
header.instance_variable_set :@_mutable, original_values[:header_mutable]
end

def post_hardfork?(name)
number >= config[:"#{name}_fork_blknum"]
end

def add_listener(l)
log_listeners.push l
end
Expand Down Expand Up @@ -396,6 +401,15 @@ def add_refund(x)
self.refunds += x
end

def add_touched(addr, gas=nil)
self.touched[addr] ||= 0
self.touched[addr] += gas if gas
end

def add_suicide(addr)
self.suicides.push addr
end

##
# Add a transaction to the transaction trie.
#
Expand Down Expand Up @@ -459,8 +473,8 @@ def apply_transaction(tx)
self.refunds = 0
end

delta_balance tx.sender, tx.gasprice * gas_remained
delta_balance coinbase, tx.gasprice * gas_used
apply_tx_refund tx.sender, gas_remained, tx.gasprice
apply_tx_reward coinbase, gas_used, tx.gasprice
self.gas_used += gas_used

output = tx.to.true? ? Utils.int_array_to_bytes(data) : data
Expand All @@ -469,7 +483,7 @@ def apply_transaction(tx)
logger.debug "TX FAILED", reason: 'out of gas', startgas: tx.startgas, gas_remained: gas_remained

self.gas_used += tx.startgas
delta_balance coinbase, tx.gasprice*tx.startgas
apply_tx_reward coinbase, tx.startgas, tx.gasprice

output = Constant::BYTE_EMPTY
success = 0
Expand All @@ -484,6 +498,20 @@ def apply_transaction(tx)
end
self.suicides = []

if post_hardfork?(:spurious_dragon)
touched.each do |addr, gas|
if account_is_empty(addr)
# revert GCALLNEWACCOUNT cost first
if gas > 0 && tx.gasprice > 0
apply_tx_refund tx.sender, gas, tx.gasprice
apply_tx_reward coinbase, -gas, tx.gasprice
end
del_account addr
end
end
end
self.touched = {}

add_transaction_to_list tx
self.logs = []

Expand Down Expand Up @@ -707,6 +735,13 @@ def account_exists(address)
@state[address].size > 0 || @caches[:all].has_key?(address)
end

##
# Returns true when the account is either empty or non-exist.
#
def account_is_empty(address)
get_balance(address) == 0 && get_code(address) == Constant::BYTE_EMPTY && get_nonce(address) == 0
end

def add_log(log)
logs.push log
log_listeners.each {|l| l.call log }
Expand Down Expand Up @@ -1352,5 +1387,17 @@ def mk_transaction_receipt(tx)
end
end

def apply_tx_refund(sender, remained, gasprice)
delta_balance sender, gasprice*remained
end

def apply_tx_reward(coinbase, used, gasprice)
amount = gasprice * used
delta_balance coinbase, amount
if post_hardfork?(:spurious_dragon)
add_touched coinbase if amount == 0
end
end

end
end
2 changes: 2 additions & 0 deletions lib/ethereum/constant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ module Constant
PRIVKEY_ZERO = ("\x00"*32).freeze
PRIVKEY_ZERO_HEX = ('0'*64).freeze

CONTRACT_CODE_SIZE_LIMIT = 0x6000

end
end
2 changes: 1 addition & 1 deletion lib/ethereum/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Env
dao_withdrawer: Utils.normalize_address('0xbf4ed7b27f1d666546e30d74d50d173d20bca754'),

anti_dos_fork_blknum: 2457000,
clearing_fork_blknum: 2**100
spurious_dragon_fork_blknum: 2675000
}.freeze

attr :db, :config, :global_config
Expand Down
36 changes: 14 additions & 22 deletions lib/ethereum/external_call.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class ExternalCall
def_delegators :@block, :get_code, :set_code, :get_balance, :set_balance,
:delta_balance, :get_nonce, :set_nonce, :increment_nonce,
:get_storage_data, :set_storage_data, :get_storage_bytes, :reset_storage,
:add_refund, :account_exists, :snapshot, :revert, :transfer_value
:add_refund, :add_touched, :add_suicide, :account_exists, :account_is_empty,
:snapshot, :revert, :transfer_value, :post_hardfork?

def initialize(block, tx)
@block = block
Expand All @@ -26,12 +27,8 @@ def log_storage(x)
@block.account_to_dict(x)[:storage]
end

def add_suicide(x)
@block.suicides.push x
end

def block_hash(x)
if post_metropolis_hardfork
if post_hardfork?(:metropolis)
get_storage_data @block.config[:metropolis_blockhash_store], x
else
d = @block.number - x
Expand Down Expand Up @@ -76,25 +73,13 @@ def tx_gasprice
@tx.gasprice
end

def post_homestead_hardfork
@block.number >= @block.config[:homestead_fork_blknum]
end

def post_anti_dos_hardfork
@block.number >= @block.config[:anti_dos_fork_blknum]
end

def post_metropolis_hardfork
@block.number >= @block.config[:metropolis_fork_blknum]
end

def create(msg)
log_msg.debug 'CONTRACT CREATION'

sender = Utils.normalize_address(msg.sender, allow_blank: true)

code = msg.data.extract_all
if post_metropolis_hardfork
if post_hardfork?(:metropolis)
msg.to = Utils.mk_metropolis_contract_address msg.sender, code
if get_code(msg.to)
n1 = get_nonce msg.to
Expand All @@ -114,7 +99,7 @@ def create(msg)
balance = get_balance(msg.to)
if balance > 0
set_balance msg.to, balance
set_nonce msg.to, 0
set_nonce msg.to, @block.config[:account_initial_nonce]
set_code msg.to, Constant::BYTE_EMPTY
reset_storage msg.to
end
Expand All @@ -123,19 +108,21 @@ def create(msg)
msg.data = VM::CallData.new [], 0, 0

snapshot = self.snapshot
increment_nonce msg.to if post_hardfork?(:spurious_dragon)
res, gas, dat = apply_msg msg, code

if res.true?
return 1, gas, msg.to if dat.empty?

gcost = dat.size * Opcodes::GCONTRACTBYTE
gcost = dat.size > Constant::CONTRACT_CODE_SIZE_LIMIT ?
Constant::UINT_MAX : dat.size * Opcodes::GCONTRACTBYTE
if gas >= gcost
gas -= gcost
else
dat = []
log_msg.debug "CONTRACT CREATION OOG", have: gas, want: gcost, block_number: @block.number

if post_homestead_hardfork
if post_hardfork?(:homestead)
revert snapshot
return 0, 0, Constant::BYTE_EMPTY
end
Expand All @@ -144,6 +131,7 @@ def create(msg)
set_code msg.to, Utils.int_array_to_bytes(dat)
return 1, gas, msg.to
else
revert snapshot if post_hardfork?(:homestead)
return 0, gas, Constant::BYTE_EMPTY
end
end
Expand Down Expand Up @@ -190,6 +178,10 @@ def apply_msg(msg, code=nil)
revert snapshot
end

if post_hardfork?(:spurious_dragon)
add_touched msg.to if msg.value == 0
end

return res, gas, dat
end

Expand Down
2 changes: 2 additions & 0 deletions lib/ethereum/opcodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,7 @@ class Opcodes
CALL_CHILD_LIMIT_NUM = 63
CALL_CHILD_LIMIT_DENOM = 64
SUICIDE_SUPPLEMENTAL_GAS = 5000
GEXPONENTBYTE_SUPPLEMENTAL_GAS = 40

end
end
4 changes: 2 additions & 2 deletions lib/ethereum/secp256k1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def recoverable_sign(msg, privkey)
pk = ::Secp256k1::PrivateKey.new privkey: privkey, raw: true
signature = pk.ecdsa_recoverable_serialize pk.ecdsa_sign_recoverable(msg, raw: true)

v = signature[1] + 27
v = signature[1]
r = Utils.big_endian_to_int signature[0][0,32]
s = Utils.big_endian_to_int signature[0][32,32]

Expand All @@ -49,7 +49,7 @@ def signature_verify(msg, vrs, pubkey)
def recover_pubkey(msg, vrs, compressed: false)
pk = ::Secp256k1::PublicKey.new(flags: ::Secp256k1::ALL_FLAGS)
sig = Utils.zpad_int(vrs[1]) + Utils.zpad_int(vrs[2])
recsig = pk.ecdsa_recoverable_deserialize(sig, vrs[0]-27)
recsig = pk.ecdsa_recoverable_deserialize(sig, vrs[0])
pk.public_key = pk.ecdsa_recover msg, recsig, raw: true
pk.serialize compressed: compressed
end
Expand Down
4 changes: 2 additions & 2 deletions lib/ethereum/special_contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def call(ext, msg)
r = msg.data.extract32(64)
s = msg.data.extract32(96)

if r >= Secp256k1::N || s >= Secp256k1::N || v < 27 || v > 28
if r >= Secp256k1::N || s >= Secp256k1::N || v < Transaction::V_MIN || v > Transaction::V_MAX
return 1, msg.gas - gas_cost, []
end

Expand All @@ -27,7 +27,7 @@ def call(ext, msg)

pub = nil
begin
pub = Secp256k1.recover_pubkey(message_hash, [v,r,s])
pub = Secp256k1.recover_pubkey(message_hash, [Transaction.decode_v(v),r,s])
rescue
return 1, msg.gas - gas_cost, []
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ethereum/tester/solidity_wrapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def solidity_library_symbol(library_name)
# Change the bytecode to use the given library address.
#
# @param hex_code [String] The bytecode encoded in hex.
# @param library_name [String] The library that will be resolved.
# @param library_symbol [String] The library that will be resolved.
# @param library_address [String] The address of the library.
#
# @return [String] The bytecode encoded in hex with the library
Expand Down
65 changes: 58 additions & 7 deletions lib/ethereum/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,41 @@ class Transaction
s: big_endian_int
)

V_MIN = 27
V_MAX = 28

EIP155_V_OFFSET = 35
EIP155_CHAIN_ID = 1
EIP155_V_MIN = EIP155_V_OFFSET + 2 * EIP155_CHAIN_ID
EIP155_V_MAX = EIP155_V_MIN + 1

class <<self
##
# A contract is a special transaction without the `to` argument.
#
def contract(nonce, gasprice, startgas, endowment, code, v=0, r=0, s=0)
new nonce, gasprice, startgas, '', endowment, code, v, r, s
end

def encode_v(v, eip155=false)
eip155 ? (v + EIP155_V_MIN) : (v + V_MIN)
end

def decode_v(v)
return unless v
if v == V_MIN || v == V_MAX
v - V_MIN
elsif v == EIP155_V_MIN || v == EIP155_V_MAX
v - EIP155_V_MIN
else
raise InvalidTransaction, "invalid signature"
end
end

def decode_chain_id(v)
raise InvalidTransaction, "invalid chain id" if v < EIP155_V_OFFSET+2
(v - EIP155_V_OFFSET) / 2
end
end

def initialize(*args)
Expand All @@ -66,12 +94,12 @@ def initialize(*args)

def sender
unless @sender
if v && v > 0
raise InvalidTransaction, "Invalid signature values!" if r >= Secp256k1::N || s >= Secp256k1::N || v < 27 || v > 28 || r == 0 || s == 0
v = Transaction.decode_v(self.v)
if v
raise InvalidTransaction, "Invalid signature values!" if r >= Secp256k1::N || s >= Secp256k1::N || v > 1 || r == 0 || s == 0

logger.debug "recovering sender"
rlpdata = RLP.encode(self, sedes: UnsignedTransaction)
rawhash = Utils.keccak256 rlpdata
rawhash = Utils.keccak256 signing_data(:verify)

pub = nil
begin
Expand All @@ -98,14 +126,14 @@ def sender=(v)
#
# A potentially already existing signature would be override.
#
def sign(key)
def sign(key, eip155=false)
raise InvalidTransaction, "Zero privkey cannot sign" if [0, '', Constant::PRIVKEY_ZERO, Constant::PRIVKEY_ZERO_HEX].include?(key)

rawhash = Utils.keccak256 RLP.encode(self, sedes: UnsignedTransaction)
rawhash = Utils.keccak256 signing_data(:sign)
key = PrivateKey.new(key).encode(:bin)

vrs = Secp256k1.recoverable_sign rawhash, key
self.v = vrs[0]
self.v = Transaction.encode_v(vrs[0], eip155)
self.r = vrs[1]
self.s = vrs[2]

Expand All @@ -124,6 +152,29 @@ def check_low_s
raise InvalidTransaction, "Invalid signature S value!" if s > Secp256k1::N/2 || s == 0
end

def signing_data(mode)
case mode
when :sign
if v == 0 # use encoding rules before EIP155
RLP.encode(self, sedes: UnsignedTransaction)
elsif v == EIP155_CHAIN_ID && r == 0 && s == 0 # after EIP155, v is chain_id >= 1
RLP.encode(self, sedes: Transaction)
else
raise InvalidTransaction, "invalid signature"
end
when :verify
if v == V_MIN || v == V_MAX # encoded v before EIP155
RLP.encode(self, sedes: UnsignedTransaction)
elsif v == EIP155_V_MIN || v == EIP155_V_MAX # after EIP155, v with chain_id encoded in it
values = UnsignedTransaction.serializable_fields.keys.map {|k| send k }
values += [EIP155_CHAIN_ID, 0, 0]
RLP.encode(values, sedes: Transaction.serializable_sedes)
end
else
raise InvalidTransaction, "invalid signature"
end
end

def full_hash
Utils.keccak256_rlp self
end
Expand Down
Loading