Advertisement

【区块链 | Polygon架构】万向区块链技术研究报告 | Polygon技术调研

阅读量:

目录

概述

概念说明

Polygon 架构

质押合约

Heimdall(验证节点层)

Bor(出块节点)

检查点

状态同步机制

资产转移(PoS桥)

简述

详细流程

1.【主】创建资产映射

2. 【主】允许谓词合约扣款

3.【主】存款(质押)

4.【子】销毁

5.【主】退出

6. 【主】退出引证

资产转移(Plasma桥)

简述

存款

取款

相关资料


区块链技术呈现出蓬勃发展的态势,在创新理念和新兴领域方面不断涌现新的思想、概念和技术术语。万向区块链基于这一趋势推出"技术研究报告"这一特色栏目,在行业内开展定期交流活动。本栏目旨在为读者提供 firsthand的技术创新动态分析及前沿技术研发解读,并引导大家迅速掌握新兴技术和应用趋势,在追踪全球科技发展方向的同时深入探讨其实际应用价值。
本期特别为大家深度解析了Polygon平台的技术架构与应用场景。
本文作者:万向区块链通用架构技术部 杨毅

概述

Polygon 是一个区块链应用平台,提供 PoS 和 Plasma 两种侧链。

Polygon PoS 网络具有三层架构:

以太坊层(根合约层):部署在以太坊主网上的一组质押管理合约。

Heimdall 层(验证节点层级)由一组 PoS Heimdall 节点组成,在并行运行的基础上监控部署于以太坊主网上的一组质押合约,并将 Polygon 网络检查点提交给以太坊主网。该系统采用 Tendermint 协 consensus 算法确保网络稳定性和安全性。

从 Heimdall 节点中选出的一组少量 Heimdall 节点被选作该 Bor 层(出块节点层) 的 Bor 出 块 节点。该方法构成了基于 Geth 的基本 实现。

概念说明

Polygon 架构

质押合约

为...而服务 Polygon 平台上的权益功能,在此框架下采用 Proof of Stake(PoS)机制进行管理。该技术旨在通过以太坊主网引入一组 PoS 管理合约来实现这一目标。

质押合约实现以下功能:

任何人在以太坊主网上都可以在质押合约上部署 MATIC 代币,并作为 验证者加入该系统。

通过验证 Polygon 网络上的状态转换获得质押奖励。

对双重签名、验证者停机等活动启用惩罚/削减。

