区块链技术迅猛发展,新想法、新概念、新名词层出不穷。本期技术研究将带大家了解区块链应用平台Polygon。
区块链技术迅猛发展,新想法、新概念、新名词层出不穷。万向区块链因此推出“技术研究报告”专栏,定期与大家分享在区块链行业创新及热门技术方面的研究成果,带领大家第一时间研究学习新技术,紧跟技术发展趋势,探索发掘技术的应用价值。
本期技术研究将带大家了解区块链应用平台Polygon。
本文作者:万向区块链通用架构技术部 杨毅
Polygon 是一个区块链应用平台,提供 PoS 和 Plasma 两种侧链。 Polygon PoS 网络具有三层架构:
为了在 Polygon 上启用权益证明 (PoS)机制,Polygon 在以太坊主网上部署了一组 PoS 管理合约。 质押合约实现以下功能:
验证者是通过链上拍卖过程选择的,该过程按定义的时间定期进行,过程如下所述:
要成为 Polygon 网络上的验证者,您必须做以下操作:
Heimdall 详细说明:https://docs.polygon.technology/docs/pos/heimdall/overview
Bor 块生产者是验证者的一个子集,并由Heimdall验证者定期洗牌。 Bor 是 Polygon 的区块生产者层,负责将交易聚合成区块的实体。目前,它是一个基本的 Geth 实现,对共识算法进行了自定义更改。 区块生产者通过 Heimdall 上的委员会选择定期改组,区块生产者的持续时间称为 Polygon 中的一个span(跨度) 。区块在Bor节点产生,其 VM 与 EVM 兼容。在 Bor 上产生的块会由 Heimdall 节点定期验证,这些块的哈希会在 Heimdall 层被组成一颗 Merkle 树,这颗 Merkle 树的根哈希会被当作一个检查点由 Heimdall 定期提交给以太坊。
Bor 层的区块生产者是根据权益从验证者池中选出的小委员会。
检查点是 Matic 协议中最关键的部分。它代表 Bor 链状态的快照,至少由 ⅔ 的验证者签名后,才能在部署在以太坊上的合约上进行验证和提交。 检查点之所以重要,有两个原因:
在提取资产时提供燃烧证明。 流程概览:
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"
}`
检查点的根哈希是使用以下方式创建的:
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)]`
是需要在每个检查点传递到以太坊链的验证者账户相关信息的哈希值。创建方式如下
// 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)]`
检查点在主链上的管理
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;
}
}
状态同步机制
interface IStateSender { function syncState(address receiver, bytes calldata data) external; }`
interface IStateReceiver { function onStateReceive(uint256 id, bytes calldata data) external; }`
简述 将资产从以太坊转移到 Polygon 再回到以太坊的完整周期的过程可以总结如下:
详细流程 存款:
取款:
Polygon POS桥支持转移 ERC20/ERC721/ERC1155 资产,每一种资产的转移流程都相差不大,只是调用的具体合约实现有所不同,这里就只以 ERC20 作为示例,具体步骤如下:
创建从主链到子链上资产映射,即在主链质押什么类型的资产,从而在子链上生成同等类型和同等数量的资产。
function mapToken( address rootToken, address childToken, bytes32 tokenType ) external;
通过调用资产合约的 approve 函数来批准 ERC20Predicate 扣除资产,批准之后才能存款。
await rootTokenContract.methods .approve(erc20Predicate, amount) .send({ from: userAddress })
调用 RootChainManager 合约的 depositFor 函数来进行资产质押。
const depositData = mainWeb3.eth.abi.encodeParameter('uint256', amount) await rootChainManagerContract.methods .depositFor(userAddress, rootToken, depositData) .send({ from: userAddress })
通过调用子资产合约的取款函数,可以在 Polygon 链上销毁资产。因为在退出步骤中需要提交此燃烧证明,所以需要存储该燃烧交易的哈希。
const burnTx = await childTokenContract.methods .withdraw(amount) .send({ from: userAddress }) const burnTxHash = burnTx.transactionHash
必须调用 RootChainManager 合约上的退出函数来解锁并从 ERC20Predicate 取回资产。必须等待包含销毁交易的检查点被提交到主链后再调用此函数才有用。
function exit(bytes calldata inputData) external;
证明由以下字段经过 RLP 编码生成:
6.1. 生成销毁证明数据 手动生成证明可能很棘手,因此建议使用 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()
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);
}
说明:这里之所以能直接将销毁日志中的 amount 转移给退出用户,其意思即为【你销毁了多少资产,我就给你退回多少资产,你最多只能销毁你目前所拥有的全部资产】,它不需要计算之前用户消耗了多少钱,因为在每一个检查点提交到主链时,之前到交易都已经得到了确认。
简述 是 Polygon 最开始提供的资产转移桥,仅支持 ETH、 ERC20 和 ERC721 资产转移,因为基于 Plasma 退出机制,Plasma 桥比 Pos 桥提供了更高的安全保证,但缺点是子链数据不可用。
存款 与 PoS 链一样。
取款 使用了欺诈性证明,采用了一种叫 MoreVP 的机制来进行挑战验证,证明过程比较复杂,感兴趣的可以看这里:基于帐户的 Plasma (MoreVP)
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!