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

  • 科帆
  • 发布于 2020-10-19 12:06
  • 阅读 5841

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

上次文章写了下双层网络,收到了一些好评也遇到了些问题。这篇文章将主要讲解轻节点证明和收据证明,只有节点可靠即节点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 条评论

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