在以太坊主网上进行维护操作,并在此处标记为关键节点。(https://docs.polygon.technology/docs/maintain/glossary#checkpoint-transaction "关键节点.")

PoS 机制还可以缓解 Polygon 侧链的数据不可用问题。

Heimdall(验证节点层)

Heimdall 是 Pos 验证层,负责将
Bor生成的块聚合到 Merkle 树中,并定期将 Merkle 根发布到主链。这一层需要负责的功能如下:

验证自上一个检查点以来的所有块。

创建块哈希的 Merkle 树。

将 Merkle 根哈希发布到以太坊主网。

验证者选择:

验证者采用链式拍卖流程被选中,在此流程中按照预定的时间周期定期运行。过程如下所述:

通过StateManager合约中的StakeFor()函数被调用来锁定链上的状态

heimdall上的 Bridge 收听此事件并向所有 heimdall 广播

共识验证程序添加到 heimdall 但未激活。

Validator仅在 StartEpoch 之后才开始验证

当StartEpoch触发时, 验证者将被加入到验证者集合中, 并参与共识机制的运行.

成为验证者:

要成为 Polygon 网络上的验证者,您必须做以下操作:

Sentry 节点(哨兵节点)

部署 Heimdall 和 Bor 两个节点作为独立设备运行。守护节点向 Polygon 网络中的每个节点提供开放服务。

解析

Validator 节点

启动并运行两个独立的机器:Heimdall 和 Bor。这些机器随后向主控节点提交配置信息,并由主控节点进行处理。主控节点通过特定接口与客户端进行通信,并根据接收到的数据生成相应的响应信息返回给客户端。

在部署在以太坊主网上的质押合约中质押 MATIC 资产。

深入解析Heimdall机制:[此处补充完整链接]

Bor(出块节点)

Bor组件构成验证者的子类集合,并由 Heimdall 验证器负责定期更新或轮换。

Bor 作为 Polygon 的区块生产者层,专门负责将交易聚合为区块的核心实体.目前仍基于 Geth 的基础架构,对共识机制进行了定制性地优化.

通过 Heimdall 网络中的动态调整机制,区块生产者会定期对委员会进行重新组合。这种持续运行的时间被定义为 Polygon 区块链中的一个 span(跨度)。每个区块会在 Bor 节点上生成,并且其虚拟机环境与 Ethereum 虚机(EVM)兼容。生成后,在 Bor 节点上产生的所有块都会被定期验证。这些块的哈希会被组织成一个 Merkle 栈(Merkle Tree),其中根节点处的哈希会被用作关键检查点供 Heimdall 定期提交给以太坊网络使用。

生产者选择:

Bor 层的区块生产者是根据权益从验证者池中选出的小委员会。

假设一个验证人通过质押持有Matic资产获得插槽。如果这位验证人持有100个Matic资产,并且每个插槽的价值为10,则他一共能获取10个插槽。

假设所有验证者插槽为这个数组 [ A, A, A, B, B, C ]

使用历史以太坊区块作为种子来对这个数组进行洗牌。

使用种子对插槽进行洗牌后,我们得到了这个数组 [ A, B, A, C, B, A, A]

为了验证系统性能,在顶部弹出性能验证工具时,请确保设置如下:若需要选择 3 个生产者,则将生产者的列表设定为 [A, B, A]。

因此,下一个跨度的生产者集定义为 [ A: 2, B:1 ]

该系统由基于这个验证器集合以及 tendermint 提议者的选举算法确定每个 sprint 的生产者。

检查点

核心节点是 Matic 协议中的关键组成部分。它代表着 Bor 链状态的快照,仅当至少 ⅔ 的验证者进行签名后,才能被部署到以太坊上的合约中进行验证与提交。

检查点之所以重要,有两个原因:

在根链上提供最终确定性。

在提取资产时提供燃烧证明。

流程概览:

从选池中挑选出一部分活跃的验证人充当一个Span(跨度)来处理打包节点。

检查点包含在特定时间段内创建的所有每一个区块的Merkle根哈希。所有节点都会核查或确认这些Merkle根哈希,并将各自的签名一并附上。

在验证者集中选出的一个Proposal来负责收集特定检查点的所有签名,并在以太坊主网上提交这些检查点的信息

该方案的入选条件与提交时间点将由验证方基于整个区块池中各方的所有权分布情况决定。

检查点数据结构

复制代码
 type CheckpointBlockHeader struct {

    
     // Proposer is selected based on stake
    
     Proposer        types.HeimdallAddress `json:"proposer"`
    
  
    
     // StartBlock: The block number on Bor from which this checkpoint starts
    
     StartBlock      uint64                `json:"startBlock"`
    
  
    
     // EndBlock: The block number on Bor from which this checkpoint ends
    
     EndBlock        uint64                `json:"endBlock"`
    
     
    
     // RootHash is the Merkle root of all the leaves containing the block 
    
     // headers starting from start to the end block 
    
     RootHash        types.HeimdallHash    `json:"rootHash"`
    
  
    
     // Account root hash for each validator
    
   // Hash of data that needs to be passed from Heimdall to Ethereum chain like slashing, withdraw topup etc.
    
     AccountRootHash types.HeimdallHash    `json:"accountRootHash"`
    
  
    
   // Timestamp when checkpoint was created on Heimdall
    
     TimeStamp       uint64          `json:"timestamp"`
    
 }

RootHash

RootHash是从
StartBlock
EndBlock 的 Bor 块哈希的 Merkle 根哈希。

检查点的根哈希是使用以下方式创建的:

复制代码
    blockHash = keccak256([number, time, tx hash, receipt hash])

1
n 的区块的 RootHash 的计算伪代码:

复制代码
 B(1) := keccak256([number, time, tx hash, receipt hash])

    
 B(2) := keccak256([number, time, tx hash, receipt hash]) 
    
 . . . 
    
 B(n) := keccak256([number, time, tx hash, receipt hash]) 
    
  
    
 // checkpoint is Merkle root of all block hash 
    
 checkpoint's root hash = Merkel[B(1), B(2), ....., B(n)]

AccountRootHash

该哈希值需要在每一个检查点发送至以太坊链上的验证者账户信息。具体创建方式如下:

复制代码
 // id

    
 eachAccountHash := keccak256([validator id, withdraw fee, slash amount])

1
n 的区块的
AccountRootHash 的计算伪代码:

复制代码
 B(1) := keccak256([validator id, withdraw fee, slash amount])

    
 B(2) := keccak256([validator id, withdraw fee, slash amount]) 
    
 . . . 
    
 B(n) := keccak256([validator id, withdraw fee, slash amount]) 
    
  
    
 // account root hash is Merkle root of all block hash 
    
 checkpoint's account root hash = Merkel[B(1), B(2), ....., B(n)]

检查点在主链上的管理

复制代码
 pragma solidity 0.6.6;

    
  
    
 import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
    
 import {ICheckpointManager} from "./ICheckpointManager.sol";
    
  
    
 /** * @notice Mock Checkpoint Manager contract to simulate plasma checkpoints while testing
    
 */
    
 contract MockCheckpointManager is ICheckpointManager {
    
     using SafeMath for uint256;
    
  
    
     uint256 public currentCheckpointNumber = 0;
    
  
    
     function setCheckpoint(bytes32 rootHash, uint256 start, uint256 end) public {
    
     HeaderBlock memory headerBlock = HeaderBlock({
    
         root: rootHash,
    
         start: start,
    
         end: end,
    
         createdAt: now,
    
         proposer: msg.sender
    
     });
    
  
    
     currentCheckpointNumber = currentCheckpointNumber.add(1);
    
     headerBlocks[currentCheckpointNumber] = headerBlock;
    
     }
    
 }

检查点生命周期

1. 整体流程

2. 在各层级中的流程

状态同步机制

主链

部署在主链上的合约,在发生事件时会由 StateSender 状态发送器发起状态触发。

复制代码
        1. pragma solidity 0.6.6;

    
        2.  
    
        3. interface IStateSender {
    
        4.     function syncState(address receiver, bytes calldata data) external;
    
        5. }

子链

子链节点将监控主链上的固定事件并进行分析;该系统调用按照文档中的规定以状态同步合约的形式执行;该地址在二进制表示中为...时可为此操作做准备。

系统调用有助于在不进行任何交易的情况下更改合约状态。

复制代码
        1. pragma solidity 0.6.6;

    
        2.  
    
        3. interface IStateReceiver {
    
        4.     function onStateReceive(uint256 id, bytes calldata data) external;
    
        5. }

资产转移(PoS桥)

简述

该流程可概括为:在区块链平台间完成资产迁移并回流至原平台的整体过程可通过以下方式概述:首先,在区块链平台之间完成资产迁移;其次,在Polygon平台上执行相应的操作;最后,在区块链平台之间再次完成资产迁移;整个过程包含三个关键步骤。

ERC系列代币(包括 ERC20、ERC721 和 ERC1155)的持有者需经授权,在 PoS 桥上指定的合约中质押其可供转让的代币。该特定合约被称为谓词合约(部署于以太坊生态系统中),实际上它决定了要被存储的数量。

一旦获得批准后,随后就要将资金存入相应的资产中.通过调用RootChainManager合约的功能模块,能够使Polygon链上的ChildChainManager合约得到激活.

该状态同步机制导致这一现象发生;深入学习相关信息可从 此处 获得;通常耗时在5至7分钟之间。

该系统会自动触发子资产合约的存款函数,并将相应的资产生成到客户在Polygon上的账户。值得注意的是,只有ChildChainManager能够使用子资产合约中的存款功能。

当用户获得了资产时,他们能够迅速开展交易。特别是在 Polygon 链上进行交易时, 费用极为低廉。

将资产从以太坊网络转移到Polygon网络需要遵循两个步骤:首先,在Polygon链上完成对资产的销毁灭绝;其次,在以太坊链上生成并提交相应的销毁交易证明。

移除交易并加入验证区块检查点后,该操作需经由权益证明验证者执行,重新提交至以太坊主链大概耗时约20分钟至3小时.

将丢失(丢弃)交易加入到检查点后,在以太坊的 RootChainManager 合约中使用 exit 函数生成相应的丢弃证明。

此函数(exit)会被用来由各个验证者们来验证对应检查点是否包含相应的燃烧交易证明。一旦这些验证工作完成并获得成功结果,则会触发最初的资产存入时锁定该资产的谓词合约。

最后一步, 智能合约解冻被固定的资产并返还给位于以太坊上的用户个人账户

详细流程

存款:

取款:

Polygon POS 桥可处理 ERC20/ERC721/ERC1155 资产的转移操作。各类资产的转移流程基本一致,在具体的合约实现上则各有特色。为了便于说明问题, 我们选择 ERC20 作为示例, 具体步骤见下文。

1.【主】创建资产映射

建立从主区块链到子区块链的资产映射关系,请问:通过在主链质押相应的
资产类型和数量,
即可
在子区块链上生成对应的同类型和同数量的
资产。

rootToken:生成该资产的主链合约地址

childToken:生成该资产的子链合约地址

tokenType:资产类型

复制代码
 function mapToken(

    
     address rootToken,
    
     address childToken,
    
     bytes32 tokenType
    
     ) external;

2. 【主】允许谓词合约扣款

通过触发资产合约中的approve函数以实现ERC20Predicate 扣除相应的资产,在完成上述操作后才能进行存款。

spender:批准使用用户资产的地址(谓词合约地址)

amount:批准使用的资产数量

复制代码
 await rootTokenContract.methods

    
   .approve(erc20Predicate, amount)
    
   .send({ from: userAddress })

3.【主】存款(质押)

调用
RootChainManager 合约的
depositFor 函数来进行资产质押。

user: 在 Polygon 链上接收存款的用户地址

rootToken: 是被质押的资产的合约在主链上的地址

depositData: abi 编码后的金额。

注意:在进行此调用之前,需要创建资产映射且必须批准质押金额。

复制代码
 const depositData = mainWeb3.eth.abi.encodeParameter('uint256', amount)

    
 await rootChainManagerContract.methods
    
   .depositFor(userAddress, rootToken, depositData)
    
   .send({ from: userAddress })

4.【子】销毁

利用子资产合约提供的取款功能,在Polygon主链上进行资产销毁操作。由于在退出流程中需提交该交易的燃烧证明,因此必须保存该交易对应的哈希值

amount:表示要燃烧的资产数量。

复制代码
 const burnTx = await childTokenContract.methods

    
   .withdraw(amount)
    
   .send({ from: userAddress })
    
 const burnTxHash = burnTx.transactionHash

5.【主】退出

需通过 RootChainManager 合约中的退出程序来释放并由 ERC20Predicate 获得相关资产。
只有当销毁交易的相关checkpoint被成功提交至主链时,方能执行此功能。

inputData:销毁交易的证明

复制代码
    function exit(bytes calldata inputData) external;

证明由以下字段经过 RLP 编码生成:

headerNumber - 【主】包含销毁交易数据的检查点号数

blockProof - 【子】包含销毁交易的区块在检查点中的叶子哈希(区块哈希)

blockNumber - 【子】包含销毁交易的区块号

blockTime - 【子】包含销毁交易的区块的时间

txRoot - 【子】包含销毁交易的区块的txRoot

receiptRoot - 【子】包含销毁交易的区块的receiptRoot

receipt - 【子】销毁交易的收据

receiptProof - 【子】销毁收据的 Merkle 证明

branchMask - 【子】32 bits,表示该 receipt 在 MPT 树中的路径

receiveLogIndex - 【子】可以从 receipt 中读取到销毁交易的日志的索引

6. 【主】退出引证

6.1. 生成销毁证明数据

生成证明过程可能较为复杂,在此情况下建议采用 Polygon Edge 工具进行操作。具体而言,在此场景下推荐采用 Polygon Edge 工具。如果希望手动发送交易,则可以在选项对象中将 encodeAbi 设置为 true 来获取原始调用数据。

复制代码
 const exitCalldata = await maticPOSClient

    
   .exitERC20(burnTxHash, { from, encodeAbi: true })

6.2. 发送退出交易(附带销毁证明数据)到主链合约

将 calldata 发送到
RootChainManager

复制代码
 await mainWeb3.eth.sendTransaction({

    
   from: userAddress,
    
   to: rootChainManagerAddress,
    
   data: exitCalldata.data
    
 })

6.3. 主链
RootChainManager.exit()

解析 calldata 拿到相关参数

只处理没有处理过的退出交易(不能重复退出)

设置当前退出交易为已处理

验证是否包含 receipt

验证是否包含检查点

通过谓词合约去释放退出用户的资产

复制代码
 function exit(bytes calldata inputData) external override {

    
     ExitPayloadReader.ExitPayload memory payload = inputData.toExitPayload();
    
  
    
     bytes memory branchMaskBytes = payload.getBranchMaskAsBytes();
    
     // checking if exit has already been processed
    
     // unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex)
    
     bytes32 exitHash = keccak256(
    
     abi.encodePacked(
    
         payload.getBlockNumber(),
    
         // first 2 nibbles are dropped while generating nibble array
    
         // this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only)
    
         // so converting to nibble array and then hashing it
    
         MerklePatriciaProof._getNibbleArray(branchMaskBytes),
    
         payload.getReceiptLogIndex()
    
     )
    
     );
    
  
    
     require(
    
     processedExits[exitHash] == false,
    
     "RootChainManager: EXIT_ALREADY_PROCESSED"
    
     );
    
     processedExits[exitHash] = true;
    
  
    
     // 解析 receipt 数据
    
     ExitPayloadReader.Receipt memory receipt = payload.getReceipt();
    
     ExitPayloadReader.Log memory log = receipt.getLog();
    
  
    
     // log should be emmited only by the child token
    
     address rootToken = childToRootToken[log.getEmitter()];
    
     require(
    
     rootToken != address(0),
    
     "RootChainManager: TOKEN_NOT_MAPPED"
    
     );
    
  
    
     address predicateAddress = typeToPredicate[
    
     tokenToType[rootToken]
    
     ];
    
  
    
     // branch mask can be maximum 32 bits
    
     require(
    
     payload.getBranchMaskAsUint() &
    
     0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 ==
    
     0,
    
     "RootChainManager: INVALID_BRANCH_MASK"
    
     );
    
  
    
     // 验证收据内容
    
     require(
    
     MerklePatriciaProof.verify(
    
         receipt.toBytes(),
    
         branchMaskBytes,
    
         payload.getReceiptProof(),
    
         payload.getReceiptRoot()
    
     ),
    
     "RootChainManager: INVALID_PROOF"
    
     );
    
  
    
     // 验证检查点内容
    
     _checkBlockMembershipInCheckpoint(
    
     payload.getBlockNumber(), 
    
     payload.getBlockTime(), 
    
     payload.getTxRoot(), 
    
     payload.getReceiptRoot(), 
    
     payload.getHeaderNumber(), 
    
     payload.getBlockProof()
    
     );
    
  
    
     // 取款
    
     ITokenPredicate(predicateAddress).exitTokens(
    
     _msgSender(),
    
     rootToken,
    
     log.toRlpBytes()
    
     );
    
 }
    
  
    
 function _checkBlockMembershipInCheckpoint(
    
     uint256 blockNumber,
    
     uint256 blockTime,
    
     bytes32 txRoot,
    
     bytes32 receiptRoot,
    
     uint256 headerNumber,
    
     bytes memory blockProof
    
 ) private view returns (uint256) {
    
     (
    
     bytes32 headerRoot,
    
     uint256 startBlock,
    
     ,
    
     uint256 createdAt,
    
  
    
     ) = _checkpointManager.headerBlocks(headerNumber);
    
  
    
     require(
    
     keccak256(
    
         abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot)
    
     )
    
         .checkMembership(
    
         blockNumber.sub(startBlock),
    
         headerRoot,
    
         blockProof
    
     ),
    
     "RootChainManager: INVALID_HEADER"
    
     );
    
     return createdAt;
    
 }

