-
Notifications
You must be signed in to change notification settings - Fork 107
/
gas.go
158 lines (128 loc) · 3.99 KB
/
gas.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package api
import (
"errors"
"fmt"
"math"
"github.com/oasisprotocol/oasis-core/go/consensus/api/transaction"
)
var (
// ErrGasOverflow is the error returned if the gas counter would
// overflow.
ErrGasOverflow = errors.New("gas overflow")
// ErrOutOfGas is the error returned if the caller is out of gas.
ErrOutOfGas = errors.New("out of gas")
)
// GasAccountant is a gas accountant interface.
type GasAccountant interface {
// UseGas attempts the use the given amount of gas. If the limit is
// reached this method will return ErrOutOfGas.
//
// The actual amount defined by the costs map will be multiplied by
// the given multiplier which must be a positive value.
UseGas(multiplier int, op transaction.Op, costs transaction.Costs) error
// GasWanted returns the amount of gas wanted.
GasWanted() transaction.Gas
// GasUsed returns the amount of gas used so far.
GasUsed() transaction.Gas
}
type basicGasAccountant struct {
maxUsedGas transaction.Gas
usedGas transaction.Gas
}
func (ga *basicGasAccountant) UseGas(multiplier int, op transaction.Op, costs transaction.Costs) error {
if multiplier < 0 {
panic("gas: multiplier must be >= 0")
}
amount, ok := costs[op]
if !ok {
return nil
}
amount = amount * transaction.Gas(multiplier)
// Check for overflow.
if math.MaxUint64-ga.usedGas < amount {
return ErrGasOverflow
}
if ga.usedGas+amount > ga.maxUsedGas {
return fmt.Errorf("%w (limit: %d wanted: %d)", ErrOutOfGas, ga.maxUsedGas, ga.usedGas+amount)
}
ga.usedGas += amount
return nil
}
func (ga *basicGasAccountant) GasWanted() transaction.Gas {
return ga.maxUsedGas
}
func (ga *basicGasAccountant) GasUsed() transaction.Gas {
return ga.usedGas
}
// NewGasAccountant creates a basic gas accountant.
//
// The gas accountant is not safe for concurrent use.
func NewGasAccountant(maxUsedGas transaction.Gas) GasAccountant {
return &basicGasAccountant{maxUsedGas: maxUsedGas}
}
type nopGasAccountant struct{}
func (ga *nopGasAccountant) UseGas(multiplier int, op transaction.Op, costs transaction.Costs) error {
if multiplier < 0 {
panic("gas: multiplier must be >= 0")
}
return nil
}
func (ga *nopGasAccountant) GasWanted() transaction.Gas {
return 0
}
func (ga *nopGasAccountant) GasUsed() transaction.Gas {
return 0
}
// Always use the same global no-op gas accountant instance to make it easier to check whether a
// no-op gas accountant is being used.
var nopGasAccountantImpl = &nopGasAccountant{}
// NewNopGasAccountant creates a no-op gas accountant that doesn't
// do any accounting.
func NewNopGasAccountant() GasAccountant {
return nopGasAccountantImpl
}
// GasAccountantKey is the gas accountant block context key.
type GasAccountantKey struct{}
// NewDefault returns a new default value for the given key.
func (gak GasAccountantKey) NewDefault() interface{} {
// This should never be called as a gas accountant must always
// be created by the application multiplexer.
panic("gas: no gas accountant in block context")
}
type compositeGasAccountant struct {
accts []GasAccountant
}
func (ga *compositeGasAccountant) UseGas(multiplier int, op transaction.Op, costs transaction.Costs) error {
if multiplier < 0 {
panic("gas: multiplier must be >= 0")
}
for _, a := range ga.accts {
if err := a.UseGas(multiplier, op, costs); err != nil {
return err
}
}
return nil
}
func (ga *compositeGasAccountant) GasWanted() transaction.Gas {
if len(ga.accts) == 0 {
return 0
}
return ga.accts[0].GasWanted()
}
func (ga *compositeGasAccountant) GasUsed() transaction.Gas {
var max transaction.Gas
for _, a := range ga.accts {
if g := a.GasUsed(); g > max {
max = g
}
}
return max
}
// NewCompositeGasAccountant creates a gas accountant that is composed
// of multiple gas accountants. Any gas used is dispatched to all
// accountants and if any returns an error, the error is propagated.
//
// The first accountant is used for GasWanted reporting.
func NewCompositeGasAccountant(accts ...GasAccountant) GasAccountant {
return &compositeGasAccountant{accts}
}