1. Encoder (Pulp)
1.1 Pulp
源码链接:https ://github.com/maticnetwork/heimdall/blob/master/auth/types/pulp.go
Heimdall 需要验证 Heimdall 在以太坊链上的交易。为此,它使用 RLP 编码来生成特殊事务,例如 checkpoint。
此特殊事务使用pulp(基于 RLP)编码而不是默认的氨基编码。
Pulp 使用基于前缀的简单编码机制来解决接口解码。检查GetPulpHash方法。
const (
// PulpHashLength pulp hash length
PulpHashLength int = 4
)
// GetPulpHash returns string hash
func GetPulpHash(name string) []byte {
return crypto.Keccak256([]byte(name))[:PulpHashLength]
}
下面返回给定的前缀字节msg。下面是一个关于如何为 pulp 编码注册对象的示例。
RegisterConcreate(name, obj) {
rtype := reflect.TypeOf(obj)
// set record for name => type of the object
p.typeInfos[hex.EncodeToString(GetPulpHash(name))] = rtype
}
// register "A"
pulp.RegisterConcrete("A", A{})
编码只是 RLP 编码并GetPulpHash在name
// EncodeToBytes encodes msg to bytes
txBytes, err := rlp.EncodeToBytes(obj)
if err != nil {
return nil, err
}
result := append(GetPulpHash("A"), txBytes[:]...), nil
解码过程如下:
// retrieve type of objet based on prefix
rtype := typeInfos[hex.EncodeToString(incomingData[:PulpHashLength])]
// create new object
newMsg := reflect.New(rtype).Interface()
// decode without prefix and inject into newly created object
if err := rlp.DecodeBytes(incomingData[PulpHashLength:], newMsg); err != nil {
return nil, err
}
// result => newMsg
Cosmos SDK 使用两种二进制线路编码协议, 即Amino 和 Protocol Buffers,其中 Amino 是一种对象编码规范。它是 Proto3 的一个子集,具有接口支持的扩展。 Amino 在很大程度上兼容(但不兼容 Proto2)。
2. 交易
事务由保存在上下文中的元数据和 通过模块的 Handler 触发模块内状态更改的消息组成。
当用户想要与应用程序交互并进行状态更改(例如充提币)时,他们会创建交易。message在将交易广播到网络之前,必须使用与适当帐户关联的私钥对每笔交易进行签名。然后必须将交易包含在一个区块中,经过验证,然后通过共识过程由网络批准。
2.1. 类型定义
事务对象是实现 Tx 接口的 SDK 类型
type Tx interface {
// Gets all the transaction's messages.
GetMsgs() []Msg
// ValidateBasic does a simple and lightweight validation check that doesn't
// require access to any other information.
ValidateBasic() Error
}
3. 标准交易
源码: https ://github.com/maticnetwork/heimdall/blob/master/auth/types/stdtx.go
Heimdall 的 StdTx 不是每笔交易都使用 Fee。我们的交易类型非常有限,并且由于最终用户不会在 Heimdall 上部署任何类型的合约,它使用固定费用模式进行交易。
源码: https ://github.com/maticnetwork/heimdall/blob/master/auth/ante.go#L32
// StdTx is a standard way to wrap a Msg with Fee and Signatures.
type StdTx struct {
Msg sdk.Msg `json:"msg" yaml:"msg"`
Signature StdSignature `json:"signature" yaml:"signature"`
Memo string `json:"memo" yaml:"memo"`
}
1.4. 数据类型
4.1. Heimdall 地址
HeimdallAddress代表 Heimdall 上的地址。它使用以太坊的通用地址库。该地址的长度为 20 个字节。
// HeimdallAddress represents Heimdall address
type HeimdallAddress common.Address
4.2. Heimdall 公钥
它表示 Heimdall 中使用的公钥,ecdsa兼容的未压缩公钥:
// PubKey pubkey
type PubKey [65]byte
4.3. Heimdall Hash
它代表 Heimdall 中的哈希。它使用以太坊的哈希值。
// HeimdallHash represents heimdall address
type HeimdallHash common.Hash
5. 验证器
验证器是 Heimdall 的重要组成部分。Heimdall 可以选择在每个区块结束时更改验证器。它被称为EndBlockerCosmos-SDK 的一部分:https ://docs.cosmos.network/master/building-modules/beginblock-endblock.html
Heimdall 对验证器使用以下结构:
// Validator for Heimdall
type Validator struct {
ID ValidatorID `json:"ID"`
StartEpoch uint64 `json:"startEpoch"`
EndEpoch uint64 `json:"endEpoch"`
VotingPower int64 `json:"power"`
PubKey PubKey `json:"pubKey"`
Signer HeimdallAddress `json:"signer"`
LastUpdated string `json:"last_updated"`
ProposerPriority int64 `json:"accum"`
}
// ValidatorID validator ID and helper functions
type ValidatorID uint64
这里StartEpoch和EndEpoch是验证器之间的检查点编号将作为验证器处于活动状态。在EndBlocker应用程序中,Heimdall 获取所有活动的验证器并更新状态中的当前验证器集。
最后,它返回 Tendermint 的验证器更新。
源码链接:https ://github.com/maticnetwork/heimdall/blob/develop/app/app.go#L500-L542
...
// --- Start update to new validators
currentValidatorSet := app.StakingKeeper.GetValidatorSet(ctx)
allValidators := app.StakingKeeper.GetAllValidators(ctx)
ackCount := app.CheckpointKeeper.GetACKCount(ctx)
// get validator updates
setUpdates := helper.GetUpdatedValidators(
¤tValidatorSet, // pointer to current validator set -- UpdateValidators will modify it
allValidators, // All validators
ackCount, // ack count
)
if len(setUpdates) > 0 {
// create new validator set
if err := currentValidatorSet.UpdateWithChangeSet(setUpdates); err != nil {
// return with nothing
logger.Error("Unable to update current validator set", "Error", err)
return abci.ResponseEndBlock{}
}
// increment proposer priority
currentValidatorSet.IncrementProposerPriority(1)
// save set in store
if err := app.StakingKeeper.UpdateValidatorSetInStore(ctx, currentValidatorSet); err != nil {
// return with nothing
logger.Error("Unable to update current validator set in state", "Error", err)
return abci.ResponseEndBlock{}
}
// convert updates from map to array
for _, v := range setUpdates {
tmValUpdates = append(tmValUpdates, abci.ValidatorUpdate{
Power: int64(v.VotingPower),
PubKey: v.PubKey.ABCIPubKey(),
})
}
}
...
// send validator updates to peppermint
return abci.ResponseEndBlock{
ValidatorUpdates: tmValUpdates,
}
6. 检查点
检查点是 Matic 协议中最关键的部分。它代表 Bor 链状态的快照,应该由 ⅔+ 的验证者集进行证明,然后才能在部署在以太坊上的合约上进行验证和提交。
Heimdall 状态的检查点结构如下所示:
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"`
}
6.1. 根 Hash
RootHashStartBlock是从到的 Bor 块哈希的 Merkle 哈希EndBlock。检查点的根哈希是使用以下方式创建的:
blockHash = keccak256([number, time, tx hash, receipt hash])
1to nBor 块的根哈希的伪代码:
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 链块头创建检查点的一些片段。源码: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
6.2. 账户 Hash
AccountRootHash是需要在每个检查点传递到以太坊链的验证者账户相关信息的哈希值。
eachAccountHash := keccak256([validator id, withdraw fee, slash amount])
1 到 n Bor 块的帐户根哈希的伪代码:
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)]
可以在此处找到帐户哈希的 Golang 代码:https ://github.com/maticnetwork/heimdall/blob/develop/types/dividend-account.go#L91-L101
// DividendAccount contains Fee, Slashed amount
type DividendAccount struct {
ID DividendAccountID `json:"ID"`
FeeAmount string `json:"feeAmount"` // string representation of big.Int
SlashedAmount string `json:"slashedAmount"` // string representation of big.Int
}
// calculate hash for particular account
func (da DividendAccount) CalculateHash() ([]byte, error) {
fee, _ := big.NewInt(0).SetString(da.FeeAmount, 10)
slashAmount, _ := big.NewInt(0).SetString(da.SlashedAmount, 10)
divAccountHash := crypto.Keccak256(appendBytes32(
new(big.Int).SetUint64(uint64(da.ID)).Bytes(),
fee.Bytes(),
slashAmount.Bytes(),
))
return divAccountHash, nil
}
7. 验证者密钥管理
每个验证器使用两个密钥来管理 Polygon 上的验证器相关活动:
7.1. 签名者密钥
签名者密钥是用于对 Heimdall 块、检查点和其他与签名相关的活动进行签名的地址。此密钥的私钥将在验证器节点上用于签名。它无法管理股权、奖励或委托。
验证者必须在此地址上保留两种类型的余额:
- Heimdall 上的 Matic 代币(通过充值交易)在 Heimdall 上执行验证者职责
- 以太坊链上的 ETH 在以太坊上发送检查点
7.2 所有者密钥
所有者密钥是用于在以太坊链上进行质押、重新质押、更改签名者密钥、提取奖励和管理委托相关参数的地址。此密钥的私钥必须不惜一切代价确保安全。
通过此密钥的所有交易都将在以太坊链上执行。
7.3 区别与联系
签名者密钥保存在节点上,通常被认为是hot钱包,而所有者密钥应该非常安全,不经常使用,通常被认为是cold钱包。质押资金由所有者密钥控制。
这种职责分离是为了确保安全性和易用性之间的有效权衡。
两个密钥都是与以太坊兼容的地址,并且工作方式完全相同。
可以有相同的所有者和签名者密钥。
7.4 签名者变更
如果以太坊链上的签名者更改,则会生成以下事件StakingInfo.sol:https ://github.com/maticnetwork/contracts/blob/develop/contracts/staking/StakingInfo.sol
// Signer change
event SignerChange(
uint256 indexed validatorId,
address indexed oldSigner,
address indexed newSigner,
bytes signerPubkey
);
Heimdall 网桥处理这些事件并在 Heimdall 上发送事务以根据事件更改状态。
8. Ante 处理程序
Ante 处理程序检查并验证交易。验证后,它会检查发送方的余额是否有足够的费用,并在成功包含交易的情况下扣除费用。
8.1 gasLimit
每个区块和交易都有一个gas使用限制。一个块可以包含多个事务。但是,一个区块中所有交易使用的 gas 必须小于区块 gas 限制,以避免出现更大的区块。
block.GasLimit >= sum(tx1.GasUsed + tx2.GasUsed + ..... + txN.GasUsed)
请注意,对交易的每个状态操作都会消耗气体,包括交易的签名验证。
8.1.1. Block gas limit
设置应用程序的共识参数时通过了最大块气体限制和每个块的字节数:https ://github.com/maticnetwork/heimdall/blob/develop/app/app.go#L464-L471
maxGasPerBlock int64 = 10000000 // 10 Million
maxBytesPerBlock int64 = 22020096 // 21 MB
// pass consensus params
ConsensusParams: &abci.ConsensusParams{
Block: &abci.BlockParams{
MaxBytes: maxBytesPerBlock,
MaxGas: maxGasPerBlock,
},
...
},
8.1.2 交易 gas limit
交易气体限制在auth模块的参数中定义。它可以通过 Heimdallgov模块进行更改。
8.1.3 检查点 TX 气体限制
由于区块包含多个交易并在以太坊链上验证此特定交易,因此需要 Merkle 证明。为避免对 checkpoint 交易进行额外的 Merkle 证明验证,Heimdall 仅在交易类型为MsgCheckpoint
// fee wanted for checkpoint transaction
gasWantedPerCheckpoinTx sdk.Gas = 10000000 // 10 Million
// checkpoint gas limit
if stdTx.Msg.Type() == "checkpoint" && stdTx.Msg.Route() == "checkpoint" {
gasForTx = gasWantedPerCheckpoinTx
}
1.8.2. 事务验证和重放保护
Ante 处理程序处理和验证传入交易中的签名:https ://github.com/maticnetwork/heimdall/blob/develop/auth/ante.go#L230-L266
每个事务都必须包含sequenceNumber以避免重放攻击。每次成功包含交易后,Ante 处理程序都会增加 TX 发送方帐户的序列号,以避免重复(重放)先前的交易。
原文信息
原文转载自:问我学院,问我社区
原文链接:http://www.wenwoha.com/blog_detail-1272.html