谓词合约:

复制代码
     /** * @notice Validates log signature, from and to address
    
      * then sends the correct amount to withdrawer
    
      * callable only by manager
    
      * @param rootToken Token which gets withdrawn
    
      * @param log Valid ERC20 burn log from child chain
    
      */
    
     function exitTokens(
    
     address,
    
     address rootToken,
    
     bytes memory log
    
     )
    
     public
    
     override
    
     only(MANAGER_ROLE)
    
     {
    
     RLPReader.RLPItem[] memory logRLPList = log.toRlpItem().toList();
    
     RLPReader.RLPItem[] memory logTopicRLPList = logRLPList[1].toList(); // topics
    
  
    
     require(
    
         bytes32(logTopicRLPList[0].toUint()) == TRANSFER_EVENT_SIG, // topic0 is event sig
    
         "ERC20Predicate: INVALID_SIGNATURE"
    
     );
    
  
    
     address withdrawer = address(logTopicRLPList[1].toUint()); // topic1 is from address
    
  
    
     require(
    
         address(logTopicRLPList[2].toUint()) == address(0), // topic2 is to address
    
         "ERC20Predicate: INVALID_RECEIVER"
    
     );
    
     
    
     uint256 amount = logRLPList[2].toUint(); // log data field is the amount
    
  
    
     IERC20(rootToken).safeTransfer(
    
         withdrawer,
    
         amount
    
     );
    
  
    
     emit ExitedERC20(withdrawer, rootToken, amount);
    
     }

