浏览代码

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.
Felix Lange 10 年之前
父节点
当前提交
de8d5aaa92
共有 10 个文件被更改,包括 92 次插入142 次删除
  1. 29 12
      core/block_processor.go
  2. 5 6
      core/chain_makers.go
  3. 13 0
      core/error.go
  4. 0 39
      core/state/errors.go
  5. 1 36
      core/state/state_object.go
  6. 0 5
      core/state/state_test.go
  7. 27 23
      core/state_transition.go
  8. 12 13
      miner/worker.go
  9. 3 5
      tests/state_test_util.go
  10. 2 3
      xeth/xeth.go

+ 29 - 12
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)

+ 5 - 6
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)
 	}

+ 13 - 0
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)
+}

+ 0 - 39
core/state/errors.go

@@ -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 <http://www.gnu.org/licenses/>.
-
-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}
-}

+ 1 - 36
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

+ 0 - 5
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)
 	}

+ 27 - 23
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 {

+ 12 - 13
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

+ 3 - 5
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()

+ 2 - 3
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
 }