Skip to content

Commit de8d5aa

Browse files
committed
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.
1 parent 10ed107 commit de8d5aa

File tree

10 files changed

+92
-142
lines changed

10 files changed

+92
-142
lines changed

core/block_processor.go

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,31 @@ type BlockProcessor struct {
5858
eventMux *event.TypeMux
5959
}
6060

61-
// TODO: type GasPool big.Int
62-
//
63-
// GasPool is implemented by state.StateObject. This is a historical
64-
// coincidence. Gas tracking should move out of StateObject.
65-
6661
// GasPool tracks the amount of gas available during
6762
// execution of the transactions in a block.
68-
type GasPool interface {
69-
AddGas(gas, price *big.Int)
70-
SubGas(gas, price *big.Int) error
63+
// The zero value is a pool with zero gas available.
64+
type GasPool big.Int
65+
66+
// AddGas makes gas available for execution.
67+
func (gp *GasPool) AddGas(amount *big.Int) *GasPool {
68+
i := (*big.Int)(gp)
69+
i.Add(i, amount)
70+
return gp
71+
}
72+
73+
// SubGas deducts the given amount from the pool if enough gas is
74+
// available and returns an error otherwise.
75+
func (gp *GasPool) SubGas(amount *big.Int) error {
76+
i := (*big.Int)(gp)
77+
if i.Cmp(amount) < 0 {
78+
return &GasLimitErr{Have: new(big.Int).Set(i), Want: amount}
79+
}
80+
i.Sub(i, amount)
81+
return nil
82+
}
83+
84+
func (gp *GasPool) String() string {
85+
return (*big.Int)(gp).String()
7186
}
7287

7388
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
8297
}
8398

8499
func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block *types.Block, transientProcess bool) (receipts types.Receipts, err error) {
85-
gp := statedb.GetOrNewStateObject(block.Coinbase())
86-
gp.SetGasLimit(block.GasLimit())
100+
gp := new(GasPool).AddGas(block.GasLimit())
101+
if glog.V(logger.Core) {
102+
glog.Infof("%x: gas (+ %v)", block.Coinbase(), gp)
103+
}
87104

88105
// Process the transactions on to parent state
89106
receipts, err = sm.ApplyTransactions(gp, statedb, block, block.Transactions(), transientProcess)
@@ -94,7 +111,7 @@ func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block
94111
return receipts, nil
95112
}
96113

