matic checkpoint理解

2023-10-23 08:46:43 浏览数 (2)

概念

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

这里有几个问题:

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

checkpoint 是什么

checkpointMatic协议中最关键的部分。它代表了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 的StartBlockEndBlock之间的区块,这点很重要。 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

代码语言:javascript复制
// 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的生命周期。

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

代码语言: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

  1. sendCheckpointToHeimdall 监听事件
  2. cp.sendCheckpointToHeimdall 事件处理器
  3. sendCheckpointAckToHeimdall checkpoint-Ack事件
代码语言:javascript复制
// 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)
	}
}

0 人点赞