上篇讲过双层网络,这篇主要讲解轻节点验证和收据证明。
上次文章写了下双层网络,收到了一些好评也遇到了些问题。这篇文章将主要讲解轻节点证明和收据证明,只有节点可靠即节点MMR验证通过才会去获取收据证明。轻节点证明中以太坊使用了CHT去证明节点安全性,CHT目前实现是POA多签合约更新CHT树根,以太坊区块头收据根目前只有是块里所有收据的验证,没实现单一收据的证明,对跨链交易不是很友好,证明的共性是都是基于树的特性去验证的,通过构建证明所需要的路径来证明数据的正确性。
文章将基于去中心化跨链之双层网络讲,这篇文章遇到了RPC
和P2P
的区别,从以太坊的狭隘理解上,P2P需要节点发现
,数据加密
,连接管理
,心跳检测
,网络协议
,广播策略
,序列化
。RPC倾向于查询
,Dapp
交互,API
访问管理,数据JOSN
化,账户,HTTP
访问相关的问题。两者实现的都是远程调用,实现的方式有所区别。
现在提跨链,言必提轻节点,有要把轻节点实现到EVM合约合约里面的,有在波卡Runtime里面实现轻节点的。目前没看到这两种实现的开源代码。跨链实现轻节点的目的是轻节点占用资源少,数据可验证,毕竟跨链交易跟本链内交易比还是少数。所有没有运行全节点的必要性。
在以太坊全节点中,每params.CHTFrequency=32768
个区块,会将区块hash
,td
存到CHT树中,key为区块高度。
td := rawdb.ReadTd(c.diskdb, hash, num)
binary.BigEndian.PutUint64(encNumber[:], num)
data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
c.trie.Update(encNumber[:], data)
看过以太坊代码应该会注意到如下代码
MainnetTrustedCheckpoint = &TrustedCheckpoint{
SectionIndex: 333,
SectionHead: common.HexToHash("0xb80784cbe88077e5911b446765edc814dd67ca3f6bdd33b6ec72d66058df4a11"),
CHTRoot: common.HexToHash("0x4da9cde840dd3de39916620f7a97674c5747a89a9359e6b918e134d199a8dd45"),
BloomRoot: common.HexToHash("0xdd0f4fef7fa2a5cc05d49568e38f15dab24098ffc7677a2e35d1a8d67f5458af"),
}
这是之前硬编码到代码中轻节点CHT验证需要的数据,后来感觉每次升级要更改这个和安全性太低升级为验证人合约。
MainnetCheckpointOracle = &CheckpointOracleConfig{
Address: common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"),
Signers: []common.Address{
common.HexToAddress("0x1b2C260efc720BE89101890E4Db589b44E950527"), // Peter
common.HexToAddress("0x78d1aD571A1A09D60D9BBf25894b44e4C8859595"), // Martin
common.HexToAddress("0x286834935f4A8Cfb4FF4C77D5770C2775aE2b0E7"), // Zsolt
common.HexToAddress("0xb86e2B0Ab5A4B1373e40c51A7C712c70Ba2f9f8E"), // Gary
common.HexToAddress("0x0DF8fa387C602AE62559cC4aFa4972A7045d6707"), // Guillaume
},
}
下面来看下这个轻节点简化验证流程
header := new(types.Header)
if err := rlp.DecodeBytes(headerEnc, header); err != nil
// Verify the CHT
value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
var node light.ChtNode
if err := rlp.DecodeBytes(value, &node); err != nil
if node.Hash != header.Hash()
if r.BlockNum != header.Number.Uint64()
}
可以看到,首先如下验证
经过如上证明,可知对方节点当前可信,这是之后验证的起点,之前的几百万区块不需要下载,这样轻节点在手机,浏览器才有可能运行。
讲解了如上CHT
证明,我们来看下MMR
证明。MMR证明需要在区块头里面加MMR的根,每挖到新区块的时候都需要更新MMR的根
n := ulvp.NewNode(b.Hash(), d, new(big.Int).Set( b.Difficulty()), big.NewInt(0), time)
mmr.Push(n)
MMR主要是靠采样,根据安全精度决对采样数据的数量,只需要logN的数据即可证明节点的安全性,被称为超轻节点。网上有很多这方面的介绍,这里主要说下如何实现。有两个地方需要更改
节点第一次握手的时候,需要校验对方是否为可信的对链节点
。
proof, err := uLVP.PushFirstMsg()
errc <- p2p.Send(p.rw, StatusMsg, &statusData{
ProtocolVersion: uint32(p.version),
NetworkID: network,
TD: td,
Head: head,
Genesis: genesis,
Proof: [][]byte{proof},
})
需要将MMR Proof
证明发给对方节点,证明包含如下部分
Right, heads := getRightDifficult(uv.localChain, curNum, new(big.Int).Set(cur.Difficulty))
proof, _, _ := uv.MmrInfo.CreateNewProof(Right)
heads = append([]*types.Header{genesis.Header(), cur}, heads...)
res := &ulvp.ChainHeaderProofMsg{
Proof: proof,Header: heads,Right: Right,}
对链节点握手验证
func (p *peer) readOtherStatus(network uint64, status *statusData, genesis common.Hash, uLVP *core.SimpleULVP) error {
msg, err := p.rw.ReadMsg()
// Decode the handshake and make sure everything matches
if err := msg.Decode(&status); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
if status.Genesis != genesis
if err := uLVP.VerifyFirstMsg(status.Proof[0]); err != nil
return nil
}
创世校验后调用MMR验证流程。
以太坊收据中存在只能整体校验,不能单独验证每个receipt的正确性。可通过查看如下代码
func DeriveSha(list DerivableList, hasher Hasher) common.Hash {
hasher.Reset()
keybuf := new(bytes.Buffer)
for i := 0; i < list.Len(); i++ {
keybuf.Reset()
rlp.Encode(keybuf, uint(i))
hasher.Update(keybuf.Bytes(), list.GetRlp(i))
}
return hasher.Hash()
}
收据树的构造是通过索引做key,验证的时候难以验证收据在区块中的索引,所以难以单独校验,通过将索引改为TxHash可解决此问题,不过需要在验证区块的时候加上交易的排序才可以。
第一篇文章讲到跨链交易转发后停了,现在接着继续讲。
case msg.Code == OtherTransactionMsg || (msg.Code == PooledTransactionsMsg && p.version >= eth65):
var txs []*types.Transaction
if err := msg.Decode(&txs); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
for i, tx := range txs {
// Validate and mark the remote transaction
p.MarkTransaction(tx.Hash())
}
// Broadcast the block and announce chain insertion event
pm.eventMux.Post(core.NewOtherTxsEvent{Txs: txs})
网络中收到跨链交易后,将交易转发给矿工模块。
if ev, ok := ev.Data.(core.NewOtherTxsEvent); ok {
for _, tx := range ev.Txs {
if w.insertCM(tx) && !request {
request = true
w.requestCrossTxProof(tx.Hash())
}
}
}
矿工将交易插入缓存并去重,调用请求收据proof证明。
请求证明的过程涉及到找哪一个Peer请求数据的流程
if ev, ok := obj.Data.(core.NewRequestTxProofEvent); ok {
peer := pm.peersOther.BestPeer()
if peer != nil {
peer.RequestMMRReceipts([]common.Hash{ev.TxHash})
}
}
通过寻找难度最高的节点请求数据是最好的,如果难度最高的节点验证失败,找次之节点继续验证
本链每个区块的Hash和难度都会通过NewOtherBlockHashesMsg
广播到对链,这样对链有了一个缓存本链哪个Peer的区块是最新的机制。
当节点收到GetMMRReceiptProofMsg
消息,为对链查询跨链消息的证明。
case msg.Code == GetMMRReceiptProofMsg:
var query getBlockMMRData
if err := msg.Decode(&query)
var mtProof ulvp.SimpleUlvpProof
receiptRep, receipt, err := pm.ulVP.GetReceiptProof(query.TxHash)
data, err := pm.ulVP.HandleSimpleUlvpMsgReq(pm.ulVP.GetSimpleUlvpMsgReq([]uint64{receipt.BlockNumber.Uint64(), pm.blockchain.CurrentBlock().NumberU64()}))
mtProof.Result = true
mtProof.ReceiptProof = receiptRep
mtProof.ChainProof = &ulvp.UlvpChainProof{Res: data}
mtProof.Header = pm.blockchain.GetHeaderByHash(receipt.BlockHash)
mtProof.End = pm.blockchain.CurrentBlock().Number()
mtProof.TxHash = query.TxHash
首先拿到对链请求的Txhash
,查找此交易收据是否存在,生成收据的proof证明。
func (uv *SimpleULVP) GetReceiptProof(txHash common.Hash) (*ulvp.ReceiptTrieResps, *types.Receipt, error) {
lookup := uv.localChain.GetTransactionLookup(txHash)
receipts := uv.localChain.GetReceiptsByHash(lookup.BlockHash)
tri := types.DeriveShaHasher(receipts, new(trie.Trie))
keybuf := new(bytes.Buffer)
keybuf.Reset()
rlp.Encode(keybuf, lookup.Index)
proofs := types.NewNodeSet()
tri.Prove(keybuf.Bytes(), 0, proofs)
return &ulvp.ReceiptTrieResps{Proofs: proofs.NodeList(), Index: lookup.Index, ReceiptHash: block.ReceiptHash()}, receipt, nil
}
根据交易所在高度生成MMR证明,由于高度发生改变,需要重新采样,故此时需要新的MMR证明。
data, err := pm.ulVP.HandleSimpleUlvpMsgReq(pm.ulVP.GetSimpleUlvpMsgReq([]uint64{receipt.BlockNumber.Uint64(), pm.blockchain.CurrentBlock().NumberU64()}))
将收据所在高度和当前区块的高度传进去生成证明。将两个证明拼在一起发给对方节点。
节点收到验证数据,验证MMR和收据证明是否正确,校验失败将把本peer踢掉,然后通知矿工从新请求。
case msg.Code == MMRReceiptProofMsg:
var request *ulvp.SimpleUlvpProof
if err := msg.Decode(&request); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err)
}
find := false
if !request.Result {
find = true
} else if _, err := request.VerifyULVPTXMsg(request.TxHash); err != nil {
find = true
}
if find {
pm.removeOtherPeer(p.id)
request.Result = false
}
pm.eventMux.Post(core.NewProofEvent{MRProof: request})
如果验证成功,矿工需要转换跨链为新的铸币交易,这部分在下篇继续梳理。目前已把节点证明和收据证明梳理完。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!