你销毁了多少资产,我就给你退回多少资产,你最多只能销毁你目前所拥有的全部资产

资产转移(Plasma桥)

简述

Polygon最初提供的资产转移桥仅支持以太坊(ETH)、ERC20代币和ERC721代币的资产转移。该桥基于Plasma退出机制相较于以太坊主链桥(即Pos桥)提供更高的安全性保证然而其缺点在于子链数据不具备可访问性。

用户在主链上的 Polygon 合约中存入资产

一旦该等资产已获得主链上的确认,则对应的资产将在 Polygon 链上生成(约需 5 至 7 分钟)

当用户有需求时,他们可从主链上取回剩余资产. Plasma 侧链启动资产抽取过程,并将在每30分钟的时间间隔内执行检查.

当提交至主链以太坊合约时, 将生成一个等值的Exit NFT(ERC721)代币。

借助该 NFT, 持有者可通过退出主链合约中的指定程序, 将资产转移到自己的以太坊账户。需在7天内完成相应的操作流程。

存款

与 PoS 链一样。

取款

采用了欺诈性证明作为基础,并引入了一种名为MoreVP的机制用于完成挑战验证工作。其验证流程较为 intricate,
如需深入了解,请参考以下链接:
基于帐户的 Plasma (MoreVP)

相关资料

MATIC网络的Polygon代币合约代码库:https://github.com/maticnetwork/pos-portal/tree/master/contracts

Polygon 官方发布页面:https://docs.polygon.technology/docs/pos/polygon-architecture

以账户为基础的Plasma系统(MoreVP版本):https://ethresear.ch/t/account-based-plasma-morevp/5480

全部评论 (0)

还没有任何评论哟~