Skip to content

Commit

Permalink
core, core/state: move gas tracking out of core/state
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fjl committed Oct 17, 2015
1 parent 10ed107 commit de8d5aa
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 142 deletions.
41 changes: 29 additions & 12 deletions core/block_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 5 additions & 6 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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)
}
Expand Down
13 changes: 13 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
39 changes: 0 additions & 39 deletions core/state/errors.go

This file was deleted.

37 changes: 1 addition & 36 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
5 changes: 0 additions & 5 deletions core/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
50 changes: 27 additions & 23 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,31 @@ 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"
"github.com/ethereum/go-ethereum/params"
)

/*
* 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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit de8d5aa

Please sign in to comment.