去中心化跨链之轻节点和收据证明

  • 科帆
  • 更新于 2020-10-19 12:06
  • 阅读 3970

上篇讲过双层网络,这篇主要讲解轻节点验证和收据证明。

上次文章写了下双层网络,收到了一些好评也遇到了些问题。这篇文章将主要讲解轻节点证明和收据证明,只有节点可靠即节点MMR验证通过才会去获取收据证明。轻节点证明中以太坊使用了CHT去证明节点安全性,CHT目前实现是POA多签合约更新CHT树根,以太坊区块头收据根目前只有是块里所有收据的验证,没实现单一收据的证明,对跨链交易不是很友好,证明的共性是都是基于树的特性去验证的,通过构建证明所需要的路径来证明数据的正确性。

文章将基于去中心化跨链之双层网络讲,这篇文章遇到了RPCP2P的区别,从以太坊的狭隘理解上,P2P需要节点发现,数据加密,连接管理,心跳检测,网络协议,广播策略,序列化。RPC倾向于查询Dapp交互,API访问管理,数据JOSN化,账户,HTTP访问相关的问题。两者实现的都是远程调用,实现的方式有所区别。

轻节点证明

现在提跨链,言必提轻节点,有要把轻节点实现到EVM合约合约里面的,有在波卡Runtime里面实现轻节点的。目前没看到这两种实现的开源代码。跨链实现轻节点的目的是轻节点占用资源少,数据可验证,毕竟跨链交易跟本链内交易比还是少数。所有没有运行全节点的必要性。

CHT证明

在以太坊全节点中,每params.CHTFrequency=32768个区块,会将区块hashtd存到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的proof证明是否可信,成功获取到叶子节点value
  • 轻节点获取的区块头HASH方法和value中获取ChtNode中hash比较
  • 比较CHT的高度是否和区块头的一致

经过如上证明,可知对方节点当前可信,这是之后验证的起点,之前的几百万区块不需要下载,这样轻节点在手机,浏览器才有可能运行。

MMR证明

讲解了如上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的数据即可证明节点的安全性,被称为超轻节点。网上有很多这方面的介绍,这里主要说下如何实现。有两个地方需要更改

  • 矿工挖矿需要更新区块头MMR根调用PushBlockInMMR
  • 验证节点校验MMR并更新树

MMR第一次验证

节点第一次握手的时候,需要校验对方是否为可信的对链节点

            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证明。

BestPeer请求证明

请求证明的过程涉及到找哪一个Peer请求数据的流程

    if ev, ok := obj.Data.(core.NewRequestTxProofEvent); ok {
        peer := pm.peersOther.BestPeer()
        if peer != nil {
            peer.RequestMMRReceipts([]common.Hash{ev.TxHash})
        }
    }

通过寻找难度最高的节点请求数据是最好的,如果难度最高的节点验证失败,找次之节点继续验证

对链Hash通知

本链每个区块的Hash和难度都会通过NewOtherBlockHashesMsg广播到对链,这样对链有了一个缓存本链哪个Peer的区块是最新的机制。

MMR第二次请求

当节点收到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})

如果验证成功,矿工需要转换跨链为新的铸币交易,这部分在下篇继续梳理。目前已把节点证明和收据证明梳理完。

Mouse和Duck跨链github链接

感兴趣的朋友欢迎一起讨论。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
科帆
科帆
江湖只有他的大名,没有他的介绍。