Filcoin出块权算法原理
万丈高楼从地起, 所以需要先从最基本的说起.
排列
从 (n) 个人中 取出 (m) 个人,如果取出来的顺序不同就是不同的结果的话,选第一个人时有 种选择,
选第二个人时, 对于前面$ n $种选择, 每一种, 都有 (n - 1) 种选择,。。。 最后一个人有 (n - m + 1) 种选择.
所以排列的数量有:
(P_n^m = n(n-1)(n-2)...(n-m+1))
组合
假设从 (n) 个人中取出 (m) 个人, 有 (C_n^m) 种方法, 对于每一种取法, 对取出的 (m) 个人进行排序, 其总和等于(P_n^m),所以下面的等式成立:
(egin{aligned} C_n^m * m! &= P_n^m => \ C_n^m &= frac{P_n^m}{m!}end{aligned})
二项式分布
二项式分布概念
假设下面的情况:
老王是条单身狗的屌丝程序员, 想着怎么找一个妹子, 老王想了一个大面积撒网,小面积培养的办法:遇到一个妹子, 就去跟她表白;
老王觉得自己还挺有魅力, 所以他假设自己每表白4次就会成功1次,
所以每次表白成功的概率为$p=0.25; $则表白失败的概率为 (q=1-p=0.75)
老王现在想算一下,如果自己对100个妹子表白, 被其中10个妹子看中的概率是多少呢?
对于100次都表白, 我们每次都用(x)来表示表白的结果:
这个(x)有可能是 (p) 的概率(成功), 也可能是(1-p)(失败), 由于有10次成功, 我们可以把这10次成功随机的放到100个(x)的位置上.
一共有多少种放法?这是一个组合, 即:(C_{100}^{10}),而基于出现在(C_{100}^{10})中的每一种情况, 其出现的概率都是(p^{10}(q)^{90}),只是p和q出现的顺序不一样而已.
所以, 可以得出如果老王对100个女孩表白, 成功10次的概率是:
概率质量函数(probability mass function)
根据上面的结果可以得出一个普遍的公式, 对于是n个独立的成功/失败试验中成功的次数的离散概率分布,
其中每次试验的成功概率为p,失败的概率为1-p, 则实验n次成功k次的概率为一个函数记为:
这个(f(k,n,p))函数称为:'概率质量函数',用于计算对于概率为p的随机事件(伯努利实验), 重复试验n次, 出现k次的概率
在wolfram上可以看到表白这件事的概率分布如下图:
累积分布函数(Cumulative Distribution Function)
老王作为一条单身汪, 也不是没有原因的, 想着想着, 竟然开始研究概率问题了:
老王想知道我要是表白100次,成功10次到15次的概率是多少呢?
所以老王把自己成功10次, 11次, 12..15次的概率加起来,得出下面的函数:
(F(K,n,p))函数就是老王表白成功的累积分布函数如图:
老王表白是一个离散的事件, 在连续的情况下, 累积分布函数应该是取积分:
泊松分布
老王每天没事就在路边对着女性表白,骚扰女性!常在路边走哪有不湿鞋, 出来混迟早要还的!!!
突然有一天老王就被一道闪电劈中了, 但是, 老王因祸得福, 居然拥有了闪电的速度, 他变成了闪电侠!
所以老王的速度越来越快, 他的每天表白的次数n可以接近无限,每天可以成功的次数变成了 (lambda),
所以这个时候,老王表白成功的概率可以算成(P=frac{lambda}{n}), 然后我们就可以推算出泊松分布的公式了, 如下:
(egin{aligned} f(k, n, p) &= C_n^kp^k(1-p)^{n-k} \ &=frac{{n(n-1)...(n-k+1)}}{k!}p^k(1-p)^{n-k} \ &=frac{{n(n-1)...(n-k+1)}}{k!}(frac{lambda}{n})^k(1-frac{lambda}{n})^{n-k} \ &=frac{{n(n-1)...(n-k+1)}}{n^k}frac{lambda^k}{k!}(1-frac{lambda}{n})^{n-k} \ &=egin{cases}limlimits_{n o infty}frac{{n(n-1)...(n-k+1)}}{n^k}end{cases}frac{lambda^k}{k!}(1-frac{lambda}{n})^{n-k} \ &=egin{cases}limlimits_{n o infty}frac{overbrace{n(n-1)...(n-k+1)}^{k个}}{underbrace{n * n ... * n}_{k个}}end{cases}frac{lambda^k}{k!}(1-frac{lambda}{n})^{n-k} \ &= 1*frac{lambda^k}{k!}(1-frac{lambda}{n})^{n-k} \ &= frac{lambda^k}{k!}(1-frac{lambda}{n})^{n}egin{cases}limlimits_{n o infty}(1-frac{lambda}{n})^{-k}end{cases} \ &= frac{lambda^k}{k!}(1-frac{lambda}{n})^{n}*1 \ &= frac{lambda^k}{k!}egin{cases}limlimits_{n o infty}(1+frac{-lambda}{n})^{n}end{cases} \ &= frac{lambda^k}{k!}e^{-lambda} end{aligned})
至于最后一步推导(egin{cases}limlimits_{n o infty}(1 - frac{lambda}{n}) = e^{-lambda}end{cases})的证明,请看这里
所以, 当二项式分布测试样本n非常大的时候, 就可以逼近泊松分布了.
Filcoin中出块权的计算
有了上面的基础, 就可以更加深入, 开始研究Filcoin出块权的计算的原理了.
Filcoin计算出块wincount的代码如下, 后面会分几个部分进行详细的讨论:
// ComputeWinCount uses VRFProof to compute number of wins
// The algorithm is based on Algorand's Sortition with Binomial distribution
// replaced by Poisson distribution.
func (ep *ElectionProof) ComputeWinCount(power BigInt, totalPower BigInt) int64 {
h := blake2b.Sum256(ep.VRFProof)
lhs := BigFromBytes(h[:]).Int // 256bits, assume Q.256 so [0, 1)
// We are calculating upside-down CDF of Poisson distribution with
// rate λ=power*E/totalPower
// Steps:
// 1. calculate λ=power*E/totalPower
// 2. calculate elam = exp(-λ)
// 3. Check how many times we win:
// j = 0
// pmf = elam
// rhs = 1 - pmf
// for h(vrf) < rhs: j++; pmf = pmf * lam / j; rhs = rhs - pmf
lam := lambda(power.Int, totalPower.Int) // Q.256
p, rhs := newPoiss(lam)
var j int64
for lhs.Cmp(rhs) < 0 && j < MaxWinCount {
rhs = p.next()
j++
}
return j
}
关于计算(lambda)的解释
矿工出块的概率理论受自身算力的影响和全网算力的影响,在Filcoin出块权限计算的时候, 这个(lambda)的计算方法为:
// computes lambda in Q.256
func lambda(power, totalPower *big.Int) *big.Int {
lam := new(big.Int).Mul(power, blocksPerEpoch.Int) // Q.0
lam = lam.Lsh(lam, precision) // Q.256
lam = lam.Div(lam /* Q.256 */, totalPower /* Q.0 */) // Q.256
return lam
}
列成代数表达式可以是这样的:
(lambda = frac{blocks\_per\_epoch * power}{total\_power} * 2^{256}, {block\_per\_epoch=5})
根据前面的内容, 可以知道二项式分布中概率(p),当重复试验次数n趋于正无穷的时候, 等式成立:(p = egin{cases}limlimits_{n o infty}frac{lambda}{n}end{cases})
所以, 这个(lambda)的值现在可以认为是在重复(2^{256})个高度以后, 矿工理论上应该获得的wincount数量(数学期望)
关于(1-CDF[Poisson[lambda], k])的意义
对于Filcoin计算Wincount部分的泊松分布, 根据前面的了解,可以知道,
累积分布(CDF[Poisson[lambda], k])的意义表示:
当(n o infty)时, 矿工出块数为(0 o k)之间的概率.
故而(1-CDF[Poisson[lambda], k])的意义表示:
当(n o infty)时, 矿工出块数为(k o n (n o infty))的概率.
所以在Filcoin计算wincount的下面这一部分代码中:
p, rhs := newPoiss(lam)
var j int64
for lhs.Cmp(rhs) < 0 && j < MaxWinCount {
rhs = p.next()
j++
}
newPoisson 返回值rhs, 是计算(1-CDF[Poisson[lambda], k], {k=0})的值
p.next 返回的rhs, 是计算(1-CDF[Poisson[lambda], k], {k=k+1})值
再返回来看这段(for(...))循环, 其功能为:
- newPoissn(lam), k=0, 返回出块数(>=1)的概率
- p.next(), k=1, 返回出块数(>=2)的概率
- p.next(), k=2, 返回出块数(>=3)的概率
- ....
关于lhs(hash256[VRFProof])的理解
VRFProof是Filcoin的分布式随机数生成服务生成的256bit的随机变量,
由于Filcoin已经把poisson分布中的概率, 映射到了([0, 2^{256}))之间,概率上可以视为([0,1))之间的一个值.
h := blake2b.Sum256(ep.VRFProof)
lhs := BigFromBytes(h[:]).Int // 256bits, assume Q.256 so [0, 1)
...
for lhs.Cmp(rhs) < 0 && j < MaxWinCount {
rhs = p.next()
j++
}
这一部分代码可以看成, 分布式随机数生成的服务器在每一轮出块计算时, 给出一个随机概率阈值记为(phi),
这个值对于所有的矿工来说都一样, 计算矿工本轮的wincount就是计算:(max(0, k), k in (1-CDF[Poisson[lambda],k] > phi)) ,
所以这个wincount在满足条件的情况下, 是符合其出块的数学期望的.
验证Wincount是否符合其算力对等的数学期望
下面的代模拟了证了10万, 100万轮出块, 矿工算力为全网30%情况下, 矿工赢得的wincount:
func TestWinCounts(t *testing.T) {
totalPower := NewInt(100)
power := NewInt(30)
ep := &ElectionProof{VRFProof: nil}
round := 100000.0
networkWincount := round * float64(blocksPerEpoch.Int64())
expectedWincount := networkWincount * 30 / 100
realWincount := int64(0)
for i := uint64(0); i < uint64(round); i++ {
i := i + 1
ep.VRFProof = []byte{byte(i), byte(i >> 8), byte(i >> 16), byte(i >> 24), byte(i >> 32)}
j := ep.ComputeWinCount(power, totalPower)
realWincount += j
}
fmt.Printf("round=%d, expected wincount = %d, actually wincount=%d
",
int64(round), int64(expectedWincount), realWincount)
}
10万轮的情况, 预期赢票数为150000, 实际赢票为:150031
=== RUN TestWinCounts
round=100000, expected wincount = 150000, actually wincount=150031
--- PASS: TestWinCounts (0.36s)
PASS
100万轮的情况, 预期赢票为1500000, 实际赢票为:1498847
=== RUN TestWinCounts
round=1000000, expected wincount = 1500000, actually wincount=1498847
--- PASS: TestWinCounts (3.26s)
PASS