From b6d88a0e9f9aaeb47d585d79c768d457b545af90 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 19 Jan 2016 23:50:00 +0100 Subject: [PATCH] core, core/vm, crypto: fixes for homestead * Removed some strange code that didn't apply state reverting properly * Refactored code setting from vm & state transition to the executioner * Updated tests --- common/registrar/ethreg/api.go | 5 +- core/block_validator.go | 11 ++-- core/database_util.go | 14 ----- core/database_util_test.go | 4 +- core/execution.go | 60 +++++++++++-------- core/state/dump.go | 3 +- core/state/state_object.go | 7 +-- core/state/statedb.go | 17 ------ core/state_transition.go | 39 ++++-------- core/{transaction_pool.go => tx_pool.go} | 26 +++----- ...ansaction_pool_test.go => tx_pool_test.go} | 0 core/types/transaction.go | 23 ++++++- core/vm/contract.go | 44 +++++++++----- core/vm/environment.go | 1 - core/vm/instructions.go | 27 ++++++--- core/vm/jit.go | 21 ++++++- core/vm/jit_test.go | 5 ++ core/vm/jump_table.go | 23 +++++-- core/vm/jump_table_test.go | 24 ++++++++ core/vm/opcodes.go | 1 + core/vm/vm.go | 51 +++------------- miner/worker.go | 3 +- params/util.go | 3 +- tests/state_test.go | 9 +++ 24 files changed, 226 insertions(+), 195 deletions(-) rename core/{transaction_pool.go => tx_pool.go} (98%) rename core/{transaction_pool_test.go => tx_pool_test.go} (100%) create mode 100644 core/vm/jump_table_test.go diff --git a/common/registrar/ethreg/api.go b/common/registrar/ethreg/api.go index 92e885b10e..1ba422c4d8 100644 --- a/common/registrar/ethreg/api.go +++ b/common/registrar/ethreg/api.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/compiler" + "github.com/ethereum/go-ethereum/common/registrar" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -30,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/common/registrar" ) // registryAPIBackend is a backend for an Ethereum Registry. @@ -112,6 +112,9 @@ type callmsg struct { func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } +func (m callmsg) FromFrontier() (common.Address, error) { + return m.from.Address(), nil +} func (m callmsg) Nonce() uint64 { return m.from.Nonce() } diff --git a/core/block_validator.go b/core/block_validator.go index 901744c61d..73c33d8dde 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -117,8 +117,7 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat // For valid blocks this should always validate to true. rbloom := types.CreateBloom(receipts) if rbloom != header.Bloom { - //fmt.Printf("FUNKY: ValidateState: block number: %v\n", block.Number()) - return fmt.Errorf("unable to replicate block's bloom=%x", rbloom) + return fmt.Errorf("unable to replicate block's bloom=%x vs calculated bloom=%x", header.Bloom, rbloom) } // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, R1]])) receiptSha := types.DeriveSha(receipts) @@ -270,10 +269,6 @@ func calcDifficultyHomestead(time, parentTime uint64, parentNumber, parentDiff * bigTime := new(big.Int).SetUint64(time) bigParentTime := new(big.Int).SetUint64(parentTime) - // for the exponential factor - periodCount := new(big.Int).Add(parentNumber, common.Big1) - periodCount.Div(periodCount, ExpDiffPeriod) - // holds intermediate values to make the algo easier to read & audit x := new(big.Int) y := new(big.Int) @@ -298,6 +293,10 @@ func calcDifficultyHomestead(time, parentTime uint64, parentNumber, parentDiff * x = params.MinimumDifficulty } + // for the exponential factor + periodCount := new(big.Int).Add(parentNumber, common.Big1) + periodCount.Div(periodCount, ExpDiffPeriod) + // the exponential factor, commonly refered to as "the bomb" // diff = diff + 2^(periodCount - 2) if periodCount.Cmp(common.Big1) > 0 { diff --git a/core/database_util.go b/core/database_util.go index 18ca1f44c6..fd2b4c3124 100644 --- a/core/database_util.go +++ b/core/database_util.go @@ -95,20 +95,6 @@ func GetHeadFastBlockHash(db ethdb.Database) common.Hash { return common.BytesToHash(data) } -// GetHeadBlockNum retrieves the block number of the current canonical head block. -func GetHeadBlockNum(db ethdb.Database) *big.Int { - data, _ := db.Get(headBlockKey) - if len(data) == 0 { - return nil - } - header := new(types.Header) - if err := rlp.Decode(bytes.NewReader(data), header); err != nil { - glog.V(logger.Error).Infof("invalid block header RLP for head block: %v", err) - return nil - } - return header.Number -} - // GetHeaderRLP retrieves a block header in its raw RLP database encoding, or nil // if the header's not found. func GetHeaderRLP(db ethdb.Database, hash common.Hash) rlp.RawValue { diff --git a/core/database_util_test.go b/core/database_util_test.go index 059f1ae9f7..6b37936358 100644 --- a/core/database_util_test.go +++ b/core/database_util_test.go @@ -62,7 +62,7 @@ func (d *diffTest) UnmarshalJSON(b []byte) (err error) { return nil } -func TestDifficulty(t *testing.T) { +func TestDifficultyFrontier(t *testing.T) { file, err := os.Open("../tests/files/BasicTests/difficulty.json") if err != nil { t.Fatal(err) @@ -77,7 +77,7 @@ func TestDifficulty(t *testing.T) { for name, test := range tests { number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1)) - diff := CalcDifficulty(test.CurrentTimestamp, test.ParentTimestamp, number, test.ParentDifficulty) + diff := calcDifficultyFrontier(test.CurrentTimestamp, test.ParentTimestamp, number, test.ParentDifficulty) if diff.Cmp(test.CurrentDifficulty) != 0 { t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff) } diff --git a/core/execution.go b/core/execution.go index 0c7e8cc9fa..24c0c93ae9 100644 --- a/core/execution.go +++ b/core/execution.go @@ -44,7 +44,6 @@ func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address originAddr := env.Origin() callerValue := caller.Value() ret, _, err = execDelegateCall(env, caller, &originAddr, &callerAddr, &addr, input, env.Db().GetCode(addr), gas, gasPrice, callerValue) - caller.SetAddress(callerAddr) return ret, err } @@ -78,15 +77,15 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A var createAccount bool if address == nil { - // Generate a new address + // Create a new account on the state nonce := env.Db().GetNonce(caller.Address()) env.Db().SetNonce(caller.Address(), nonce+1) addr = crypto.CreateAddress(caller.Address(), nonce) address = &addr createAccount = true } - snapshotPreTransfer := env.MakeSnapshot() + snapshotPreTransfer := env.MakeSnapshot() var ( from = env.Db().GetAccount(caller.Address()) to vm.Account @@ -101,24 +100,38 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A } } env.Transfer(from, to, value) - snapshotPostTransfer := env.MakeSnapshot() + // initialise a new contract and set the code that is to be used by the + // EVM. The contract is a scoped environment for this execution context + // only. contract := vm.NewContract(caller, to, value, gas, gasPrice) contract.SetCallCode(codeAddr, code) + defer contract.Finalise() ret, err = evm.Run(contract, input) - - if err != nil { - if err == vm.CodeStoreOutOfGasError { - // TODO: this is rather hacky, restore all state changes - // except sender's account nonce increment - toNonce := env.Db().GetNonce(to.Address()) - env.SetSnapshot(snapshotPostTransfer) - env.Db().SetNonce(to.Address(), toNonce) + // if the contract creation ran successfully and no errors were returned + // calculate the gas required to store the code. If the code could not + // be stored due to not enough gas set an error and let it be handled + // by the error checking condition below. + if err == nil && createAccount { + dataGas := big.NewInt(int64(len(ret))) + dataGas.Mul(dataGas, params.CreateDataGas) + if contract.UseGas(dataGas) { + env.Db().SetCode(*address, ret) } else { - env.SetSnapshot(snapshotPreTransfer) //env.Db().Set(snapshot) + err = vm.CodeStoreOutOfGasError } } + + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil && (params.IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError) { + contract.UseGas(contract.Gas) + + env.SetSnapshot(snapshotPreTransfer) + } + return ret, addr, err } @@ -131,32 +144,27 @@ func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toA return nil, common.Address{}, vm.DepthError } - if !env.CanTransfer(*originAddr, value) { - caller.ReturnGas(gas, gasPrice) - return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address())) - } - snapshot := env.MakeSnapshot() - var ( - //from = env.Db().GetAccount(*originAddr) - to vm.Account - ) + var to vm.Account if !env.Db().Exist(*toAddr) { to = env.Db().CreateAccount(*toAddr) } else { to = env.Db().GetAccount(*toAddr) } - contract := vm.NewContract(caller, to, value, gas, gasPrice) + // Iinitialise a new contract and make initialise the delegate values + contract := vm.NewContract(caller, to, value, gas, gasPrice).AsDelegate() contract.SetCallCode(codeAddr, code) - contract.DelegateCall = true + defer contract.Finalise() ret, err = evm.Run(contract, input) - if err != nil { - env.SetSnapshot(snapshot) //env.Db().Set(snapshot) + contract.UseGas(contract.Gas) + + env.SetSnapshot(snapshot) } + return ret, addr, err } diff --git a/core/state/dump.go b/core/state/dump.go index cff9c50aa3..8eb03e8e44 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -28,6 +28,7 @@ type Account struct { Nonce uint64 `json:"nonce"` Root string `json:"root"` CodeHash string `json:"codeHash"` + Code string `json:"code"` Storage map[string]string `json:"storage"` } @@ -47,7 +48,7 @@ func (self *StateDB) RawDump() World { addr := self.trie.GetKey(it.Key) stateObject, _ := DecodeObject(common.BytesToAddress(addr), self.db, it.Value) - account := Account{Balance: stateObject.balance.String(), Nonce: stateObject.nonce, Root: common.Bytes2Hex(stateObject.Root()), CodeHash: common.Bytes2Hex(stateObject.codeHash)} + account := Account{Balance: stateObject.balance.String(), Nonce: stateObject.nonce, Root: common.Bytes2Hex(stateObject.Root()), CodeHash: common.Bytes2Hex(stateObject.codeHash), Code: common.Bytes2Hex(stateObject.Code())} account.Storage = make(map[string]string) storageIt := stateObject.trie.Iterator() diff --git a/core/state/state_object.go b/core/state/state_object.go index ebc9f8358f..6095fc96a4 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -211,11 +211,6 @@ func (c *StateObject) Address() common.Address { return c.address } -// Sets the address of the contract/account -func (c *StateObject) SetAddress(addr common.Address) { - c.address = addr -} - func (self *StateObject) Trie() *trie.SecureTrie { return self.trie } @@ -247,7 +242,7 @@ func (self *StateObject) Nonce() uint64 { // as a vm.Account interface that also satisfies the vm.ContractRef // interface. Interfaces are awesome. func (self *StateObject) Value() *big.Int { - return nil + panic("Value on StateObject should never be called") } func (self *StateObject) EachStorage(cb func(key, value []byte)) { diff --git a/core/state/statedb.go b/core/state/statedb.go index e1dde84d16..22ffa36a06 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -87,18 +87,6 @@ func (self *StateDB) GetLogs(hash common.Hash) vm.Logs { return self.logs[hash] } -func (self *StateDB) GetAllLogs() *map[common.Hash]vm.Logs { - copy := make(map[common.Hash]vm.Logs, len(self.logs)) - for k, v := range self.logs { - copy[k] = v - } - return © -} - -func (self *StateDB) SetAllLogs(logs *map[common.Hash]vm.Logs) { - self.logs = *logs -} - func (self *StateDB) Logs() vm.Logs { var logs vm.Logs for _, lgs := range self.logs { @@ -107,11 +95,6 @@ func (self *StateDB) Logs() vm.Logs { return logs } -// TODO: this may not be the most proper thing -func (self *StateDB) GetDB() ethdb.Database { - return self.db -} - func (self *StateDB) AddRefund(gas *big.Int) { self.refund.Add(self.refund, gas) } diff --git a/core/state_transition.go b/core/state_transition.go index 0cd2262620..52a46c63d6 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -21,7 +21,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -225,38 +224,24 @@ func (self *StateTransition) transitionDb() (ret []byte, usedGas *big.Int, err e } vmenv := self.env - snapshot := vmenv.MakeSnapshot() - var addr common.Address + //var addr common.Address if contractCreation { - ret, addr, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value) - if err == nil { - dataGas := big.NewInt(int64(len(ret))) - dataGas.Mul(dataGas, params.CreateDataGas) - if err := self.useGas(dataGas); err == nil { - self.state.SetCode(addr, ret) - } else { - if homestead { - // rollback all contract creation changes except for - // sender's incremented account nonce and gas usage - // TODO: fucking gas hack... verify potential DoS vuln - accNonce := vmenv.Db().GetNonce(sender.Address()) - logs := vmenv.Db().(*state.StateDB).GetAllLogs() - vmenv.SetSnapshot(snapshot) - vmenv.Db().SetNonce(sender.Address(), accNonce) - vmenv.Db().(*state.StateDB).SetAllLogs(logs) - self.gas = Big0 - - } - ret = nil // does not affect consensus but useful for StateTests validations - glog.V(logger.Core).Infoln("Insufficient gas for creating code. Require", dataGas, "and have", self.gas) - } + ret, _, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value) + if homestead && err == vm.CodeStoreOutOfGasError { + self.gas = Big0 + } + + if err != nil { + ret = nil + glog.V(logger.Core).Infoln("VM create err:", err) } - glog.V(logger.Core).Infoln("VM create err:", err) } else { // Increment the nonce for the next transaction self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1) ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value) - glog.V(logger.Core).Infoln("VM call err:", err) + if err != nil { + glog.V(logger.Core).Infoln("VM call err:", err) + } } if err != nil && IsValueTransferErr(err) { diff --git a/core/transaction_pool.go b/core/tx_pool.go similarity index 98% rename from core/transaction_pool.go rename to core/tx_pool.go index a815c9ef02..b8fb4cd357 100644 --- a/core/transaction_pool.go +++ b/core/tx_pool.go @@ -71,6 +71,8 @@ type TxPool struct { mu sync.RWMutex pending map[common.Hash]*types.Transaction // processable transactions queue map[common.Address]map[common.Hash]*types.Transaction + + homestead bool } func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func() *big.Int) *TxPool { @@ -86,6 +88,7 @@ func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func( localTx: newTxSet(), events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}), } + go pool.eventLoop() return pool @@ -99,6 +102,10 @@ func (pool *TxPool) eventLoop() { switch ev := ev.Data.(type) { case ChainHeadEvent: pool.mu.Lock() + if ev.Block != nil && params.IsHomestead(ev.Block.Number()) { + pool.homestead = true + } + pool.resetState() pool.mu.Unlock() case GasPriceChanged: @@ -211,12 +218,6 @@ func (pool *TxPool) SetLocal(tx *types.Transaction) { // validateTx checks whether a transaction is valid according // to the consensus rules. func (pool *TxPool) validateTx(tx *types.Transaction) error { - // Validate sender - var ( - from common.Address - err error - ) - local := pool.localTx.contains(tx.Hash()) // Drop transactions under our own minimal accepted gas price if !local && pool.minGasPrice.Cmp(tx.GasPrice()) > 0 { @@ -228,15 +229,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error { return err } - homestead := params.IsHomestead(GetHeadBlockNum(currentState.GetDB())) - - // Validate the transaction sender and it's sig. Throw - // if the from fields is invalid. - if homestead { - from, err = tx.From() - } else { - from, err = tx.FromFrontier() - } + from, err := tx.From() if err != nil { return ErrInvalidSender } @@ -271,8 +264,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error { return ErrInsufficientFunds } - // Should supply enough intrinsic gas - intrGas := IntrinsicGas(tx.Data(), MessageCreatesContract(tx), homestead) + intrGas := IntrinsicGas(tx.Data(), MessageCreatesContract(tx), pool.homestead) if tx.Gas().Cmp(intrGas) < 0 { return ErrIntrinsicGas } diff --git a/core/transaction_pool_test.go b/core/tx_pool_test.go similarity index 100% rename from core/transaction_pool_test.go rename to core/tx_pool_test.go diff --git a/core/types/transaction.go b/core/types/transaction.go index af952e4500..0c9c1ce183 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -157,7 +157,14 @@ func (tx *Transaction) Size() common.StorageSize { return common.StorageSize(c) } -// From() caches the address, allowing it to be used regardless of +// From returns the address derived from the signature (V, R, S) using secp256k1 +// eliptic curve and an error if it failed deriving or upon an incorrect +// signature. +// +// From Uses the homestead consensus rules to determine whether the signature is +// valid. +// +// From caches the address, allowing it to be used regardless of // Frontier / Homestead. however, the first time called it runs // signature validations, so we need two versions. This makes it // easier to ensure backwards compatibility of things like package rpc @@ -168,6 +175,20 @@ func (tx *Transaction) From() (common.Address, error) { return doFrom(tx, true) } +// FromFrontier returns the address derived from the signature (V, R, S) using +// secp256k1 eliptic curve and an error if it failed deriving or upon an +// incorrect signature. +// +// FromFrantier uses the frontier consensus rules to determine whether the +// signature is valid. +// +// FromFrontier caches the address, allowing it to be used regardless of +// Frontier / Homestead. however, the first time called it runs +// signature validations, so we need two versions. This makes it +// easier to ensure backwards compatibility of things like package rpc +// where eth_getblockbynumber uses tx.From() and needs to work for +// both txs before and after the first homestead block. Signatures +// valid in homestead are a subset of valid ones in Frontier) func (tx *Transaction) FromFrontier() (common.Address, error) { return doFrom(tx, false) } diff --git a/core/vm/contract.go b/core/vm/contract.go index dac81a5292..d239952183 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -26,7 +26,6 @@ import ( type ContractRef interface { ReturnGas(*big.Int, *big.Int) Address() common.Address - SetAddress(common.Address) Value() *big.Int SetCode([]byte) EachStorage(cb func(key, value []byte)) @@ -35,8 +34,12 @@ type ContractRef interface { // Contract represents an ethereum contract in the state database. It contains // the the contract code, calling arguments. Contract implements ContractRef type Contract struct { - caller ContractRef - self ContractRef + // CallerAddress is the result of the caller which initialised this + // contract. However when the "call method" is delegated this value + // needs to be initialised to that of the caller's caller. + CallerAddress common.Address + caller ContractRef + self ContractRef jumpdests destinations // result of JUMPDEST analysis. @@ -51,9 +54,9 @@ type Contract struct { DelegateCall bool } -// Create a new context for the given data items. +// NewContract returns a new contract environment for the execution of EVM. func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.Int) *Contract { - c := &Contract{caller: caller, self: object, Args: nil} + c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil} if parent, ok := caller.(*Contract); ok { // Reuse JUMPDEST analysis from parent context if available. @@ -74,6 +77,16 @@ func NewContract(caller ContractRef, object ContractRef, value, gas, price *big. return c } +// AsDelegate sets the contract to be a delegate call and returns the current +// contract (for chaining calls) +func (c *Contract) AsDelegate() *Contract { + c.DelegateCall = true + // NOTE: caller must, at all times be a contract. It should never happen + // that caller is something other than a Contract. + c.CallerAddress = c.caller.(*Contract).CallerAddress + return c +} + // GetOp returns the n'th element in the contract's byte array func (c *Contract) GetOp(n uint64) OpCode { return OpCode(c.GetByte(n)) @@ -88,13 +101,19 @@ func (c *Contract) GetByte(n uint64) byte { return 0 } -// Return returns the given ret argument and returns any remaining gas to the -// caller -func (c *Contract) Return(ret []byte) []byte { +// Caller returns the caller of the contract. +// +// Caller will recursively call caller when the contract is a delegate +// call, including that of caller's caller. +func (c *Contract) Caller() common.Address { + return c.CallerAddress +} + +// Finalise finalises the contract and returning any remaining gas to the original +// caller. +func (c *Contract) Finalise() { // Return the remaining gas to the caller c.caller.ReturnGas(c.Gas, c.Price) - - return ret } // UseGas attempts the use gas and subtracts it and returns true on success @@ -118,11 +137,6 @@ func (c *Contract) Address() common.Address { return c.self.Address() } -// SetAddress sets the contracts address -func (c *Contract) SetAddress(addr common.Address) { - c.self.SetAddress(addr) -} - // Value returns the contracts value (sent to it from it's caller) func (c *Contract) Value() *big.Int { return c.value diff --git a/core/vm/environment.go b/core/vm/environment.go index dc60af2ca2..a58e3ba2bd 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -121,7 +121,6 @@ type Account interface { SetNonce(uint64) Balance() *big.Int Address() common.Address - SetAddress(common.Address) ReturnGas(*big.Int, *big.Int) SetCode([]byte) EachStorage(cb func(key, value []byte)) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 9d3d4e6fee..26f7671ff5 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -337,13 +337,7 @@ func opOrigin(instr instruction, pc *uint64, env Environment, contract *Contract } func opCaller(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { - var bigAddr *big.Int - if contract.DelegateCall { - bigAddr = env.Origin().Big() - } else { - bigAddr = contract.caller.Address().Big() - } - stack.push(bigAddr) + stack.push(contract.Caller().Big()) } func opCallValue(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { @@ -514,6 +508,25 @@ func opGas(instr instruction, pc *uint64, env Environment, contract *Contract, m } func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { + var ( + value = stack.pop() + offset, size = stack.pop(), stack.pop() + input = memory.Get(offset.Int64(), size.Int64()) + gas = new(big.Int).Set(contract.Gas) + ) + contract.UseGas(contract.Gas) + _, addr, suberr := env.Create(contract, input, gas, contract.Price, value) + // Push item on the stack based on the returned error. If the ruleset is + // homestead we must check for CodeStoreOutOfGasError (homestead only + // rule) and treat as an error, if the ruleset is frontier we must + // ignore this error and pretend the operation was successful. + if params.IsHomestead(env.BlockNumber()) && suberr == CodeStoreOutOfGasError { + stack.push(new(big.Int)) + } else if suberr != nil && suberr != CodeStoreOutOfGasError { + stack.push(new(big.Int)) + } else { + stack.push(addr.Big()) + } } func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) { diff --git a/core/vm/jit.go b/core/vm/jit.go index 1aa7d7ef2c..504aab5230 100644 --- a/core/vm/jit.go +++ b/core/vm/jit.go @@ -275,6 +275,11 @@ func CompileProgram(program *Program) (err error) { program.addInstr(op, pc, opGas, nil) case CREATE: program.addInstr(op, pc, opCreate, nil) + case DELEGATECALL: + // Instruction added regardless of homestead phase. + // Homestead (and execution of the opcode) is checked during + // runtime. + program.addInstr(op, pc, opDelegateCall, nil) case CALL: program.addInstr(op, pc, opCall, nil) case CALLCODE: @@ -317,10 +322,14 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env }() } + homestead := params.IsHomestead(env.BlockNumber()) for pc < uint64(len(program.instructions)) { instrCount++ instr := program.instructions[pc] + if instr.Op() == DELEGATECALL && !homestead { + return nil, fmt.Errorf("Invalid opcode 0x%x", instr.Op()) + } ret, err := instr.do(program, &pc, env, contract, mem, stack) if err != nil { @@ -328,13 +337,13 @@ func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env } if instr.halts() { - return contract.Return(ret), nil + return ret, nil } } contract.Input = nil - return contract.Return(nil), nil + return nil, nil } // validDest checks if the given distination is a valid one given the @@ -457,7 +466,6 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi gas.Add(gas, stack.data[stack.len()-1]) if op == CALL { - //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil { if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { gas.Add(gas, params.CallNewAccountGas) } @@ -470,6 +478,13 @@ func jitCalculateGasAndSize(env Environment, contract *Contract, instr instructi x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7]) y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5]) + newMemSize = common.BigMax(x, y) + case DELEGATECALL: + gas.Add(gas, stack.data[stack.len()-1]) + + x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6]) + y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4]) + newMemSize = common.BigMax(x, y) } quadMemGas(mem, newMemSize, gas) diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index 8c50ed0f5c..e8e078a463 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -127,6 +127,8 @@ type account struct{} func (account) SubBalance(amount *big.Int) {} func (account) AddBalance(amount *big.Int) {} +func (account) SetAddress(common.Address) {} +func (account) Value() *big.Int { return nil } func (account) SetBalance(*big.Int) {} func (account) SetNonce(uint64) {} func (account) Balance() *big.Int { return nil } @@ -206,3 +208,6 @@ func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) { return nil, common.Address{}, nil } +func (self *Env) DelegateCall(me ContractRef, addr common.Address, data []byte, gas, price *big.Int) ([]byte, error) { + return nil, nil +} diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 222c938546..37d7bb160f 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -1,13 +1,29 @@ package vm -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/params" +) type jumpPtr struct { fn instrFn valid bool } -var jumpTable [256]jumpPtr +type vmJumpTable [256]jumpPtr + +func (jt vmJumpTable) init(blockNumber *big.Int) { + // when initialising a new VM execution we must first check the homestead + // changes. + if params.IsHomestead(blockNumber) { + jumpTable[DELEGATECALL] = jumpPtr{opDelegateCall, true} + } else { + jumpTable[DELEGATECALL] = jumpPtr{nil, false} + } +} + +var jumpTable vmJumpTable func init() { jumpTable[ADD] = jumpPtr{opAdd, true} @@ -62,10 +78,9 @@ func init() { jumpTable[PC] = jumpPtr{nil, true} jumpTable[MSIZE] = jumpPtr{opMsize, true} jumpTable[GAS] = jumpPtr{opGas, true} - jumpTable[CREATE] = jumpPtr{nil, true} + jumpTable[CREATE] = jumpPtr{opCreate, true} jumpTable[CALL] = jumpPtr{opCall, true} jumpTable[CALLCODE] = jumpPtr{opCallCode, true} - jumpTable[DELEGATECALL] = jumpPtr{opDelegateCall, true} jumpTable[LOG0] = jumpPtr{makeLog(0), true} jumpTable[LOG1] = jumpPtr{makeLog(1), true} jumpTable[LOG2] = jumpPtr{makeLog(2), true} diff --git a/core/vm/jump_table_test.go b/core/vm/jump_table_test.go new file mode 100644 index 0000000000..98d34bef20 --- /dev/null +++ b/core/vm/jump_table_test.go @@ -0,0 +1,24 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/params" +) + +func TestInit(t *testing.T) { + params.HomesteadBlock = big.NewInt(1) + + jumpTable.init(big.NewInt(0)) + if jumpTable[DELEGATECALL].valid { + t.Error("Expected DELEGATECALL not to be present") + } + + for _, n := range []int64{1, 2, 100} { + jumpTable.init(big.NewInt(n)) + if !jumpTable[DELEGATECALL].valid { + t.Error("Expected DELEGATECALL to be present for block", n) + } + } +} diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 00593ae95d..7d861f1de9 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -404,6 +404,7 @@ var stringToOp = map[string]OpCode{ "CALLDATALOAD": CALLDATALOAD, "CALLDATASIZE": CALLDATASIZE, "CALLDATACOPY": CALLDATACOPY, + "DELEGATECALL": DELEGATECALL, "CODESIZE": CODESIZE, "CODECOPY": CODECOPY, "GASPRICE": GASPRICE, diff --git a/core/vm/vm.go b/core/vm/vm.go index 863b2cc0d0..320135ff26 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -35,6 +35,9 @@ type Vm struct { // New returns a new Vm func New(env Environment) *Vm { + // init the jump table. Also prepares the homestead changes + jumpTable.init(env.BlockNumber()) + return &Vm{env: env} } @@ -43,16 +46,6 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) { self.env.SetDepth(self.env.Depth() + 1) defer self.env.SetDepth(self.env.Depth() - 1) - // User defer pattern to check for an error and, based on the error being nil or not, use all gas and return. - defer func() { - if err != nil { - // In case of a VM exception (known exceptions) all gas consumed (panics NOT included). - contract.UseGas(contract.Gas) - - ret = contract.Return(nil) - } - }() - if contract.CodeAddr != nil { if p := Precompiled[contract.CodeAddr.Str()]; p != nil { return self.RunPrecompiled(p, input, contract) @@ -61,7 +54,7 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) { // Don't bother with the execution if there's no code. if len(contract.Code) == 0 { - return contract.Return(nil), nil + return nil, nil } var ( @@ -199,46 +192,17 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) { continue } - case CREATE: - var ( - value = stack.pop() - offset, size = stack.pop(), stack.pop() - input = mem.Get(offset.Int64(), size.Int64()) - gas = new(big.Int).Set(contract.Gas) - addr common.Address - ret []byte - suberr error - ) - contract.UseGas(contract.Gas) - ret, addr, suberr = self.env.Create(contract, input, gas, contract.Price, value) - if suberr != nil { - stack.push(new(big.Int)) - } else { - // gas < len(ret) * Createinstr.dataGas == NO_CODE - dataGas := big.NewInt(int64(len(ret))) - dataGas.Mul(dataGas, params.CreateDataGas) - if contract.UseGas(dataGas) { - self.env.Db().SetCode(addr, ret) - } else { - if params.IsHomestead(self.env.BlockNumber()) { - stack.push(new(big.Int)) - return nil, CodeStoreOutOfGasError - } - } - stack.push(addr.Big()) - } - case RETURN: offset, size := stack.pop(), stack.pop() ret := mem.GetPtr(offset.Int64(), size.Int64()) - return contract.Return(ret), nil + return ret, nil case SUICIDE: opSuicide(instruction{}, nil, self.env, contract, mem, stack) fallthrough case STOP: // Stop the contract - return contract.Return(nil), nil + return nil, nil } } } else { @@ -359,7 +323,6 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef gas.Add(gas, stack.data[stack.len()-1]) if op == CALL { - //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil { if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) { gas.Add(gas, params.CallNewAccountGas) } @@ -392,7 +355,7 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Co if contract.UseGas(gas) { ret = p.Call(input) - return contract.Return(ret), nil + return ret, nil } else { return nil, OutOfGasError } diff --git a/miner/worker.go b/miner/worker.go index 81f7b16aca..71f22ef1ce 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -593,7 +593,8 @@ func (env *Work) commitTransactions(mux *event.TypeMux, transactions types.Trans var coalescedLogs vm.Logs for _, tx := range transactions { - // We can skip err. It has already been validated in the tx pool + // Error may be ignored here. The error has already been checked + // during transaction acceptance is the transaction pool. from, _ := tx.From() // Check if it falls within margin. Txs from owned accounts are always processed. diff --git a/params/util.go b/params/util.go index 6dda7752ba..856a39e3a6 100644 --- a/params/util.go +++ b/params/util.go @@ -18,8 +18,7 @@ package params import "math/big" -// TODO: set exact block number after community consensus is reached. -var HomesteadBlock *big.Int = big.NewInt(0) +var HomesteadBlock *big.Int = big.NewInt(2000000) func IsHomestead(blockNumber *big.Int) bool { // for unit tests TODO: flip to true after homestead is live diff --git a/tests/state_test.go b/tests/state_test.go index 1ee162613d..4c1820944a 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -130,6 +130,15 @@ func TestStateTransaction(t *testing.T) { } } +func TestStateTransition(t *testing.T) { + params.HomesteadBlock = big.NewInt(1000000) + + fn := filepath.Join(stateTestDir, "stTransitionTest.json") + if err := RunStateTest(fn, StateSkipTests); err != nil { + t.Error(err) + } +} + func TestCallCreateCallCode(t *testing.T) { params.HomesteadBlock = big.NewInt(1000000)