概念
checkpoint
是Matic
协议中最关键的部分。它代表了Bor
链状态的快照,应该由⅔ 的验证器集证明,然后再验证并提交给部署在以太坊上的合约。
这里有几个问题:
- checkpoint 是什么
- 为什么要提交bor的状态,状态中包含哪些信息
- checkpoint 验证流程
checkpoint 是什么
checkpoint
是Matic
协议中最关键的部分。它代表了Bor
链状态的快照,应该由⅔ 的validator
集证明,然后再验证并提交给部署在以太坊上的合约。
Heimdall 层允许将 Bor 生成的区块聚合到单个 Merkle 根中,并定期将其发布到以太坊主链。
此已发布状态也称为检查点,因此整个过程称为validator(检查点)
。
检查点提议者最初是通过 Tendermint
的加权循环算法选择的。
checkpoint 结构
代码语言:javascript复制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"`
}
注意:这里 checkpoint 的提交是基于 bor 的StartBlock
到EndBlock
之间的区块,这点很重要。
bor
是基于Ethereum协议
实现的底层链。官方定义
blockHash
代码语言:javascript复制blockHash = keccak256([number, time, tx hash, receipt hash])
rootHash
代码语言:javascript复制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)]
代码实现
下面是 Bor 链区块创建checkpoint
的代码片段,官方代码:
https://github.com/maticnetwork/heimdall/blob/develop/checkpoint/types/merkel.go#L60-L114
// Golang representation of block data used in checkpoint
blockData := crypto.Keccak256(appendBytes32(
blockHeader.Number.Bytes(),
new(big.Int).SetUint64(blockHeader.Time).Bytes(),
blockHeader.TxHash.Bytes(),
blockHeader.ReceiptHash.Bytes(),
))
// array of block hashes of Bor blocks
headers := [blockData1, blockData2, ..., blockDataN]
// merkel tre
tree := merkle.NewTreeWithOpts(merkle.TreeOptions{EnableHashSorting: false, DisableHashLeaves: true})
tree.Generate(convert(headers), sha3.NewLegacyKeccak256())
// create checkpoint's root hash
rootHash := tree.Root().Hash
总体流程
- 侧链提交 checkpoint
- Validator 接收、验证checkpoint,并提交主链
- 主链接收checkpoint,并发送checkpoint-ack
- Validator 接收、验证 checkpoint-ack
Validator
层通过 bridge
模块监听主链
和侧链
上的合约事件。
质押:质押链为ETH主链
存款:发生在ETH主链
取款:发生在MATIC侧链
下面的流程图代表了checkpoint
的生命周期。
Heimdall
使用与Tendermint
相同的共识算法
来选择下一个Proposer。Proposer
也就是 Heimdall
层的出块者,在 Matic 中发分起一个 Propose 提案
在以太坊链上提交checkpoint
时,可能会因为多种原因而失败,如gas limit
,以太坊拥堵,高gas
费用。这就是为什么需要多阶段的checkpoint
过程。
因为每个checkpoint
都Proposer
提起的,而每个validator
都有机会被选举为Proposer
。
如果提交以太坊链上的checkpoint
成功或失败,将会发送ack
和no-ack
交易将改变Heimdall上的提议者,以进行下一个检查点。
Checkpoint 流程
![Checkpoint 流程](checkpointMessage 流程.jpg)
那么问题来了,Heimdall 链是怎么知道 checkpoint 提交 Ethereum主链成功没成功?
Heimdall项目中的bor模块,是基于Ethereum
实现ETH协议,实际就是包装了EVM,可以接收ETH广播的区块,并监听合约事件,从事件中获取需要的事件信息。
Checkpoint 事件监听
看下 checkpoint 相关的事件监听,heimdall 的事件处理通过将监听器监听到的事件,发送到队列当中,由事件处理器进行处理。
这些逻辑在 bridge
模块中进行。
关键事件:
- sendCheckpointToHeimdall
- sendCheckpointToRootchain
- sendCheckpointAckToHeimdall
Rootchain是Ethereum
Heimdall是matic的中间层
获取监听事件
bridge/setu/listener/heimdall.go
代码语言:javascript复制// ProcessBlockEvent - process Blockevents (BeginBlock, EndBlock events) from heimdall.
func (hl *HeimdallListener) ProcessBlockEvent(event sdk.StringEvent, blockHeight int64) {
hl.Logger.Info("Received block event from Heimdall", "eventType", event.Type, "height", blockHeight)
eventBytes, err := json.Marshal(event)
if err != nil {
hl.Logger.Error("Error while parsing block event", "error", err, "eventType", event.Type)
return
}
switch event.Type {
case checkpointTypes.EventTypeCheckpoint:
//发送事件到队列
hl.sendBlockTask("sendCheckpointToRootchain", eventBytes, blockHeight)
case checkpointTypes.EventTypeCheckpointSync:
hl.sendBlockTask("sendCheckpointSyncToStakeChain", eventBytes, blockHeight)
case slashingTypes.EventTypeSlashLimit:
hl.sendBlockTask("sendTickToHeimdall", eventBytes, blockHeight)
case slashingTypes.EventTypeTickConfirm:
hl.sendBlockTask("sendTickToRootchain", eventBytes, blockHeight)
case stakingTypes.EventTypeValidatorJoin,
stakingTypes.EventTypeSignerUpdate,
stakingTypes.EventTypeValidatorExit,
stakingTypes.EventTypeStakingSyncAck:
hl.sendBlockTask("sendStakingSyncToHeimdall", eventBytes, blockHeight)
case stakingTypes.EventTypeStakingSync:
hl.sendBlockTask("sendStakingSyncToRootChain", eventBytes, blockHeight)
default:
hl.Logger.Debug("BlockEvent Type mismatch", "eventType", event.Type)
}
}
发送事件到队列
hl.queueConnector 是Heimdall的内部队列
代码语言:javascript复制func (hl *HeimdallListener) sendBlockTask(taskName string, eventBytes []byte, blockHeight int64) {
// create machinery task
signature := &tasks.Signature{
Name: taskName,
Args: []tasks.Arg{
{
Type: "string",
Value: string(eventBytes),
},
{
Type: "int64",
Value: blockHeight,
},
},
}
signature.RetryCount = 3
signature.RetryTimeout = 3
hl.Logger.Info("Sending block level task",
"taskName", taskName, "eventBytes", eventBytes, "currentTime", time.Now(), "blockHeight", blockHeight)
// send task
_, err := hl.queueConnector.Server.SendTask(signature)
if err != nil {
hl.Logger.Error("Error sending block level task", "taskName", taskName, "blockHeight", blockHeight, "error", err)
}
}
处理队列的 checkpoint 事件
bridge/setu/processor/checkpoint.go
sendCheckpointToHeimdall
监听事件cp.sendCheckpointToHeimdall
事件处理器sendCheckpointAckToHeimdall
checkpoint-Ack事件
// RegisterTasks - Registers checkpoint related tasks with machinery
func (cp *CheckpointProcessor) RegisterTasks() {
cp.Logger.Info("Registering checkpoint tasks")
if err := cp.queueConnector.Server.RegisterTask("sendCheckpointToHeimdall", cp.sendCheckpointToHeimdall); err != nil {
cp.Logger.Error("RegisterTasks | sendCheckpointToHeimdall", "error", err)
}
if err := cp.queueConnector.Server.RegisterTask("sendCheckpointToRootchain", cp.sendCheckpointToRootchain); err != nil {
cp.Logger.Error("RegisterTasks | sendCheckpointToRootchain", "error", err)
}
if err := cp.queueConnector.Server.RegisterTask("sendCheckpointAckToHeimdall", cp.sendCheckpointAckToHeimdall); err != nil {
cp.Logger.Error("RegisterTasks | sendCheckpointAckToHeimdall", "error", err)
}
if err := cp.queueConnector.Server.RegisterTask("sendCheckpointSyncToStakeChain", cp.sendCheckpointSyncToStakeChain); err != nil {
cp.Logger.Error("RegisterTasks | sendCheckpointSyncToStakeChain", "error", err)
}
if err := cp.queueConnector.Server.RegisterTask("sendCheckpointSyncAckToHeimdall", cp.sendCheckpointSyncAckToHeimdall); err != nil {
cp.Logger.Error("RegisterTasks | sendCheckpointSyncAckToHeimdall", "error", err)
}
}