From de8d5aaa92ff843e6960eb9b7bd7ec1c1ebc6608 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 2 Sep 2015 12:55:11 +0200 Subject: [PATCH] core, core/state: move gas tracking out of core/state The amount of gas available for tx execution was tracked in the StateObject representing the coinbase account. This commit makes the gas counter a separate type in package core, which avoids unintended consequences of intertwining the counter with state logic. --- core/block_processor.go | 41 ++++++++++++++++++++++--------- core/chain_makers.go | 11 ++++----- core/error.go | 13 ++++++++++ core/state/errors.go | 39 ----------------------------- core/state/state_object.go | 37 +--------------------------- core/state/state_test.go | 5 ---- core/state_transition.go | 50 ++++++++++++++++++++------------------ miner/worker.go | 25 +++++++++---------- tests/state_test_util.go | 8 +++--- xeth/xeth.go | 5 ++-- 10 files changed, 92 insertions(+), 142 deletions(-) delete mode 100644 core/state/errors.go diff --git a/core/block_processor.go b/core/block_processor.go index a07d79bcf..7032c077c 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -58,16 +58,31 @@ type BlockProcessor struct { eventMux *event.TypeMux } -// TODO: type GasPool big.Int -// -// GasPool is implemented by state.StateObject. This is a historical -// coincidence. Gas tracking should move out of StateObject. - // GasPool tracks the amount of gas available during // execution of the transactions in a block. -type GasPool interface { - AddGas(gas, price *big.Int) - SubGas(gas, price *big.Int) error +// The zero value is a pool with zero gas available. +type GasPool big.Int + +// AddGas makes gas available for execution. +func (gp *GasPool) AddGas(amount *big.Int) *GasPool { + i := (*big.Int)(gp) + i.Add(i, amount) + return gp +} + +// SubGas deducts the given amount from the pool if enough gas is +// available and returns an error otherwise. +func (gp *GasPool) SubGas(amount *big.Int) error { + i := (*big.Int)(gp) + if i.Cmp(amount) < 0 { + return &GasLimitErr{Have: new(big.Int).Set(i), Want: amount} + } + i.Sub(i, amount) + return nil +} + +func (gp *GasPool) String() string { + return (*big.Int)(gp).String() } func NewBlockProcessor(db ethdb.Database, pow pow.PoW, blockchain *BlockChain, eventMux *event.TypeMux) *BlockProcessor { @@ -82,8 +97,10 @@ func NewBlockProcessor(db ethdb.Database, pow pow.PoW, blockchain *BlockChain, e } func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block *types.Block, transientProcess bool) (receipts types.Receipts, err error) { - gp := statedb.GetOrNewStateObject(block.Coinbase()) - gp.SetGasLimit(block.GasLimit()) + gp := new(GasPool).AddGas(block.GasLimit()) + if glog.V(logger.Core) { + glog.Infof("%x: gas (+ %v)", block.Coinbase(), gp) + } // Process the transactions on to parent state receipts, err = sm.ApplyTransactions(gp, statedb, block, block.Transactions(), transientProcess) @@ -94,7 +111,7 @@ func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block return receipts, nil } -func (self *BlockProcessor) ApplyTransaction(gp GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) { +func (self *BlockProcessor) ApplyTransaction(gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) { _, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, header), tx, gp) if err != nil { return nil, nil, err @@ -128,7 +145,7 @@ func (self *BlockProcessor) BlockChain() *BlockChain { return self.bc } -func (self *BlockProcessor) ApplyTransactions(gp GasPool, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, error) { +func (self *BlockProcessor) ApplyTransactions(gp *GasPool, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, error) { var ( receipts types.Receipts totalUsedGas = big.NewInt(0) diff --git a/core/chain_makers.go b/core/chain_makers.go index 85a4955db..e20a05c7d 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -54,7 +54,7 @@ type BlockGen struct { header *types.Header statedb *state.StateDB - coinbase *state.StateObject + gasPool *GasPool txs []*types.Transaction receipts []*types.Receipt uncles []*types.Header @@ -63,15 +63,14 @@ type BlockGen struct { // SetCoinbase sets the coinbase of the generated block. // It can be called at most once. func (b *BlockGen) SetCoinbase(addr common.Address) { - if b.coinbase != nil { + if b.gasPool != nil { if len(b.txs) > 0 { panic("coinbase must be set before adding transactions") } panic("coinbase can only be set once") } b.header.Coinbase = addr - b.coinbase = b.statedb.GetOrNewStateObject(addr) - b.coinbase.SetGasLimit(b.header.GasLimit) + b.gasPool = new(GasPool).AddGas(b.header.GasLimit) } // SetExtra sets the extra data field of the generated block. @@ -88,10 +87,10 @@ func (b *BlockGen) SetExtra(data []byte) { // added. Notably, contract code relying on the BLOCKHASH instruction // will panic during execution. func (b *BlockGen) AddTx(tx *types.Transaction) { - if b.coinbase == nil { + if b.gasPool == nil { b.SetCoinbase(common.Address{}) } - _, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.coinbase) + _, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.gasPool) if err != nil { panic(err) } diff --git a/core/error.go b/core/error.go index 5e32124a7..6498194cd 100644 --- a/core/error.go +++ b/core/error.go @@ -188,3 +188,16 @@ func IsBadHashError(err error) bool { _, ok := err.(BadHashError) return ok } + +type GasLimitErr struct { + Have, Want *big.Int +} + +func IsGasLimitErr(err error) bool { + _, ok := err.(*GasLimitErr) + return ok +} + +func (err *GasLimitErr) Error() string { + return fmt.Sprintf("GasLimit reached. Have %d gas, transaction requires %d", err.Have, err.Want) +} diff --git a/core/state/errors.go b/core/state/errors.go deleted file mode 100644 index a08ed2d23..000000000 --- a/core/state/errors.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package state - -import ( - "fmt" - "math/big" -) - -type GasLimitErr struct { - Message string - Is, Max *big.Int -} - -func IsGasLimitErr(err error) bool { - _, ok := err.(*GasLimitErr) - - return ok -} -func (err *GasLimitErr) Error() string { - return err.Message -} -func GasLimitError(is, max *big.Int) *GasLimitErr { - return &GasLimitErr{Message: fmt.Sprintf("GasLimit error. Max %s, transaction would take it to %s", max, is), Is: is, Max: max} -} diff --git a/core/state/state_object.go b/core/state/state_object.go index 40af9ed9c..c06e3d227 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -75,11 +75,6 @@ type StateObject struct { // Cached storage (flushed when updated) storage Storage - // Total gas pool is the total amount of gas currently - // left if this object is the coinbase. Gas is directly - // purchased of the coinbase. - gasPool *big.Int - // Mark for deletion // When an object is marked for deletion it will be delete from the trie // during the "update" phase of the state transition @@ -89,10 +84,9 @@ type StateObject struct { } func NewStateObject(address common.Address, db ethdb.Database) *StateObject { - object := &StateObject{db: db, address: address, balance: new(big.Int), gasPool: new(big.Int), dirty: true} + object := &StateObject{db: db, address: address, balance: new(big.Int), dirty: true} object.trie, _ = trie.NewSecure(common.Hash{}, db) object.storage = make(Storage) - object.gasPool = new(big.Int) return object } @@ -121,7 +115,6 @@ func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Datab object.codeHash = extobject.CodeHash object.trie = trie object.storage = make(map[string]common.Hash) - object.gasPool = new(big.Int) object.code, _ = db.Get(extobject.CodeHash) return object } @@ -209,36 +202,9 @@ func (c *StateObject) St() Storage { return c.storage } -// -// Gas setters and getters -// - // Return the gas back to the origin. Used by the Virtual machine or Closures func (c *StateObject) ReturnGas(gas, price *big.Int) {} -func (self *StateObject) SetGasLimit(gasLimit *big.Int) { - self.gasPool = new(big.Int).Set(gasLimit) - self.dirty = true - - if glog.V(logger.Core) { - glog.Infof("%x: gas (+ %v)", self.Address(), self.gasPool) - } -} - -func (self *StateObject) SubGas(gas, price *big.Int) error { - if self.gasPool.Cmp(gas) < 0 { - return GasLimitError(self.gasPool, gas) - } - self.gasPool.Sub(self.gasPool, gas) - self.dirty = true - return nil -} - -func (self *StateObject) AddGas(gas, price *big.Int) { - self.gasPool.Add(self.gasPool, gas) - self.dirty = true -} - func (self *StateObject) Copy() *StateObject { stateObject := NewStateObject(self.Address(), self.db) stateObject.balance.Set(self.balance) @@ -248,7 +214,6 @@ func (self *StateObject) Copy() *StateObject { stateObject.code = common.CopyBytes(self.code) stateObject.initCode = common.CopyBytes(self.initCode) stateObject.storage = self.storage.Copy() - stateObject.gasPool.Set(self.gasPool) stateObject.remove = self.remove stateObject.dirty = self.dirty stateObject.deleted = self.deleted diff --git a/core/state/state_test.go b/core/state/state_test.go index 08fbc47fa..7ddbe11a1 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -138,7 +138,6 @@ func TestSnapshot2(t *testing.T) { so0 := state.GetStateObject(stateobjaddr0) so0.balance = big.NewInt(42) so0.nonce = 43 - so0.gasPool = big.NewInt(44) so0.code = []byte{'c', 'a', 'f', 'e'} so0.codeHash = so0.CodeHash() so0.remove = true @@ -150,7 +149,6 @@ func TestSnapshot2(t *testing.T) { so1 := state.GetStateObject(stateobjaddr1) so1.balance = big.NewInt(52) so1.nonce = 53 - so1.gasPool = big.NewInt(54) so1.code = []byte{'c', 'a', 'f', 'e', '2'} so1.codeHash = so1.CodeHash() so1.remove = true @@ -207,9 +205,6 @@ func compareStateObjects(so0, so1 *StateObject, t *testing.T) { } } - if so0.gasPool.Cmp(so1.gasPool) != 0 { - t.Fatalf("GasPool mismatch: have %v, want %v", so0.gasPool, so1.gasPool) - } if so0.remove != so1.remove { t.Fatalf("Remove mismatch: have %v, want %v", so0.remove, so1.remove) } diff --git a/core/state_transition.go b/core/state_transition.go index e83019229..7ecc01d4c 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" @@ -29,23 +28,24 @@ import ( ) /* - * The State transitioning model - * - * A state transition is a change made when a transaction is applied to the current world state - * The state transitioning model does all all the necessary work to work out a valid new state root. - * 1) Nonce handling - * 2) Pre pay / buy gas of the coinbase (miner) - * 3) Create a new state object if the recipient is \0*32 - * 4) Value transfer - * == If contract creation == - * 4a) Attempt to run transaction data - * 4b) If valid, use result as code for the new state object - * == end == - * 5) Run Script section - * 6) Derive new state root - */ +The State Transitioning Model + +A state transition is a change made when a transaction is applied to the current world state +The state transitioning model does all all the necessary work to work out a valid new state root. + +1) Nonce handling +2) Pre pay gas +3) Create a new state object if the recipient is \0*32 +4) Value transfer +== If contract creation == + 4a) Attempt to run transaction data + 4b) If valid, use result as code for the new state object +== end == +5) Run Script section +6) Derive new state root +*/ type StateTransition struct { - gp GasPool + gp *GasPool msg Message gas, gasPrice *big.Int initialGas *big.Int @@ -94,7 +94,7 @@ func IntrinsicGas(data []byte) *big.Int { return igas } -func ApplyMessage(env vm.Environment, msg Message, gp GasPool) ([]byte, *big.Int, error) { +func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) { var st = StateTransition{ gp: gp, env: env, @@ -158,7 +158,7 @@ func (self *StateTransition) buyGas() error { if sender.Balance().Cmp(mgval) < 0 { return fmt.Errorf("insufficient ETH for gas (%x). Req %v, has %v", sender.Address().Bytes()[:4], mgval, sender.Balance()) } - if err = self.gp.SubGas(mgas, self.gasPrice); err != nil { + if err = self.gp.SubGas(mgas); err != nil { return err } self.addGas(mgas) @@ -180,9 +180,9 @@ func (self *StateTransition) preCheck() (err error) { return NonceError(msg.Nonce(), n) } - // Pre-pay gas / Buy gas of the coinbase account + // Pre-pay gas if err = self.buyGas(); err != nil { - if state.IsGasLimitErr(err) { + if IsGasLimitErr(err) { return err } return InvalidTxError(err) @@ -246,17 +246,21 @@ func (self *StateTransition) transitionDb() (ret []byte, usedGas *big.Int, err e } func (self *StateTransition) refundGas() { + // Return eth for remaining gas to the sender account, + // exchanged at the original rate. sender, _ := self.from() // err already checked - // Return remaining gas remaining := new(big.Int).Mul(self.gas, self.gasPrice) sender.AddBalance(remaining) + // Apply refund counter, capped to half of the used gas. uhalf := remaining.Div(self.gasUsed(), common.Big2) refund := common.BigMin(uhalf, self.state.GetRefund()) self.gas.Add(self.gas, refund) self.state.AddBalance(sender.Address(), refund.Mul(refund, self.gasPrice)) - self.gp.AddGas(self.gas, self.gasPrice) + // Also return remaining gas to the block gas counter so it is + // available for the next transaction. + self.gp.AddGas(self.gas) } func (self *StateTransition) gasUsed() *big.Int { diff --git a/miner/worker.go b/miner/worker.go index 5bce32f21..d827cb97d 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -62,13 +62,12 @@ type uint64RingBuffer struct { // environment is the workers current environment and holds // all of the current state information type Work struct { - state *state.StateDB // apply state changes here - coinbase *state.StateObject // the miner's account - ancestors *set.Set // ancestor set (used for checking uncle parent validity) - family *set.Set // family set (used for checking uncle invalidity) - uncles *set.Set // uncle set - remove *set.Set // tx which will be removed - tcount int // tx count in cycle + state *state.StateDB // apply state changes here + ancestors *set.Set // ancestor set (used for checking uncle parent validity) + family *set.Set // family set (used for checking uncle invalidity) + uncles *set.Set // uncle set + remove *set.Set // tx which will be removed + tcount int // tx count in cycle ignoredTransactors *set.Set lowGasTransactors *set.Set ownedAccounts *set.Set @@ -366,7 +365,6 @@ func (self *worker) makeCurrent(parent *types.Block, header *types.Header) error family: set.New(), uncles: set.New(), header: header, - coinbase: state.GetOrNewStateObject(self.coinbase), createdAt: time.Now(), } @@ -514,7 +512,6 @@ func (self *worker) commitNewWork() { transactions := append(singleTxOwner, multiTxOwner...) */ - work.coinbase.SetGasLimit(header.GasLimit) work.commitTransactions(transactions, self.gasPrice, self.proc) self.eth.TxPool().RemoveTransactions(work.lowGasTxs) @@ -575,6 +572,8 @@ func (self *worker) commitUncle(work *Work, uncle *types.Header) error { } func (env *Work) commitTransactions(transactions types.Transactions, gasPrice *big.Int, proc *core.BlockProcessor) { + gp := new(core.GasPool).AddGas(env.header.GasLimit) + for _, tx := range transactions { // We can skip err. It has already been validated in the tx pool from, _ := tx.From() @@ -612,9 +611,9 @@ func (env *Work) commitTransactions(transactions types.Transactions, gasPrice *b env.state.StartRecord(tx.Hash(), common.Hash{}, 0) - err := env.commitTransaction(tx, proc) + err := env.commitTransaction(tx, proc, gp) switch { - case state.IsGasLimitErr(err): + case core.IsGasLimitErr(err): // ignore the transactor so no nonce errors will be thrown for this account // next time the worker is run, they'll be picked up again. env.ignoredTransactors.Add(from) @@ -632,9 +631,9 @@ func (env *Work) commitTransactions(transactions types.Transactions, gasPrice *b } } -func (env *Work) commitTransaction(tx *types.Transaction, proc *core.BlockProcessor) error { +func (env *Work) commitTransaction(tx *types.Transaction, proc *core.BlockProcessor, gp *core.GasPool) error { snap := env.state.Copy() - receipt, _, err := proc.ApplyTransaction(env.coinbase, env.state, env.header, tx, env.header.GasUsed, true) + receipt, _, err := proc.ApplyTransaction(gp, env.state, env.header, tx, env.header.GasUsed, true) if err != nil { env.state.Set(snap) return err diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 676d9ed8c..352fe3570 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -223,7 +223,6 @@ func RunState(statedb *state.StateDB, env, tx map[string]string) ([]byte, vm.Log price = common.Big(tx["gasPrice"]) value = common.Big(tx["value"]) nonce = common.Big(tx["nonce"]).Uint64() - caddr = common.HexToAddress(env["currentCoinbase"]) ) var to *common.Address @@ -235,16 +234,15 @@ func RunState(statedb *state.StateDB, env, tx map[string]string) ([]byte, vm.Log vm.Precompiled = vm.PrecompiledContracts() snapshot := statedb.Copy() - coinbase := statedb.GetOrNewStateObject(caddr) - coinbase.SetGasLimit(common.Big(env["currentGasLimit"])) + gaspool := new(core.GasPool).AddGas(common.Big(env["currentGasLimit"])) key, _ := hex.DecodeString(tx["secretKey"]) addr := crypto.PubkeyToAddress(crypto.ToECDSA(key).PublicKey) message := NewMessage(addr, to, data, value, gas, price, nonce) vmenv := NewEnvFromMap(statedb, env, tx) vmenv.origin = addr - ret, _, err := core.ApplyMessage(vmenv, message, coinbase) - if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || state.IsGasLimitErr(err) { + ret, _, err := core.ApplyMessage(vmenv, message, gaspool) + if core.IsNonceErr(err) || core.IsInvalidTxErr(err) || core.IsGasLimitErr(err) { statedb.Set(snapshot) } statedb.Commit() diff --git a/xeth/xeth.go b/xeth/xeth.go index baa8314ad..1cb072f0d 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -850,7 +850,6 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st } from.SetBalance(common.MaxBig) - from.SetGasLimit(common.MaxBig) msg := callmsg{ from: from, @@ -874,8 +873,8 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st header := self.CurrentBlock().Header() vmenv := core.NewEnv(statedb, self.backend.BlockChain(), msg, header) - - res, gas, err := core.ApplyMessage(vmenv, msg, from) + gp := new(core.GasPool).AddGas(common.MaxBig) + res, gas, err := core.ApplyMessage(vmenv, msg, gp) return common.ToHex(res), gas.String(), err }