-
Notifications
You must be signed in to change notification settings - Fork 199
/
block.go
168 lines (138 loc) · 5.24 KB
/
block.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
package throttle
import (
"sync"
"github.com/ElrondNetwork/elrond-go-logger"
"github.com/ElrondNetwork/elrond-go/core"
"github.com/ElrondNetwork/elrond-go/process"
)
var _ process.BlockSizeThrottler = (*blockSizeThrottle)(nil)
var log = logger.GetOrCreate("process/throttle")
const (
jumpAbovePercent = 75
jumpBelowPercent = 75
jumpAboveFactor = 0.5
jumpBelowFactor = 0.5
maxNumOfStatistics = 600
numOfStatisticsToRemove = 100
)
type blockInfo struct {
succeed bool
round uint64
size uint32
currentMaxSize uint32
}
// blockSizeThrottle implements BlockSizeThrottler interface which throttle block size
type blockSizeThrottle struct {
statistics []*blockInfo
currentMaxSize uint32
mutThrottler sync.RWMutex
minSize uint32
maxSize uint32
}
// NewBlockSizeThrottle creates a new blockSizeThrottle object
func NewBlockSizeThrottle(minSize uint32, maxSize uint32) (*blockSizeThrottle, error) {
bst := blockSizeThrottle{
statistics: make([]*blockInfo, 0),
currentMaxSize: maxSize,
minSize: minSize,
maxSize: maxSize,
}
return &bst, nil
}
// GetCurrentMaxSize gets the current max size in bytes which could be used in one block, taking into consideration the previous results
func (bst *blockSizeThrottle) GetCurrentMaxSize() uint32 {
bst.mutThrottler.RLock()
currentMaxSize := bst.currentMaxSize
bst.mutThrottler.RUnlock()
return currentMaxSize
}
// Add adds the new size for last block which has been sent in the given round
func (bst *blockSizeThrottle) Add(round uint64, size uint32) {
bst.mutThrottler.Lock()
bst.statistics = append(
bst.statistics,
&blockInfo{round: round, size: size, currentMaxSize: bst.currentMaxSize},
)
if len(bst.statistics) > maxNumOfStatistics {
bst.statistics = bst.statistics[numOfStatisticsToRemove:]
}
bst.mutThrottler.Unlock()
}
// Succeed sets the state of the last block which has been sent in the given round
func (bst *blockSizeThrottle) Succeed(round uint64) {
bst.mutThrottler.Lock()
for i := len(bst.statistics) - 1; i >= 0; i-- {
if bst.statistics[i].round == round {
bst.statistics[i].succeed = true
break
}
}
bst.mutThrottler.Unlock()
}
// ComputeCurrentMaxSize computes the current max size in bytes which could be used in one block, taking into consideration the previous results
func (bst *blockSizeThrottle) ComputeCurrentMaxSize() {
//TODO: This is the first basic implementation, which will adapt the max size which could be used in one block,
//based on the last recent history. It will always choose the next value of current max size, as a middle distance between
//the last succeeded and the last not succeeded actions, or vice-versa, depending of the last action state.
//This algorithm is good when the network speed/latency is changing during the time, and the node will adapt
//based on the last recent history and not on some minimum/maximum values recorded in its whole history.
bst.mutThrottler.Lock()
defer func() {
log.Debug("ComputeCurrentMaxSize",
"current max size", bst.currentMaxSize,
)
bst.mutThrottler.Unlock()
}()
if len(bst.statistics) == 0 {
return
}
lastActionSucceed := bst.statistics[len(bst.statistics)-1].succeed
lastActionMaxSize := bst.statistics[len(bst.statistics)-1].currentMaxSize
if lastActionSucceed {
bst.currentMaxSize = bst.getMaxSizeWhenSucceed(lastActionMaxSize)
} else {
bst.currentMaxSize = bst.getMaxSizeWhenNotSucceed(lastActionMaxSize)
}
}
func (bst *blockSizeThrottle) getMaxSizeWhenSucceed(lastActionMaxSize uint32) uint32 {
if lastActionMaxSize >= bst.maxSize {
return bst.maxSize
}
maxSizeUsedWithoutSucceed := bst.getCloserAboveCurrentMaxSizeUsedWithoutSucceed(lastActionMaxSize)
if lastActionMaxSize*100/maxSizeUsedWithoutSucceed > jumpAbovePercent {
return maxSizeUsedWithoutSucceed
}
increasedMaxSize := core.MaxUint32(1, uint32(float32(maxSizeUsedWithoutSucceed-lastActionMaxSize)*jumpAboveFactor))
return lastActionMaxSize + increasedMaxSize
}
func (bst *blockSizeThrottle) getCloserAboveCurrentMaxSizeUsedWithoutSucceed(currentMaxSize uint32) uint32 {
for i := len(bst.statistics) - 1; i >= 0; i-- {
if !bst.statistics[i].succeed && bst.statistics[i].currentMaxSize > currentMaxSize {
return bst.statistics[i].currentMaxSize
}
}
return bst.maxSize
}
func (bst *blockSizeThrottle) getMaxSizeWhenNotSucceed(lastActionMaxSize uint32) uint32 {
if lastActionMaxSize <= bst.minSize {
return bst.minSize
}
maxSizeUsedWithSucceed := bst.getCloserBelowCurrentMaxSizeUsedWithSucceed(lastActionMaxSize)
if maxSizeUsedWithSucceed*100/lastActionMaxSize > jumpBelowPercent {
return maxSizeUsedWithSucceed
}
decreasedMaxSize := core.MaxUint32(1, uint32(float32(lastActionMaxSize-maxSizeUsedWithSucceed)*jumpBelowFactor))
return lastActionMaxSize - decreasedMaxSize
}
func (bst *blockSizeThrottle) getCloserBelowCurrentMaxSizeUsedWithSucceed(currentMaxSize uint32) uint32 {
for i := len(bst.statistics) - 1; i >= 0; i-- {
if bst.statistics[i].succeed && bst.statistics[i].currentMaxSize < currentMaxSize {
return bst.statistics[i].currentMaxSize
}
}
return bst.minSize
}
// IsInterfaceNil returns true if there is no value under the interface
func (bst *blockSizeThrottle) IsInterfaceNil() bool {
return bst == nil
}