@@ -7,11 +7,11 @@ import (
7
7
"os"
8
8
"strconv"
9
9
10
- "github.com/dreading/gospecfunc/bessel"
11
- "github.com/filecoin-project/lotus/build"
10
+ skellampmf "github.com/rvagg/go-skellam-pmf"
12
11
"github.com/urfave/cli/v2"
13
12
"golang.org/x/exp/constraints"
14
- "gonum.org/v1/gonum/stat/distuv"
13
+
14
+ "github.com/filecoin-project/lotus/build"
15
15
)
16
16
17
17
var finalityCmd = & cli.Command {
@@ -25,6 +25,10 @@ var finalityCmd = &cli.Command{
25
25
& cli.StringFlag {
26
26
Name : "input" ,
27
27
},
28
+ & cli.IntFlag {
29
+ Name : "target" ,
30
+ Usage : "target epoch for which finality is calculated" ,
31
+ },
28
32
},
29
33
ArgsUsage : "[inputFile]" ,
30
34
Action : func (cctx * cli.Context ) error {
@@ -33,7 +37,7 @@ var finalityCmd = &cli.Command{
33
37
if err != nil {
34
38
return err
35
39
}
36
- defer file .Close ()
40
+ defer func () { _ = file .Close () } ()
37
41
38
42
var chain []int
39
43
scanner := bufio .NewScanner (file )
@@ -49,14 +53,15 @@ var finalityCmd = &cli.Command{
49
53
return err
50
54
}
51
55
52
- blocksPerEpoch := 5.0 // Expected number of blocks per epoch
53
- byzantineFraction := 0.3 // Upper bound on the fraction of malicious nodes in the network
54
- currentEpoch := len (chain ) - 1 // Current epoch (end of history)
55
- targetEpoch := currentEpoch - 30 // Target epoch for which finality is calculated
56
+ blocksPerEpoch := 5.0 // Expected number of blocks per epoch
57
+ byzantineFraction := 0.3 // Upper bound on the fraction of malicious nodes in the network
58
+ currentEpoch := len (chain ) - 1 // Current epoch (end of history)
59
+ // targetEpoch := currentEpoch - 30 // Target epoch for which finality is calculated
60
+ targetEpoch := cctx .Int ("target" )
56
61
57
62
finality := FinalityCalcValidator (chain , blocksPerEpoch , byzantineFraction , currentEpoch , targetEpoch )
58
63
59
- fmt .Fprintf (cctx .App .Writer , "Finality probability: %f \n " , finality )
64
+ _ , _ = fmt .Fprintf (cctx .App .Writer , "Finality=%v @ %d for chain len=%d \n " , finality , targetEpoch , currentEpoch )
60
65
61
66
return nil
62
67
},
@@ -69,10 +74,10 @@ func FinalityCalcValidator(chain []int, blocksPerEpoch float64, byzantineFractio
69
74
// Threshold at which the probability of an event is considered negligible
70
75
const negligibleThreshold = 1e-25
71
76
72
- maxKL := 400 // Max k for which to calculate Pr(L=k)
73
- maxKB := int (( currentEpoch - targetEpoch ) * int (blocksPerEpoch ) ) // Max k for which to calculate Pr(B=k)
74
- maxKM := 400 // Max k for which to calculate Pr(M=k)
75
- maxIM := 100 // Maximum number of epochs for the calculation (after which the pr become negligible)
77
+ maxKL := 400 // Max k for which to calculate Pr(L=k)
78
+ maxKB := ( currentEpoch - targetEpoch ) * int (blocksPerEpoch ) // Max k for which to calculate Pr(B=k)
79
+ maxKM := 400 // Max k for which to calculate Pr(M=k)
80
+ maxIM := 100 // Maximum number of epochs for the calculation (after which the pr become negligible)
76
81
77
82
rateMaliciousBlocks := blocksPerEpoch * byzantineFraction // upper bound
78
83
rateHonestBlocks := blocksPerEpoch - rateMaliciousBlocks // lower bound
@@ -88,15 +93,14 @@ func FinalityCalcValidator(chain []int, blocksPerEpoch float64, byzantineFractio
88
93
sumExpectedAdversarialBlocksI += rateMaliciousBlocks
89
94
sumChainBlocksI += chain [i - 1 ]
90
95
// Poisson(k=k, lambda=sum(f*e))
91
- prLi := distuv. Poisson { Lambda : sumExpectedAdversarialBlocksI }. Prob ( float64 (k + sumChainBlocksI ))
96
+ prLi := poissonProb ( sumExpectedAdversarialBlocksI , float64 (k + sumChainBlocksI ))
92
97
prL [k ] = math .Max (prL [k ], prLi )
93
98
94
- // Break if prL[k] becomes negligible
95
- if k > 1 && prL [k ] < negligibleThreshold && prL [k ] < prL [k - 1 ] {
96
- maxKL = k
97
- prL = prL [:k + 1 ]
98
- break
99
- }
99
+ }
100
+ if k > 1 && prL [k ] < negligibleThreshold && prL [k ] < prL [k - 1 ] {
101
+ maxKL = k
102
+ prL = prL [:k + 1 ]
103
+ break
100
104
}
101
105
}
102
106
@@ -108,7 +112,7 @@ func FinalityCalcValidator(chain []int, blocksPerEpoch float64, byzantineFractio
108
112
109
113
// Calculate Pr(B=k) for each value of k
110
114
for k := 0 ; k <= maxKB ; k ++ {
111
- prB [k ] = distuv. Poisson { Lambda : float64 (currentEpoch - targetEpoch ) * rateMaliciousBlocks }. Prob ( float64 (k ))
115
+ prB [k ] = poissonProb ( float64 (currentEpoch - targetEpoch )* rateMaliciousBlocks , float64 (k ))
112
116
113
117
// Break if prB[k] becomes negligible
114
118
if k > 1 && prB [k ] < negligibleThreshold && prB [k ] < prB [k - 1 ] {
@@ -119,11 +123,11 @@ func FinalityCalcValidator(chain []int, blocksPerEpoch float64, byzantineFractio
119
123
}
120
124
121
125
// Compute M
122
- prHgt0 := 1 - distuv. Poisson { Lambda : rateHonestBlocks }. Prob ( 0 )
126
+ prHgt0 := 1 - poissonProb ( rateHonestBlocks , 0 )
123
127
124
128
expZ := 0.0
125
129
for k := 0 ; k < int (4 * blocksPerEpoch ); k ++ {
126
- pmf := distuv. Poisson { Lambda : rateMaliciousBlocks }. Prob ( float64 (k ))
130
+ pmf := poissonProb ( rateMaliciousBlocks , float64 (k ))
127
131
expZ += ((rateHonestBlocks + float64 (k )) / math .Pow (2 , float64 (k ))) * pmf
128
132
}
129
133
@@ -132,7 +136,7 @@ func FinalityCalcValidator(chain []int, blocksPerEpoch float64, byzantineFractio
132
136
prM := make ([]float64 , maxKM + 1 )
133
137
for k := 0 ; k <= maxKM ; k ++ {
134
138
for i := maxIM ; i > 0 ; i -- {
135
- probMI := SkellamPMF (k , float64 (i )* rateMaliciousBlocks , float64 (i )* ratePublicChain )
139
+ probMI := skellampmf . SkellamPMF (k , float64 (i )* rateMaliciousBlocks , float64 (i )* ratePublicChain )
136
140
137
141
// Break if probMI becomes negligible
138
142
if probMI < negligibleThreshold && probMI < prM [k ] {
@@ -186,6 +190,18 @@ func FinalityCalcValidator(chain []int, blocksPerEpoch float64, byzantineFractio
186
190
return math .Min (prError , 1.0 )
187
191
}
188
192
193
+ func poissonProb (lambda float64 , x float64 ) float64 {
194
+ return math .Exp (poissonLogProb (lambda , x ))
195
+ }
196
+
197
+ func poissonLogProb (lambda float64 , x float64 ) float64 {
198
+ if x < 0 || math .Floor (x ) != x {
199
+ return math .Inf (- 1 )
200
+ }
201
+ lg , _ := math .Lgamma (math .Floor (x ) + 1 )
202
+ return x * math .Log (lambda ) - lambda - lg
203
+ }
204
+
189
205
func sum [T constraints.Integer | constraints.Float ](s []T ) T {
190
206
var total T
191
207
for _ , v := range s {
@@ -210,46 +226,3 @@ func min(a, b int) int {
210
226
}
211
227
return b
212
228
}
213
-
214
- // SkellamPMF calculates the probability mass function (PMF) of a Skellam distribution.
215
- //
216
- // The Skellam distribution is the probability distribution of the difference
217
- // of two independent Poisson random variables.
218
- //
219
- // Arguments:
220
- // * k - The difference of two Poisson random variables.
221
- // * mu1 - The expected value of the first Poisson distribution.
222
- // * mu2 - The expected value of the second Poisson distribution.
223
- //
224
- // Returns:
225
- // * A float64 representing the PMF of the Skellam distribution at k.
226
- func SkellamPMF (k int , mu1 float64 , mu2 float64 ) float64 {
227
- // Based on https://github.com/jsoares/rusty-skellam/blob/main/src/lib.rs
228
-
229
- // Return NaN if parameters outside range
230
- if math .IsNaN (mu1 ) || mu1 <= 0 || math .IsNaN (mu2 ) || mu2 <= 0 {
231
- return math .NaN ()
232
- }
233
-
234
- // Parameterise and compute the Modified Bessel function of the first kind
235
- nu := float64 (k )
236
- z := complex (2.0 * math .Sqrt (mu1 * mu2 ), 0 )
237
- besselResult := bessel .I (nu , z )
238
-
239
- // Compute the pmf
240
- return math .Exp (- (mu1 + mu2 )) * math .Pow (mu1 / mu2 , nu / 2.0 ) * real (besselResult )
241
- }
242
-
243
- /*
244
- func main() {
245
- seed := rand.NewSource(1)
246
- random := rand.New(seed)
247
- chain := make([]int, 1000)
248
- for i := range chain {
249
- chain[i] = random.Intn(5) + 1
250
- }
251
-
252
- errorProbability := FinalityCalcValidator(chain, 5.0, 0.3, 1000, 900)
253
- fmt.Printf("Error probability: %f\n", errorProbability)
254
- }
255
- */
0 commit comments