/
gas_power_check.go
178 lines (147 loc) · 4.64 KB
/
gas_power_check.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package gaspowercheck
import (
"errors"
"math/big"
"time"
"github.com/Fantom-foundation/lachesis-base/eventcheck/epochcheck"
"github.com/Fantom-foundation/lachesis-base/inter/idx"
"github.com/Fantom-foundation/lachesis-base/inter/pos"
"github.com/Ecosystem-Knowledge/go-ecoterium/inter"
)
var (
// ErrWrongGasPowerLeft indicates that event's GasPowerLeft is miscalculated.
ErrWrongGasPowerLeft = errors.New("event has wrong GasPowerLeft")
)
type ValidatorState struct {
PrevEpochEvent inter.EventI
GasRefund uint64
}
// ValidationContext for gaspower checking
type ValidationContext struct {
Epoch idx.Epoch
Configs [inter.GasPowerConfigs]Config
EpochStart inter.Timestamp
Validators *pos.Validators
ValidatorStates []ValidatorState
}
// Reader is accessed by the validator to get the current state.
type Reader interface {
GetValidationContext() *ValidationContext
}
// Config for gaspower checking. There'll be 2 different configs for short-term and long-term gas power checks.
type Config struct {
Idx int
AllocPerSec uint64
MaxAllocPeriod inter.Timestamp
MinEnsuredAlloc uint64
StartupAllocPeriod inter.Timestamp
MinStartupGas uint64
}
// Checker which checks gas power
type Checker struct {
reader Reader
}
// New Checker for gas power
func New(reader Reader) *Checker {
return &Checker{
reader: reader,
}
}
func mul(a *big.Int, b uint64) {
a.Mul(a, new(big.Int).SetUint64(b))
}
func div(a *big.Int, b uint64) {
a.Div(a, new(big.Int).SetUint64(b))
}
// CalcGasPower calculates available gas power for the event, i.e. how many gas its content may consume
func (v *Checker) CalcGasPower(e inter.EventI, selfParent inter.EventI) (inter.GasPowerLeft, error) {
ctx := v.reader.GetValidationContext()
// check that all the data is for the same epoch
if ctx.Epoch != e.Epoch() {
return inter.GasPowerLeft{}, epochcheck.ErrNotRelevant
}
var res inter.GasPowerLeft
for i := range ctx.Configs {
res.Gas[i] = calcGasPower(e, selfParent, ctx, ctx.Configs[i])
}
return res, nil
}
func calcGasPower(e inter.EventI, selfParent inter.EventI, ctx *ValidationContext, config Config) uint64 {
var prevGasPowerLeft uint64
var prevTime inter.Timestamp
if e.SelfParent() != nil {
prevGasPowerLeft = selfParent.GasPowerLeft().Gas[config.Idx]
prevTime = selfParent.MedianTime()
} else {
validatorState := ctx.ValidatorStates[ctx.Validators.GetIdx(e.Creator())]
if validatorState.PrevEpochEvent != nil {
prevGasPowerLeft = validatorState.PrevEpochEvent.GasPowerLeft().Gas[config.Idx]
prevTime = validatorState.PrevEpochEvent.MedianTime()
} else {
prevGasPowerLeft = 0
prevTime = ctx.EpochStart
}
prevGasPowerLeft += validatorState.GasRefund
}
return CalcValidatorGasPower(e, e.MedianTime(), prevTime, prevGasPowerLeft, ctx.Validators, config)
}
func CalcValidatorGasPower(e inter.EventI, eTime, prevTime inter.Timestamp, prevGasPowerLeft uint64, validators *pos.Validators, config Config) uint64 {
gasPowerPerSec, maxGasPower, startup := calcValidatorGasPowerPerSec(e.Creator(), validators, config)
if e.SelfParent() == nil {
if prevGasPowerLeft < startup {
prevGasPowerLeft = startup
}
}
if prevTime > eTime {
prevTime = eTime
}
gasPowerAllocatedBn := new(big.Int).SetUint64(uint64(eTime - prevTime))
mul(gasPowerAllocatedBn, gasPowerPerSec)
div(gasPowerAllocatedBn, uint64(time.Second))
gasPower := gasPowerAllocatedBn.Uint64() + prevGasPowerLeft
if gasPower > maxGasPower {
gasPower = maxGasPower
}
return gasPower
}
func calcValidatorGasPowerPerSec(
validator idx.ValidatorID,
validators *pos.Validators,
config Config,
) (
perSec uint64,
maxGasPower uint64,
startup uint64,
) {
stake := validators.Get(validator)
if stake == 0 {
return 0, 0, 0
}
gas := config
validatorGasPowerPerSecBn := new(big.Int).SetUint64(gas.AllocPerSec)
mul(validatorGasPowerPerSecBn, uint64(stake))
div(validatorGasPowerPerSecBn, uint64(validators.TotalWeight()))
perSec = validatorGasPowerPerSecBn.Uint64()
maxGasPower = perSec * (uint64(gas.MaxAllocPeriod) / uint64(time.Second))
if maxGasPower < gas.MinEnsuredAlloc {
maxGasPower = gas.MinEnsuredAlloc
}
startup = perSec * (uint64(gas.StartupAllocPeriod) / uint64(time.Second))
if startup < gas.MinStartupGas {
startup = gas.MinStartupGas
}
return
}
// Validate event
func (v *Checker) Validate(e inter.EventI, selfParent inter.EventI) error {
gasPowers, err := v.CalcGasPower(e, selfParent)
if err != nil {
return err
}
for i := range gasPowers.Gas {
if e.GasPowerLeft().Gas[i]+e.GasPowerUsed() != gasPowers.Gas[i] { // GasPowerUsed is checked in basic_check
return ErrWrongGasPowerLeft
}
}
return nil
}