247 lines
8.9 KiB
Go
247 lines
8.9 KiB
Go
// Package v2 is used for tendermint v0.34.22 and its compatible version.
|
|
package v2
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/cometbft/cometbft/crypto/ed25519"
|
|
"github.com/cometbft/cometbft/light"
|
|
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
|
"github.com/cometbft/cometbft/types"
|
|
)
|
|
|
|
const (
|
|
uint64TypeLength uint64 = 8
|
|
consensusStateLengthBytesLength uint64 = 32
|
|
validateResultMetaDataLength uint64 = 32
|
|
|
|
chainIDLength uint64 = 32
|
|
heightLength uint64 = 8
|
|
validatorSetHashLength uint64 = 32
|
|
validatorPubkeyLength uint64 = 32
|
|
validatorVotingPowerLength uint64 = 8
|
|
relayerAddressLength uint64 = 20
|
|
relayerBlsKeyLength uint64 = 48
|
|
singleValidatorBytesLength uint64 = validatorPubkeyLength + validatorVotingPowerLength + relayerAddressLength + relayerBlsKeyLength
|
|
maxConsensusStateLength uint64 = chainIDLength + heightLength + validatorSetHashLength + 99*singleValidatorBytesLength // Maximum validator quantity 99
|
|
)
|
|
|
|
type ConsensusState struct {
|
|
ChainID string
|
|
Height uint64
|
|
NextValidatorSetHash []byte
|
|
ValidatorSet *types.ValidatorSet
|
|
}
|
|
|
|
// output:
|
|
// | chainID | height | nextValidatorSetHash | [{validator pubkey, voting power, relayer address, relayer bls pubkey}] |
|
|
// | 32 bytes | 8 bytes | 32 bytes | [{32 bytes, 8 bytes, 20 bytes, 48 bytes}] |
|
|
func (cs ConsensusState) EncodeConsensusState() ([]byte, error) {
|
|
validatorSetLength := uint64(len(cs.ValidatorSet.Validators))
|
|
serializeLength := chainIDLength + heightLength + validatorSetHashLength + validatorSetLength*singleValidatorBytesLength
|
|
if serializeLength > maxConsensusStateLength {
|
|
return nil, fmt.Errorf("too many validators %d, consensus state bytes should not exceed %d", len(cs.ValidatorSet.Validators), maxConsensusStateLength)
|
|
}
|
|
|
|
encodingBytes := make([]byte, serializeLength)
|
|
|
|
pos := uint64(0)
|
|
if uint64(len(cs.ChainID)) > chainIDLength {
|
|
return nil, errors.New("chainID length should be no more than 32")
|
|
}
|
|
copy(encodingBytes[pos:pos+chainIDLength], cs.ChainID)
|
|
pos += chainIDLength
|
|
|
|
binary.BigEndian.PutUint64(encodingBytes[pos:pos+heightLength], cs.Height)
|
|
pos += heightLength
|
|
|
|
copy(encodingBytes[pos:pos+validatorSetHashLength], cs.NextValidatorSetHash)
|
|
pos += validatorSetHashLength
|
|
|
|
for index := uint64(0); index < validatorSetLength; index++ {
|
|
validator := cs.ValidatorSet.Validators[index]
|
|
pubkey, ok := validator.PubKey.(ed25519.PubKey)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid pubkey type")
|
|
}
|
|
|
|
copy(encodingBytes[pos:pos+validatorPubkeyLength], pubkey[:])
|
|
pos += validatorPubkeyLength
|
|
|
|
binary.BigEndian.PutUint64(encodingBytes[pos:pos+validatorVotingPowerLength], uint64(validator.VotingPower))
|
|
pos += validatorVotingPowerLength
|
|
|
|
copy(encodingBytes[pos:pos+relayerAddressLength], validator.RelayerAddress)
|
|
pos += relayerAddressLength
|
|
|
|
copy(encodingBytes[pos:pos+relayerBlsKeyLength], validator.BlsKey)
|
|
pos += relayerBlsKeyLength
|
|
}
|
|
|
|
return encodingBytes, nil
|
|
}
|
|
|
|
func (cs *ConsensusState) ApplyLightBlock(block *types.LightBlock, isHertz bool) (bool, error) {
|
|
if uint64(block.Height) <= cs.Height {
|
|
return false, fmt.Errorf("block height <= consensus height (%d < %d)", block.Height, cs.Height)
|
|
}
|
|
|
|
if err := block.ValidateBasic(cs.ChainID); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if cs.Height == uint64(block.Height-1) {
|
|
if !bytes.Equal(cs.NextValidatorSetHash, block.ValidatorsHash) {
|
|
return false, fmt.Errorf("validators hash mismatch, expected: %s, real: %s", cs.NextValidatorSetHash, block.ValidatorsHash)
|
|
}
|
|
err := block.ValidatorSet.VerifyCommitLight(cs.ChainID, block.Commit.BlockID, block.Height, block.Commit)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
} else {
|
|
// Ensure that +`trustLevel` (default 1/3) or more of last trusted validators signed correctly.
|
|
err := cs.ValidatorSet.VerifyCommitLightTrusting(cs.ChainID, block.Commit, light.DefaultTrustLevel)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Ensure that +2/3 of new validators signed correctly.
|
|
//
|
|
// NOTE: this should always be the last check because untrustedVals can be
|
|
// intentionally made very large to DOS the light client. not the case for
|
|
// VerifyAdjacent, where validator set is known in advance.
|
|
err = block.ValidatorSet.VerifyCommitLight(cs.ChainID, block.Commit.BlockID, block.Height, block.Commit)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
valSetChanged := !(bytes.Equal(cs.ValidatorSet.Hash(), block.ValidatorsHash))
|
|
|
|
// update consensus state
|
|
cs.Height = uint64(block.Height)
|
|
cs.NextValidatorSetHash = block.NextValidatorsHash
|
|
cs.ValidatorSet = block.ValidatorSet
|
|
|
|
if !isHertz {
|
|
// This logic is wrong, fixed in hertz fork.
|
|
return !(bytes.Equal(cs.ValidatorSet.Hash(), block.ValidatorsHash)), nil
|
|
}
|
|
|
|
return valSetChanged, nil
|
|
}
|
|
|
|
// input:
|
|
// | chainID | height | nextValidatorSetHash | [{validator pubkey, voting power, relayer address, relayer bls pubkey}] |
|
|
// | 32 bytes | 8 bytes | 32 bytes | [{32 bytes, 8 bytes, 20 bytes, 48 bytes}] |
|
|
func DecodeConsensusState(input []byte) (ConsensusState, error) {
|
|
minimumLength := chainIDLength + heightLength + validatorSetHashLength
|
|
inputLen := uint64(len(input))
|
|
if inputLen <= minimumLength || (inputLen-minimumLength)%singleValidatorBytesLength != 0 {
|
|
return ConsensusState{}, fmt.Errorf("expected input size %d+%d*N, actual input size: %d", minimumLength, singleValidatorBytesLength, inputLen)
|
|
}
|
|
|
|
pos := uint64(0)
|
|
chainID := string(bytes.Trim(input[pos:pos+chainIDLength], "\x00"))
|
|
pos += chainIDLength
|
|
|
|
height := binary.BigEndian.Uint64(input[pos : pos+heightLength])
|
|
pos += heightLength
|
|
|
|
nextValidatorSetHash := input[pos : pos+validatorSetHashLength]
|
|
pos += validatorSetHashLength
|
|
|
|
validatorSetLength := (inputLen - minimumLength) / singleValidatorBytesLength
|
|
validatorSetBytes := input[pos:]
|
|
validatorSet := make([]*types.Validator, 0, validatorSetLength)
|
|
for index := uint64(0); index < validatorSetLength; index++ {
|
|
validatorBytes := validatorSetBytes[singleValidatorBytesLength*index : singleValidatorBytesLength*(index+1)]
|
|
|
|
pos = 0
|
|
pubkey := ed25519.PubKey(make([]byte, ed25519.PubKeySize))
|
|
copy(pubkey[:], validatorBytes[:validatorPubkeyLength])
|
|
pos += validatorPubkeyLength
|
|
|
|
votingPower := int64(binary.BigEndian.Uint64(validatorBytes[pos : pos+validatorVotingPowerLength]))
|
|
pos += validatorVotingPowerLength
|
|
|
|
relayerAddress := make([]byte, relayerAddressLength)
|
|
copy(relayerAddress[:], validatorBytes[pos:pos+relayerAddressLength])
|
|
pos += relayerAddressLength
|
|
|
|
relayerBlsKey := make([]byte, relayerBlsKeyLength)
|
|
copy(relayerBlsKey[:], validatorBytes[pos:])
|
|
|
|
validator := types.NewValidator(pubkey, votingPower)
|
|
validator.SetRelayerAddress(relayerAddress)
|
|
validator.SetBlsKey(relayerBlsKey)
|
|
validatorSet = append(validatorSet, validator)
|
|
}
|
|
|
|
consensusState := ConsensusState{
|
|
ChainID: chainID,
|
|
Height: height,
|
|
NextValidatorSetHash: nextValidatorSetHash,
|
|
ValidatorSet: &types.ValidatorSet{
|
|
Validators: validatorSet,
|
|
},
|
|
}
|
|
|
|
return consensusState, nil
|
|
}
|
|
|
|
// input:
|
|
// consensus state length | consensus state | light block |
|
|
// 32 bytes | | |
|
|
func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.LightBlock, error) {
|
|
if uint64(len(input)) <= consensusStateLengthBytesLength {
|
|
return nil, nil, errors.New("invalid input")
|
|
}
|
|
|
|
csLen := binary.BigEndian.Uint64(input[consensusStateLengthBytesLength-uint64TypeLength : consensusStateLengthBytesLength])
|
|
|
|
if consensusStateLengthBytesLength+csLen < consensusStateLengthBytesLength {
|
|
return nil, nil, fmt.Errorf("integer overflow, csLen: %d", csLen)
|
|
}
|
|
|
|
if uint64(len(input)) <= consensusStateLengthBytesLength+csLen {
|
|
return nil, nil, fmt.Errorf("expected payload size %d, actual size: %d", consensusStateLengthBytesLength+csLen, len(input))
|
|
}
|
|
|
|
cs, err := DecodeConsensusState(input[consensusStateLengthBytesLength : consensusStateLengthBytesLength+csLen])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var lbpb tmproto.LightBlock
|
|
err = lbpb.Unmarshal(input[consensusStateLengthBytesLength+csLen:])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
block, err := types.LightBlockFromProto(&lbpb)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return &cs, block, nil
|
|
}
|
|
|
|
// output:
|
|
// | validatorSetChanged | empty | consensusStateBytesLength | new consensusState |
|
|
// | 1 byte | 23 bytes | 8 bytes | |
|
|
func EncodeLightBlockValidationResult(validatorSetChanged bool, consensusStateBytes []byte) []byte {
|
|
lengthBytes := make([]byte, validateResultMetaDataLength)
|
|
if validatorSetChanged {
|
|
copy(lengthBytes[:1], []byte{0x01})
|
|
}
|
|
|
|
consensusStateBytesLength := uint64(len(consensusStateBytes))
|
|
binary.BigEndian.PutUint64(lengthBytes[validateResultMetaDataLength-uint64TypeLength:], consensusStateBytesLength)
|
|
|
|
result := append(lengthBytes, consensusStateBytes...)
|
|
return result
|
|
}
|