概念

checkpointMatic协议中最关键的部分。它代表了Bor链状态的快照,应该由⅔+的验证器集证明,然后再验证并提交给部署在以太坊上的合约。

这里有几个问题:

  1. checkpoint 是什么
  2. 为什么要提交bor的状态,状态中包含哪些信息
  3. checkpoint 验证流程

checkpoint 是什么

checkpointMatic协议中最关键的部分。它代表了Bor链状态的快照,应该由⅔+的validator集证明,然后再验证并提交给部署在以太坊上的合约。

Heimdall 层允许将 Bor 生成的区块聚合到单个 Merkle 根中,并定期将其发布到以太坊主链。
此已发布状态也称为检查点,因此整个过程称为validator(检查点)
检查点提议者最初是通过 Tendermint 的加权循环算法选择的。

checkpoint 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 的StartBlockEndBlock之间的区块,这点很重要。
bor是基于Ethereum协议实现的底层链。官方定义

checkpoint

blockHash

1
blockHash = keccak256([number, time, tx hash, receipt hash])

rootHash

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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

总体流程

  1. 侧链提交 checkpoint
  2. Validator 接收、验证checkpoint,并提交主链
  3. 主链接收checkpoint,并发送checkpoint-ack
  4. Validator 接收、验证 checkpoint-ack

Validator 层通过 bridge模块监听主链侧链上的合约事件。

质押:质押链为ETH主链

存款:发生在ETH主链

取款:发生在MATIC侧链
下面的流程图代表了checkpoint的生命周期。
checkpoint流程

Heimdall使用与Tendermint相同的共识算法来选择下一个Proposer。Proposer 也就是 Heimdall 层的出块者,在 Matic 中发分起一个 Propose 提案

在以太坊链上提交checkpoint时,可能会因为多种原因而失败,如gas limit,以太坊拥堵,高gas费用。这就是为什么需要多阶段的checkpoint过程。

因为每个checkpointProposer提起的,而每个validator都有机会被选举为Proposer
如果提交以太坊链上的checkpoint成功或失败,将会发送ackno-ack交易将改变Heimdall上的提议者,以进行下一个检查点。

Checkpoint 流程

![Checkpoint 流程](checkpointMessage 流程.jpg)

那么问题来了,Heimdall 链是怎么知道 checkpoint 提交 Ethereum主链成功没成功?

Heimdall项目中的bor模块,是基于Ethereum实现ETH协议,实际就是包装了EVM,可以接收ETH广播的区块,并监听合约事件,从事件中获取需要的事件信息。

Checkpoint 事件监听

看下 checkpoint 相关的事件监听,heimdall 的事件处理通过将监听器监听到的事件,发送到队列当中,由事件处理器进行处理。
这些逻辑在 bridge 模块中进行。

关键事件:

  1. sendCheckpointToHeimdall
  2. sendCheckpointToRootchain
  3. sendCheckpointAckToHeimdall

Rootchain是Ethereum
Heimdall是matic的中间层

获取监听事件

bridge/setu/listener/heimdall.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 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的内部队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

  1. sendCheckpointToHeimdall 监听事件
  2. cp.sendCheckpointToHeimdall 事件处理器
  3. sendCheckpointAckToHeimdall checkpoint-Ack事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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)
}
}