97-
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) {
114+
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) {
98115
_, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, header), tx, gp)
99116
if err != nil {
100117
return nil, nil, err
@@ -128,7 +145,7 @@ func (self *BlockProcessor) BlockChain() *BlockChain {
128145
return self.bc
129146
}
130147

131-
func (self *BlockProcessor) ApplyTransactions(gp GasPool, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, error) {
148+
func (self *BlockProcessor) ApplyTransactions(gp *GasPool, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, error) {
132149
var (
133150
receipts types.Receipts
134151
totalUsedGas = big.NewInt(0)

core/chain_makers.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type BlockGen struct {
5454
header *types.Header
5555
statedb *state.StateDB
5656

57-
coinbase *state.StateObject
57+
gasPool *GasPool
5858
txs []*types.Transaction
5959
receipts []*types.Receipt
6060
uncles []*types.Header
@@ -63,15 +63,14 @@ type BlockGen struct {
6363
// SetCoinbase sets the coinbase of the generated block.
6464
// It can be called at most once.
6565
func (b *BlockGen) SetCoinbase(addr common.Address) {
66-
if b.coinbase != nil {
66+
if b.gasPool != nil {
6767
if len(b.txs) > 0 {
6868
panic("coinbase must be set before adding transactions")
6969
}
7070
panic("coinbase can only be set once")
7171
}
7272
b.header.Coinbase = addr
73-
b.coinbase = b.statedb.GetOrNewStateObject(addr)
74-
b.coinbase.SetGasLimit(b.header.GasLimit)
73+
b.gasPool = new(GasPool).AddGas(b.header.GasLimit)
7574
}
7675

7776
// SetExtra sets the extra data field of the generated block.
@@ -88,10 +87,10 @@ func (b *BlockGen) SetExtra(data []byte) {
8887
// added. Notably, contract code relying on the BLOCKHASH instruction
8988
// will panic during execution.
9089
func (b *BlockGen) AddTx(tx *types.Transaction) {
91-
if b.coinbase == nil {
90+
if b.gasPool == nil {
9291
b.SetCoinbase(common.Address{})
9392
}
94-
_, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.coinbase)
93+
_, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.gasPool)
9594
if err != nil {
9695
panic(err)
9796
}

core/error.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,16 @@ func IsBadHashError(err error) bool {
188188
_, ok := err.(BadHashError)
189189
return ok
190190
}
191+
192+
type GasLimitErr struct {
193+
Have, Want *big.Int
194+
}
195+
196+
func IsGasLimitErr(err error) bool {
197+
_, ok := err.(*GasLimitErr)
198+
return ok
199+
}
200+
201+
func (err *GasLimitErr) Error() string {
202+
return fmt.Sprintf("GasLimit reached. Have %d gas, transaction requires %d", err.Have, err.Want)
203+
}

core/state/errors.go

Lines changed: 0 additions & 39 deletions
This file was deleted.

core/state/state_object.go

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,6 @@ type StateObject struct {
7575
// Cached storage (flushed when updated)
7676
storage Storage
7777

78-
// Total gas pool is the total amount of gas currently
79-
// left if this object is the coinbase. Gas is directly
80-
// purchased of the coinbase.
81-
gasPool *big.Int
82-
8378
// Mark for deletion
8479
// When an object is marked for deletion it will be delete from the trie
8580
// during the "update" phase of the state transition
@@ -89,10 +84,9 @@ type StateObject struct {
8984
}
9085

9186
func NewStateObject(address common.Address, db ethdb.Database) *StateObject {
92-
object := &StateObject{db: db, address: address, balance: new(big.Int), gasPool: new(big.Int), dirty: true}
87+
object := &StateObject{db: db, address: address, balance: new(big.Int), dirty: true}
9388
object.trie, _ = trie.NewSecure(common.Hash{}, db)
9489
object.storage = make(Storage)
95-
object.gasPool = new(big.Int)
9690
return object
9791
}
9892

@@ -121,7 +115,6 @@ func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Datab
121115
object.codeHash = extobject.CodeHash
122116
object.trie = trie
123117
object.storage = make(map[string]common.Hash)
124-
object.gasPool = new(big.Int)
125118
object.code, _ = db.Get(extobject.CodeHash)
126119
return object
127120
}
@@ -209,36 +202,9 @@ func (c *StateObject) St() Storage {
209202
return c.storage
210203
}
211204

212-
//
213-
// Gas setters and getters
214-
//
215-
216205
// Return the gas back to the origin. Used by the Virtual machine or Closures
217206
func (c *StateObject) ReturnGas(gas, price *big.Int) {}
218207

219-
func (self *StateObject) SetGasLimit(gasLimit *big.Int) {
220-
self.gasPool = new(big.Int).Set(gasLimit)
221-
self.dirty = true
222-
223-
if glog.V(logger.Core) {
224-
glog.Infof("%x: gas (+ %v)", self.Address(), self.gasPool)
225-
}
226-
}
227-
228-
func (self *StateObject) SubGas(gas, price *big.Int) error {
229-
if self.gasPool.Cmp(gas) < 0 {
230-
return GasLimitError(self.gasPool, gas)
231-
}
232-
self.gasPool.Sub(self.gasPool, gas)
233-
self.dirty = true
234-
return nil
235-
}
236-
237-
func (self *StateObject) AddGas(gas, price *big.Int) {
238-
self.gasPool.Add(self.gasPool, gas)
239-
self.dirty = true
240-
}
241-
242208
func (self *StateObject) Copy() *StateObject {
243209
stateObject := NewStateObject(self.Address(), self.db)
244210
stateObject.balance.Set(self.balance)
@@ -248,7 +214,6 @@ func (self *StateObject) Copy() *StateObject {
248214
stateObject.code = common.CopyBytes(self.code)
249215
stateObject.initCode = common.CopyBytes(self.initCode)
250216
stateObject.storage = self.storage.Copy()
251-
stateObject.gasPool.Set(self.gasPool)
252217
stateObject.remove = self.remove
253218
stateObject.dirty = self.dirty
254219
stateObject.deleted = self.deleted

core/state/state_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ func TestSnapshot2(t *testing.T) {
138138
so0 := state.GetStateObject(stateobjaddr0)
139139
so0.balance = big.NewInt(42)
140140
so0.nonce = 43
141-
so0.gasPool = big.NewInt(44)
142141
so0.code = []byte{'c', 'a', 'f', 'e'}
143142
so0.codeHash = so0.CodeHash()
144143
so0.remove = true
@@ -150,7 +149,6 @@ func TestSnapshot2(t *testing.T) {
150149
so1 := state.GetStateObject(stateobjaddr1)
151150
so1.balance = big.NewInt(52)
152151
so1.nonce = 53
153-
so1.gasPool = big.NewInt(54)
154152
so1.code = []byte{'c', 'a', 'f', 'e', '2'}
155153
so1.codeHash = so1.CodeHash()
156154
so1.remove = true
@@ -207,9 +205,6 @@ func compareStateObjects(so0, so1 *StateObject, t *testing.T) {
207205
}
208206
}
209207

210-
if so0.gasPool.Cmp(so1.gasPool) != 0 {
211-
t.Fatalf("GasPool mismatch: have %v, want %v", so0.gasPool, so1.gasPool)
212-
}
213208
if so0.remove != so1.remove {
214209
t.Fatalf("Remove mismatch: have %v, want %v", so0.remove, so1.remove)
215210
}

core/state_transition.go

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,31 @@ import (
2121
"math/big"
2222

2323
"github.com/ethereum/go-ethereum/common"
24-
"github.com/ethereum/go-ethereum/core/state"
2524
"github.com/ethereum/go-ethereum/core/vm"
2625
"github.com/ethereum/go-ethereum/logger"
2726
"github.com/ethereum/go-ethereum/logger/glog"
2827
"github.com/ethereum/go-ethereum/params"
2928
)
3029

3130
/*
32-
* The State transitioning model
33-
*
34-
* A state transition is a change made when a transaction is applied to the current world state
35-
* The state transitioning model does all all the necessary work to work out a valid new state root.
36-
* 1) Nonce handling
37-
* 2) Pre pay / buy gas of the coinbase (miner)
38-
* 3) Create a new state object if the recipient is \0*32
39-
* 4) Value transfer
40-
* == If contract creation ==
41-
* 4a) Attempt to run transaction data
42-
* 4b) If valid, use result as code for the new state object
43-
* == end ==
44-
* 5) Run Script section
45-
* 6) Derive new state root
46-
*/
31+
The State Transitioning Model
32+
33+
A state transition is a change made when a transaction is applied to the current world state
34+
The state transitioning model does all all the necessary work to work out a valid new state root.
35+
36+
1) Nonce handling
37+
2) Pre pay gas
38+
3) Create a new state object if the recipient is \0*32
39+
4) Value transfer
40+
== If contract creation ==
41+
4a) Attempt to run transaction data
42+
4b) If valid, use result as code for the new state object
43+
== end ==
44+
5) Run Script section
45+
6) Derive new state root
46+
*/
4747
type StateTransition struct {
48-
gp GasPool
48+
gp *GasPool
4949
msg Message
5050
gas, gasPrice *big.Int
5151
initialGas *big.Int
@@ -94,7 +94,7 @@ func IntrinsicGas(data []byte) *big.Int {
9494
return igas
9595
}
9696

97-
func ApplyMessage(env vm.Environment, msg Message, gp GasPool) ([]byte, *big.Int, error) {
97+
func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
9898
var st = StateTransition{
9999
gp: gp,
100100
env: env,
@@ -158,7 +158,7 @@ func (self *StateTransition) buyGas() error {
158158
if sender.Balance().Cmp(mgval) < 0 {
159159
return fmt.Errorf("insufficient ETH for gas (%x). Req %v, has %v", sender.Address().Bytes()[:4], mgval, sender.Balance())
160160
}
161-
if err = self.gp.SubGas(mgas, self.gasPrice); err != nil {
161+
if err = self.gp.SubGas(mgas); err != nil {
162162
return err
163163
}
164164
self.addGas(mgas)
@@ -180,9 +180,9 @@ func (self *StateTransition) preCheck() (err error) {
180180
return NonceError(msg.Nonce(), n)
181181
}
182182

183-
// Pre-pay gas / Buy gas of the coinbase account
183+
// Pre-pay gas
184184
if err = self.buyGas(); err != nil {
185-
if state.IsGasLimitErr(err) {
185+
if IsGasLimitErr(err) {
186186
return err
187187
}
188188
return InvalidTxError(err)
@@ -246,17 +246,21 @@ func (self *StateTransition) transitionDb() (ret []byte, usedGas *big.Int, err e
246246
}
247247

248248
func (self *StateTransition) refundGas() {
249+
// Return eth for remaining gas to the sender account,
250+
// exchanged at the original rate.
249251
sender, _ := self.from() // err already checked
250-
// Return remaining gas
251252
remaining := new(big.Int).Mul(self.gas, self.gasPrice)
252253
sender.AddBalance(remaining)
253254

255+
// Apply refund counter, capped to half of the used gas.
254256
uhalf := remaining.Div(self.gasUsed(), common.Big2)
255257
refund := common.BigMin(uhalf, self.state.GetRefund())
256258
self.gas.Add(self.gas, refund)
257259
self.state.AddBalance(sender.Address(), refund.Mul(refund, self.gasPrice))
258260

259-
self.gp.AddGas(self.gas, self.gasPrice)
261+
// Also return remaining gas to the block gas counter so it is
262+
// available for the next transaction.
263+
self.gp.AddGas(self.gas)
260264
}
261265

262266
func (self *StateTransition) gasUsed() *big.Int {

0 commit comments

Comments
 (0)