/
gas_power_check.go
158 lines (131 loc) · 4.35 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
package gaspowercheck
import (
"errors"
"math/big"
"time"
"github.com/Fantom-foundation/lachesis-ex/eventcheck/epochcheck"
"github.com/Fantom-foundation/lachesis-ex/inter"
"github.com/Fantom-foundation/lachesis-ex/inter/idx"
"github.com/Fantom-foundation/lachesis-ex/inter/pos"
)
var (
// ErrWrongGasPowerLeft indicates that event's GasPowerLeft is miscalculated.
ErrWrongGasPowerLeft = errors.New("event has wrong GasPowerLeft")
)
// ValidationContext for gaspower checking
type ValidationContext struct {
Epoch idx.Epoch
Configs [2]Config
Validators *pos.Validators
PrevEpochLastHeaders inter.HeadersByCreator
PrevEpochEndTime inter.Timestamp
PrevEpochRefunds map[idx.StakerID]uint64
}
// DagReader is accessed by the validator to get the current state.
type DagReader 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
StartupAllocPeriod inter.Timestamp
MinStartupGas uint64
}
// Checker which checks gas power
type Checker struct {
reader DagReader
}
// New Checker for gas power
func New(reader DagReader) *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))
}
func calcValidatorGasPowerPerSec(
validator idx.StakerID,
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.TotalStake()))
perSec = validatorGasPowerPerSecBn.Uint64()
maxGasPower = perSec * (uint64(gas.MaxAllocPeriod) / uint64(time.Second))
startup = perSec * (uint64(gas.StartupAllocPeriod) / uint64(time.Second))
if startup < gas.MinStartupGas {
startup = gas.MinStartupGas
}
return
}
// CalcGasPower calculates available gas power for the event, i.e. how many gas its content may consume
func (v *Checker) CalcGasPower(e *inter.EventHeaderData, selfParent *inter.EventHeaderData) (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.EventHeaderData, selfParent *inter.EventHeaderData, ctx *ValidationContext, config *Config) uint64 {
gasPowerPerSec, maxGasPower, startup := calcValidatorGasPowerPerSec(e.Creator, ctx.Validators, config)
var prevGasPowerLeft uint64
var prevMedianTime inter.Timestamp
if e.SelfParent() != nil {
prevGasPowerLeft = selfParent.GasPowerLeft.Gas[config.Idx]
prevMedianTime = selfParent.MedianTime
} else if prevConfirmedHeader := ctx.PrevEpochLastHeaders[e.Creator]; prevConfirmedHeader != nil {
prevGasPowerLeft = prevConfirmedHeader.GasPowerLeft.Gas[config.Idx] + ctx.PrevEpochRefunds[e.Creator]
if prevGasPowerLeft < startup {
prevGasPowerLeft = startup
}
prevMedianTime = prevConfirmedHeader.MedianTime
} else {
prevGasPowerLeft = startup + ctx.PrevEpochRefunds[e.Creator]
prevMedianTime = ctx.PrevEpochEndTime
}
if prevMedianTime > e.MedianTime {
prevMedianTime = e.MedianTime // do not change e.MedianTime
}
gasPowerAllocatedBn := new(big.Int).SetUint64(uint64(e.MedianTime - prevMedianTime))
mul(gasPowerAllocatedBn, gasPowerPerSec)
div(gasPowerAllocatedBn, uint64(time.Second))
gasPower := gasPowerAllocatedBn.Uint64() + prevGasPowerLeft
if gasPower > maxGasPower {
gasPower = maxGasPower
}
return gasPower
}
// Validate event
func (v *Checker) Validate(e *inter.Event, selfParent *inter.EventHeaderData) error {
gasPowers, err := v.CalcGasPower(&e.EventHeaderData, 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
}