Skip to content

Commit 2e581a9

Browse files
committed
Add ABLA algorithm
1 parent e66148c commit 2e581a9

10 files changed

Lines changed: 489 additions & 62 deletions

File tree

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
package blockchain
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"lukechampine.com/uint128"
9+
)
10+
11+
var (
12+
// Post-filter set to 2 GB due to limitation of 32-bit architectures
13+
// block storage.
14+
// When 32-bit will be deprecated, it should be entirely removed.
15+
TEMP_32_BIT_MAX_SAFE_BLOCKSIZE_LIMIT = uint64(2000000000)
16+
)
17+
18+
const (
19+
UINT64_MAX = ^uint64(0)
20+
21+
// Constant 2^7, used as fixed precision for algorithm's "asymmetry
22+
// factor" configuration value, e.g. we will store the real number 1.5
23+
// as integer 192 so when we want to multiply or divide an integer with
24+
// value of 1.5, we will do muldiv(value, 192, B7) or
25+
// muldiv(value, B7, 192).
26+
B7 = uint64(1) << 7
27+
28+
// Sanity ranges for configuration values
29+
MIN_ZETA_XB7 = uint64(129) // zeta real value of 1.0078125
30+
MAX_ZETA_XB7 = uint64(256) // zeta real value of 2.0000000
31+
MIN_GAMMA_RECIPROCAL = uint64(9484)
32+
MAX_GAMMA_RECIPROCAL = uint64(151744)
33+
MIN_DELTA = uint64(0)
34+
MAX_DELTA = uint64(32)
35+
MIN_THETA_RECIPROCAL = uint64(9484)
36+
MAX_THETA_RECIPROCAL = uint64(151744)
37+
)
38+
39+
// Utility function to calculate x * y / z where intermediate product
40+
// can overflow uint64 but the final result can not.
41+
func muldiv(x, y, z uint64) uint64 {
42+
if z == 0 {
43+
fmt.Fprintf(os.Stderr, "muldiv divide by 0\n")
44+
os.Exit(1)
45+
}
46+
bx := uint128.From64(x)
47+
by := uint128.From64(y)
48+
bz := uint128.From64(z)
49+
mul := bx.Mul(by)
50+
q := mul.Div(bz)
51+
if q.Hi > 0 {
52+
fmt.Fprintf(os.Stderr, "muldiv result overflow\n")
53+
os.Exit(1)
54+
}
55+
return q.Lo
56+
}
57+
58+
// Utility function
59+
func minUint64(a, b uint64) uint64 {
60+
if a < b {
61+
return a
62+
} else {
63+
return b
64+
}
65+
}
66+
67+
// Utility function
68+
func maxUint64(a, b uint64) uint64 {
69+
if a > b {
70+
return a
71+
} else {
72+
return b
73+
}
74+
}
75+
76+
// Algorithm configuration
77+
type ABLAConfig struct {
78+
// Initial control block size value, also used as floor value
79+
epsilon0 uint64
80+
// Initial elastic buffer size value, also used as floor value
81+
beta0 uint64
82+
// Last block height which will have the initial block size limit
83+
n0 uint64
84+
// Reciprocal of control function "forget factor" value
85+
gammaReciprocal uint64
86+
// Control function "asymmetry factor" value
87+
zeta_xB7 uint64
88+
// Reciprocal of elastic buffer decay rate
89+
thetaReciprocal uint64
90+
// Elastic buffer "gear factor"
91+
delta uint64
92+
// Maximum control block size value
93+
epsilonMax uint64
94+
// Maximum elastic buffer size value
95+
betaMax uint64
96+
}
97+
98+
// Set epsilonMax and betaMax such that algo's internal arithmetic ops can't overflow UINT64_MAX
99+
func (config *ABLAConfig) SetMax() {
100+
maxSafeBlocksizeLimit := UINT64_MAX / config.zeta_xB7 * B7
101+
102+
// elastic_buffer_ratio_max = (delta * gamma / theta * (zeta - 1)) / (gamma / theta * (zeta - 1) + 1)
103+
maxElasticBufferRatioNumerator := config.delta * ((config.zeta_xB7 - B7) * config.thetaReciprocal / config.gammaReciprocal)
104+
maxElasticBufferRatioDenominator := (config.zeta_xB7-B7)*config.thetaReciprocal/config.gammaReciprocal + B7
105+
106+
config.epsilonMax = maxSafeBlocksizeLimit / (maxElasticBufferRatioNumerator + maxElasticBufferRatioDenominator) * maxElasticBufferRatioDenominator
107+
config.betaMax = maxSafeBlocksizeLimit - config.epsilonMax
108+
109+
fmt.Fprintf(os.Stderr, "[INFO] Auto-configured epsilonMax: %d, betaMax: %d\n", config.epsilonMax, config.betaMax)
110+
}
111+
112+
func (config *ABLAConfig) IsValid() (errs *strings.Builder) {
113+
if config.epsilon0 > config.epsilonMax {
114+
errs = new(strings.Builder)
115+
errs.WriteString("Error, initial control block size limit sanity check failed (epsilonMax)")
116+
return errs
117+
}
118+
if config.beta0 > config.betaMax {
119+
errs = new(strings.Builder)
120+
errs.WriteString("Error, initial elastic buffer size sanity check failed (betaMax).")
121+
return errs
122+
}
123+
if config.zeta_xB7 < MIN_ZETA_XB7 || config.zeta_xB7 > MAX_ZETA_XB7 {
124+
errs = new(strings.Builder)
125+
errs.WriteString("Error, zeta sanity check failed.")
126+
return errs
127+
}
128+
if config.gammaReciprocal < MIN_GAMMA_RECIPROCAL || config.gammaReciprocal > MAX_GAMMA_RECIPROCAL {
129+
errs = new(strings.Builder)
130+
errs.WriteString("Error, gammaReciprocal sanity check failed.")
131+
return errs
132+
}
133+
if config.delta+1 <= MIN_DELTA || config.delta > MAX_DELTA {
134+
errs = new(strings.Builder)
135+
errs.WriteString("Error, delta sanity check failed.")
136+
return errs
137+
}
138+
if config.thetaReciprocal < MIN_THETA_RECIPROCAL || config.thetaReciprocal > MAX_THETA_RECIPROCAL {
139+
errs = new(strings.Builder)
140+
errs.WriteString("Error, thetaReciprocal sanity check failed.")
141+
return errs
142+
}
143+
if config.epsilon0 < muldiv(config.gammaReciprocal, B7, config.zeta_xB7-B7) {
144+
// Required due to truncation of integer ops.
145+
// With this we ensure that the control size can be adjusted for at least 1 byte.
146+
// Also, with this we ensure that divisior bytesMax in calculateNextABLAState() can't be 0.
147+
errs = new(strings.Builder)
148+
errs.WriteString("Error, epsilon0 sanity check failed. Too low relative to gamma and zeta.")
149+
return errs
150+
}
151+
return nil
152+
}
153+
154+
// Algorithm's internal state
155+
// Note: limit for the block with blockHeight will be given by
156+
// controlBlockSize + elasticBufferSize
157+
type ABLAState struct {
158+
// Block height for which the state applies
159+
blockHeight uint64
160+
// Control function state
161+
controlBlockSize uint64
162+
// Elastic buffer function state
163+
elasticBufferSize uint64
164+
}
165+
166+
// Returns true if this state is valid relative to `config`. On false return, optional out `errs` is set
167+
// to point to a constant string explaining the reason that this state is invalid.
168+
func (state *ABLAState) IsValid(config *ABLAConfig) (errs *strings.Builder) {
169+
if state.controlBlockSize < config.epsilon0 || state.controlBlockSize > config.epsilonMax {
170+
errs = new(strings.Builder)
171+
errs.WriteString("Error, invalid controlBlockSize state. Can't be below initialization value or above epsilonMax.")
172+
return errs
173+
}
174+
if state.elasticBufferSize < config.beta0 || state.elasticBufferSize > config.betaMax {
175+
errs = new(strings.Builder)
176+
errs.WriteString("Error, invalid elasticBufferSize state. Can't be below initialization value or above betaMax.")
177+
return errs
178+
}
179+
return nil
180+
}
181+
182+
// Calculate the limit for the block to which the algorithm's state
183+
// applies, given algorithm state
184+
func (state *ABLAState) getBlockSizeLimit() uint64 {
185+
return minUint64(state.controlBlockSize+state.elasticBufferSize, TEMP_32_BIT_MAX_SAFE_BLOCKSIZE_LIMIT)
186+
// Note: Remove the TEMP_32_BIT_MAX_SAFE_BLOCKSIZE_LIMIT limit once 32-bit architecture is deprecated:
187+
// return state.controlBlockSize + state.elasticBufferSize
188+
}
189+
190+
// Calculate algorithm's look-ahead block size limit, for a block N blocks ahead of current one.
191+
// Returns the limit for block with current+N height, assuming all blocks 100% full.
192+
func (state *ABLAState) lookaheadState(config *ABLAConfig, count uint) ABLAState {
193+
lookaheadState := *state
194+
for i := uint(0); i < count; i++ {
195+
maxSize := lookaheadState.getBlockSizeLimit()
196+
lookaheadState = lookaheadState.nextABLAState(config, maxSize)
197+
}
198+
return lookaheadState
199+
}
200+
201+
// Calculate algorithm's state for the next block (n), given
202+
// current blockchain tip (n-1) block size, algorithm state, and
203+
// algorithm configuration. Returns the next state after this block.
204+
func (state *ABLAState) nextABLAState(config *ABLAConfig, currentBlockSize uint64) ABLAState {
205+
// Next block's ABLA state
206+
var newState ABLAState
207+
208+
// n = n + 1
209+
newState.blockHeight = state.blockHeight + 1
210+
211+
// For safety: we clamp this current block's blocksize to the maximum value this algorithm expects. Normally this
212+
// won't happen unless the node is run with some -excessiveblocksize parameter that permits larger blocks than this
213+
// algo's current state expects.
214+
currentBlockSize = minUint64(currentBlockSize, state.controlBlockSize+state.elasticBufferSize)
215+
216+
// if block height is in range 0 to n0 inclusive use initialization values
217+
// else use algorithmic limit
218+
if newState.blockHeight <= config.n0 {
219+
// epsilon_n = epsilon_0
220+
newState.controlBlockSize = config.epsilon0
221+
// beta_n = beta_0
222+
newState.elasticBufferSize = config.beta0
223+
} else {
224+
// control function
225+
226+
// zeta * x_{n-1}
227+
amplifiedCurrentBlockSize := muldiv(config.zeta_xB7, currentBlockSize, B7)
228+
229+
// if zeta * x_{n-1} > epsilon_{n-1} then increase
230+
// else decrease or no change
231+
if amplifiedCurrentBlockSize > state.controlBlockSize {
232+
// zeta * x_{n-1} - epsilon_{n-1}
233+
bytesToAdd := amplifiedCurrentBlockSize - state.controlBlockSize
234+
235+
// zeta * y_{n-1}
236+
amplifiedBlockSizeLimit := muldiv(config.zeta_xB7, state.controlBlockSize+state.elasticBufferSize, B7)
237+
238+
// zeta * y_{n-1} - epsilon_{n-1}
239+
bytesMax := amplifiedBlockSizeLimit - state.controlBlockSize
240+
241+
// zeta * beta_{n-1} * (zeta * x_{n-1} - epsilon_{n-1}) / (zeta * y_{n-1} - epsilon_{n-1})
242+
scalingOffset := muldiv(muldiv(config.zeta_xB7, state.elasticBufferSize, B7),
243+
bytesToAdd, bytesMax)
244+
// epsilon_n = epsilon_{n-1} + gamma * (zeta * x_{n-1} - epsilon_{n-1} - zeta * beta_{n-1} * (zeta * x_{n-1} - epsilon_{n-1}) / (zeta * y_{n-1} - epsilon_{n-1}))
245+
newState.controlBlockSize = state.controlBlockSize + (bytesToAdd-scalingOffset)/config.gammaReciprocal
246+
} else {
247+
// epsilon_{n-1} - zeta * x_{n-1}
248+
bytesToRemove := state.controlBlockSize - amplifiedCurrentBlockSize
249+
250+
// epsilon_{n-1} + gamma * (zeta * x_{n-1} - epsilon_{n-1})
251+
// rearranged to:
252+
// epsilon_{n-1} - gamma * (epsilon_{n-1} - zeta * x_{n-1})
253+
newState.controlBlockSize = state.controlBlockSize - bytesToRemove/config.gammaReciprocal
254+
255+
// epsilon_n = max(epsilon_{n-1} + gamma * (zeta * x_{n-1} - epsilon_{n-1}), epsilon_0)
256+
newState.controlBlockSize = maxUint64(newState.controlBlockSize, config.epsilon0)
257+
}
258+
259+
// elastic buffer function
260+
261+
// beta_{n-1} * theta
262+
bufferDecay := state.elasticBufferSize / config.thetaReciprocal
263+
264+
// if zeta * x_{n-1} > epsilon_{n-1} then increase
265+
// else decrease or no change
266+
if amplifiedCurrentBlockSize > state.controlBlockSize {
267+
// (epsilon_{n} - epsilon_{n-1}) * delta
268+
bytesToAdd := (newState.controlBlockSize - state.controlBlockSize) * config.delta
269+
270+
// beta_{n-1} - beta_{n-1} * theta + (epsilon_{n} - epsilon_{n-1}) * delta
271+
newState.elasticBufferSize = state.elasticBufferSize - bufferDecay + bytesToAdd
272+
} else {
273+
// beta_{n-1} - beta_{n-1} * theta
274+
newState.elasticBufferSize = state.elasticBufferSize - bufferDecay
275+
}
276+
// max(beta_{n-1} - beta_{n-1} * theta + (epsilon_{n} - epsilon_{n-1}) * delta, beta_0) , if zeta * x_{n-1} > epsilon_{n-1}
277+
// max(beta_{n-1} - beta_{n-1} * theta, beta_0) , if zeta * x_{n-1} <= epsilon_{n-1}
278+
newState.elasticBufferSize = maxUint64(newState.elasticBufferSize, config.beta0)
279+
280+
// clip controlBlockSize to epsilonMax to avoid integer overflow for extreme sizes
281+
newState.controlBlockSize = minUint64(newState.controlBlockSize, config.epsilonMax)
282+
// clip elasticBufferSize to betaMax to avoid integer overflow for extreme sizes
283+
newState.elasticBufferSize = minUint64(newState.elasticBufferSize, config.betaMax)
284+
}
285+
return newState
286+
}
287+
288+
func main() {
289+
var configInitialExcessiveBlockSize uint64 = 32000000 // excessiveblocksize
290+
291+
//ablaconfig
292+
config := &ABLAConfig{
293+
beta0: 0,
294+
n0: 0,
295+
gammaReciprocal: 0,
296+
zeta_xB7: 0,
297+
thetaReciprocal: 0,
298+
delta: 0,
299+
}
300+
301+
// Finalize config
302+
if configInitialExcessiveBlockSize > config.beta0 {
303+
config.epsilon0 = configInitialExcessiveBlockSize - config.beta0
304+
config.SetMax()
305+
} else {
306+
fmt.Fprintf(os.Stderr, "Error, initial block size limit relative to initial elastic buffer size sanity check failed.\n")
307+
os.Exit(1)
308+
}
309+
310+
// Sanity check the config
311+
errs := config.IsValid()
312+
if errs != nil {
313+
fmt.Fprintf(os.Stderr, "ABLA Config sanity check failed: %s\n", errs.String())
314+
os.Exit(1)
315+
}
316+
317+
// Initialize state for height 0
318+
state := ABLAState{
319+
blockHeight: 0,
320+
controlBlockSize: config.epsilon0,
321+
elasticBufferSize: config.beta0,
322+
}
323+
324+
errs = state.IsValid(config)
325+
if errs != nil {
326+
fmt.Fprintf(os.Stderr, "ABLA state sanity check failed: %s\n", errs.String())
327+
os.Exit(1)
328+
}
329+
330+
// Calculate and print
331+
var blockSize uint64
332+
blockSize = minUint64(blockSize, state.getBlockSizeLimit())
333+
// calculate the next block's algorithm state
334+
state = state.nextABLAState(config, blockSize) // TODO UPDATE STATE AFTER BLOCK ACCEPTED
335+
336+
blockSizeLimit := state.getBlockSizeLimit() // TODO CHECKS BLOCK SIZE LIMIT
337+
338+
fmt.Printf("%d,%d,%d,%d,%d\n",
339+
state.blockHeight-1, blockSize, blockSizeLimit, state.elasticBufferSize,
340+
state.controlBlockSize)
341+
342+
}

0 commit comments

Comments
 (0)