Lotus 的 PoSt 的部分从 electionPoSt 变成两种新的 PoSt,一种是 winningPoSt,一种是 windowPoSt。先讲讲 winningPoSt 吧。winningPoSt,顾名思义,在 winning 的时候进行的 PoSt。所谓的 winning,也就是获取出块权。
简单的说,winningPoSt,随机抽查的一个 sector,该 sector 中的 66 条随机抽查的 merkle path 都正确。代码逻辑从 Lotus 的 go 的代码说起。一切从出块开始 – lotus/miner/miner.go 的 Miner 结构的 mineOne 函数。
func(m*Miner)mineOne(ctxcontext.Context,addraddress.Address,base*MiningBase)(*types.BlockMsg,error){
mbi,err:=m.api.MinerGetBaseInfo(ctx,addr,round,base.TipSet.Key())
rand,err:=m.api.ChainGetRandomness(ctx,base.TipSet.Key(),crypto.DomainSeparationTag_WinningPoStChallengeSeed,base.TipSet.Height()+base.NullRounds,nil)
prand:=abi.PoStRandomness(rand)
postProof,err:=m.epp.ComputeProof(ctx,mbi.Sectors,prand)
其中,MinerGetBaseInfo 函数是获取一些基本信息,其中包括需要抽取的 sector 信息。ComputeProof 函数就是计算 winningPoSt 证明。
因为这些逻辑的具体实现是在 rust-fil-proofs,也就是 rust 语言实现的。从 go 到 rust-fil-proofs,跨越了不少接口:
中间的接口就不介绍了,直接看 rust-fil-proofs 提供的两个 API 函数。
01 抽查个数设置
Sector 个数以及总的抽查的叶子个数的定义在 rust-fil-proofs/filecoin-proofs/hide/constants.rs 中:
pubconstWINNING_POST_CHALLENGE_COUNT:usize=66;
pubconstWINNING_POST_SECTOR_COUNT:usize=1;
也就是说,要从有效 Sector 中抽取一个 Sector,并在这个 Sector 上抽查 66 个叶子节点。
02Sector 的抽查逻辑
generate_winning_post_sector_challenge 函数实现了 Sector 的抽查逻辑。核心逻辑显然是如何抽查 Sector?具体的逻辑在 fallback::generate_sector_challenges 的函数中:
letmuthasher=Sha256::new();
hasher.input(AsRef::::as_ref(&prover;_id));
hasher.input(AsRef::::as_ref(&randomness;));
hasher.input(&n.to;_le_bytes()[..]);
lethash=hasher.result();
letsector_challenge=LittleEndian::read_u64(&hash.as;_ref()[..8]);
letsector_index=sector_challenge%sector_set_len;
简单的说,就是把 prover_id, 随机信息,抽查 Sector 的编号做 sha256 的 hash 计算,计算结果和当前有限的 Sector 个数取模。也就是 sector_index 就是最终抽查的 Sector 的 id。
03Challenge 的叶子抽查逻辑
generate_winning_post 在抽查的 Sector 形成的 merkle 树 (replica_r_last) 上,抽查叶子节点。抽查叶子节点的计算逻辑在 fallback::generate_leaf_challenge 的函数中:
letmuthasher=Sha256::new();
hasher.input(AsRef::::as_ref(&randomness;));
hasher.input(§or;_id.to_le_bytes()[..]);
hasher.input(&leaf;_challenge_index.to_le_bytes()[..]);
lethash=hasher.result();
letleaf_challenge=LittleEndian::read_u64(&hash.as;_ref()[..8]);
letchallenged_range_index=leaf_challenge%(pub_params.sector_size/NODE_SIZEasu64);
把随机信息,sector id 和挑战叶子的编号进行 hash 计算。计算的结果和叶子的总个数取模。32G 的 Sector,叶子个数为 1G 个。
04 零知识证明电路
零知识证明的计算部分可以查看 rust-fil-proofs/post/fallback 目录。大体的逻辑模块和结构可以查看之前的文章介绍:
讲讲 rust-fil-proofs/post/fallback/circuit.rs 中的 Sector 结构吧。这个结构就代表一个抽查。从 synthesize 函数可以看出:
//1.Verifycomm_r
letcomm_r_last_num=num::AllocatedNum::alloc(cs.namespace(||"comm_r_last"),||{
comm_r_last
.map(Into::into)
.ok_or_else(||SynthesisError::AssignmentMissing)
})?;
letcomm_c_num=num::AllocatedNum::alloc(cs.namespace(||"comm_c"),||{
comm_c
.map(Into::into)
.ok_or_else(||SynthesisError::AssignmentMissing)
})?;
letcomm_r_num=num::AllocatedNum::alloc(cs.namespace(||"comm_r"),||{
comm_r
.map(Into::into)
.ok_or_else(||SynthesisError::AssignmentMissing)
})?;
comm_r_num.inputize(cs.namespace(||"comm_r_input"))?;
comm_r 作为 public 输入,其他 comm_r_last 和 comm_c 作为 private 输入。
//1.VerifyH(Comm_C||comm_r_last)==comm_r
{
lethash_num=::Function::hash2_circuit(
cs.namespace(||"H_comm_c_comm_r_last"),
&comm;_c_num,
&comm;_r_last_num,
)?;
//Checkactualequality
constraint::equal(
cs,
||"enforce_comm_c_comm_r_last_hash_comm_r",
&comm;_r_num,
&hash;_num,
);
}
验证 comm_r 是否由 comm_c 和 comm_r_last 计算得到。
//2.VerifyInclusionPaths
for(i,(leaf,path))inleafs.iter().zip(paths.iter()).enumerate(){
oRCircuit::::synthesize(
cs.namespace(||format!("challenge_inclusion_{}",i)),
Root::Val(*leaf),
path.clone(),
Root::from_allocated:comm_r_last_num.clone()),
true,
)?;
}
验证从叶子节点是否可以正确计算出 Merkle 树根。
总结:
Lotus 的 PoSt 包括两部分:winningPoSt 和 windowPoSt。winningPoSt 是在获取出块权时,需要提供的 PoSt 证明。从所有有效的 Sector 中,抽取一个 Sector,并抽查该 Sector 上的 66 个叶子。