Some checks failed
CodeQL / Analyze (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Deploy to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled
Buf-Push / push (push) Has been cancelled
235 lines
9.2 KiB
Go
235 lines
9.2 KiB
Go
package tendermint
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
storetypes "cosmossdk.io/store/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/cometbft/cometbft/light"
|
|
cmttypes "github.com/cometbft/cometbft/types"
|
|
|
|
clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"
|
|
commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types"
|
|
host "github.com/cosmos/ibc-go/v10/modules/core/24-host"
|
|
"github.com/cosmos/ibc-go/v10/modules/core/exported"
|
|
)
|
|
|
|
// VerifyClientMessage checks if the clientMessage is of type Header or Misbehaviour and verifies the message
|
|
func (cs *ClientState) VerifyClientMessage(
|
|
ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore,
|
|
clientMsg exported.ClientMessage,
|
|
) error {
|
|
switch msg := clientMsg.(type) {
|
|
case *Header:
|
|
return cs.verifyHeader(ctx, clientStore, cdc, msg)
|
|
case *Misbehaviour:
|
|
return cs.verifyMisbehaviour(ctx, clientStore, cdc, msg)
|
|
default:
|
|
return clienttypes.ErrInvalidClientType
|
|
}
|
|
}
|
|
|
|
// verifyHeader returns an error if:
|
|
// - the client or header provided are not parseable to tendermint types
|
|
// - the header is invalid
|
|
// - header height is less than or equal to the trusted header height
|
|
// - header revision is not equal to trusted header revision
|
|
// - header valset commit verification fails
|
|
// - header timestamp is past the trusting period in relation to the consensus state
|
|
// - header timestamp is less than or equal to the consensus state timestamp
|
|
func (cs *ClientState) verifyHeader(
|
|
ctx sdk.Context, clientStore storetypes.KVStore, cdc codec.BinaryCodec,
|
|
header *Header,
|
|
) error {
|
|
currentTimestamp := ctx.BlockTime()
|
|
|
|
// Retrieve trusted consensus states for each Header in misbehaviour
|
|
consState, found := GetConsensusState(clientStore, cdc, header.TrustedHeight)
|
|
if !found {
|
|
return errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "could not get trusted consensus state from clientStore for Header at TrustedHeight: %s", header.TrustedHeight)
|
|
}
|
|
|
|
if err := checkTrustedHeader(header, consState); err != nil {
|
|
return err
|
|
}
|
|
|
|
// UpdateClient only accepts updates with a header at the same revision
|
|
// as the trusted consensus state
|
|
if header.GetHeight().GetRevisionNumber() != header.TrustedHeight.RevisionNumber {
|
|
return errorsmod.Wrapf(
|
|
ErrInvalidHeaderHeight,
|
|
"header height revision %d does not match trusted header revision %d",
|
|
header.GetHeight().GetRevisionNumber(), header.TrustedHeight.RevisionNumber,
|
|
)
|
|
}
|
|
|
|
tmTrustedValidators, err := cmttypes.ValidatorSetFromProto(header.TrustedValidators)
|
|
if err != nil {
|
|
return errorsmod.Wrap(err, "trusted validator set in not tendermint validator set type")
|
|
}
|
|
|
|
tmSignedHeader, err := cmttypes.SignedHeaderFromProto(header.SignedHeader)
|
|
if err != nil {
|
|
return errorsmod.Wrap(err, "signed header in not tendermint signed header type")
|
|
}
|
|
|
|
tmValidatorSet, err := cmttypes.ValidatorSetFromProto(header.ValidatorSet)
|
|
if err != nil {
|
|
return errorsmod.Wrap(err, "validator set in not tendermint validator set type")
|
|
}
|
|
|
|
// assert header height is newer than consensus state
|
|
if header.GetHeight().LTE(header.TrustedHeight) {
|
|
return errorsmod.Wrapf(
|
|
clienttypes.ErrInvalidHeader,
|
|
"header height ≤ consensus state height (%s ≤ %s)", header.GetHeight(), header.TrustedHeight,
|
|
)
|
|
}
|
|
|
|
// Construct a trusted header using the fields in consensus state
|
|
// Only Height, Time, and NextValidatorsHash are necessary for verification
|
|
// NOTE: updates must be within the same revision
|
|
trustedHeader := cmttypes.Header{
|
|
ChainID: cs.GetChainID(),
|
|
Height: int64(header.TrustedHeight.RevisionHeight),
|
|
Time: consState.Timestamp,
|
|
NextValidatorsHash: consState.NextValidatorsHash,
|
|
}
|
|
signedHeader := cmttypes.SignedHeader{
|
|
Header: &trustedHeader,
|
|
}
|
|
|
|
// Verify next header with the passed-in trustedVals
|
|
// - asserts trusting period not passed
|
|
// - assert header timestamp is not past the trusting period
|
|
// - assert header timestamp is past latest stored consensus state timestamp
|
|
// - assert that a TrustLevel proportion of TrustedValidators signed new Commit
|
|
err = light.Verify(
|
|
&signedHeader,
|
|
tmTrustedValidators, tmSignedHeader, tmValidatorSet,
|
|
cs.TrustingPeriod, currentTimestamp, cs.MaxClockDrift, cs.TrustLevel.ToTendermint(),
|
|
)
|
|
if err != nil {
|
|
return errorsmod.Wrap(err, "failed to verify header")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateState may be used to either create a consensus state for:
|
|
// - a future height greater than the latest client state height
|
|
// - a past height that was skipped during bisection
|
|
// If we are updating to a past height, a consensus state is created for that height to be persisted in client store
|
|
// If we are updating to a future height, the consensus state is created and the client state is updated to reflect
|
|
// the new latest height
|
|
// A list containing the updated consensus height is returned.
|
|
// UpdateState must only be used to update within a single revision, thus header revision number and trusted height's revision
|
|
// number must be the same. To update to a new revision, use a separate upgrade path
|
|
// UpdateState will prune the oldest consensus state if it is expired.
|
|
// If the provided clientMsg is not of type of Header then the handler will noop and empty slice is returned.
|
|
func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) []exported.Height {
|
|
header, ok := clientMsg.(*Header)
|
|
if !ok {
|
|
// clientMsg is invalid Misbehaviour, no update necessary
|
|
return []exported.Height{}
|
|
}
|
|
|
|
// performance: do not prune in checkTx
|
|
// simulation must prune for accurate gas estimation
|
|
if (!ctx.IsCheckTx() && !ctx.IsReCheckTx()) || ctx.ExecMode() == sdk.ExecModeSimulate {
|
|
cs.pruneOldestConsensusState(ctx, cdc, clientStore)
|
|
}
|
|
|
|
// check for duplicate update
|
|
if _, found := GetConsensusState(clientStore, cdc, header.GetHeight()); found {
|
|
// perform no-op
|
|
return []exported.Height{header.GetHeight()}
|
|
}
|
|
|
|
height, ok := header.GetHeight().(clienttypes.Height)
|
|
if !ok {
|
|
panic(fmt.Errorf("cannot convert %T to %T", header.GetHeight(), &clienttypes.Height{}))
|
|
}
|
|
if height.GT(cs.LatestHeight) {
|
|
cs.LatestHeight = height
|
|
}
|
|
|
|
consensusState := &ConsensusState{
|
|
Timestamp: header.GetTime(),
|
|
Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()),
|
|
NextValidatorsHash: header.Header.NextValidatorsHash,
|
|
}
|
|
|
|
// set client state, consensus state and associated metadata
|
|
setClientState(clientStore, cdc, &cs)
|
|
setConsensusState(clientStore, cdc, consensusState, header.GetHeight())
|
|
setConsensusMetadata(ctx, clientStore, header.GetHeight())
|
|
|
|
return []exported.Height{height}
|
|
}
|
|
|
|
// pruneOldestConsensusState will retrieve the earliest consensus state for this clientID and check if it is expired. If it is,
|
|
// that consensus state will be pruned from store along with all associated metadata. This will prevent the client store from
|
|
// becoming bloated with expired consensus states that can no longer be used for updates and packet verification.
|
|
func (cs ClientState) pruneOldestConsensusState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore) {
|
|
// Check the earliest consensus state to see if it is expired, if so then set the prune height
|
|
// so that we can delete consensus state and all associated metadata.
|
|
var (
|
|
pruneHeight exported.Height
|
|
)
|
|
|
|
pruneCb := func(height exported.Height) bool {
|
|
consState, found := GetConsensusState(clientStore, cdc, height)
|
|
// this error should never occur
|
|
if !found {
|
|
panic(errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "failed to retrieve consensus state at height: %s", height))
|
|
}
|
|
|
|
if cs.IsExpired(consState.Timestamp, ctx.BlockTime()) {
|
|
pruneHeight = height
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
IterateConsensusStateAscending(clientStore, pruneCb)
|
|
|
|
// if pruneHeight is set, delete consensus state and metadata
|
|
if pruneHeight != nil {
|
|
deleteConsensusState(clientStore, pruneHeight)
|
|
deleteConsensusMetadata(clientStore, pruneHeight)
|
|
}
|
|
}
|
|
|
|
// UpdateStateOnMisbehaviour updates state upon misbehaviour, freezing the ClientState. This method should only be called when misbehaviour is detected
|
|
// as it does not perform any misbehaviour checks.
|
|
func (cs ClientState) UpdateStateOnMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, _ exported.ClientMessage) {
|
|
cs.FrozenHeight = FrozenHeight
|
|
|
|
clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs))
|
|
}
|
|
|
|
// checkTrustedHeader checks that consensus state matches trusted fields of Header
|
|
func checkTrustedHeader(header *Header, consState *ConsensusState) error {
|
|
tmTrustedValidators, err := cmttypes.ValidatorSetFromProto(header.TrustedValidators)
|
|
if err != nil {
|
|
return errorsmod.Wrap(err, "trusted validator set in not tendermint validator set type")
|
|
}
|
|
|
|
// assert that trustedVals is NextValidators of last trusted header
|
|
// to do this, we check that trustedVals.Hash() == consState.NextValidatorsHash
|
|
tvalHash := tmTrustedValidators.Hash()
|
|
if !bytes.Equal(consState.NextValidatorsHash, tvalHash) {
|
|
return errorsmod.Wrapf(
|
|
ErrInvalidValidatorSet,
|
|
"trusted validators %s, does not hash to latest trusted validators. Expected: %X, got: %X",
|
|
header.TrustedValidators, consState.NextValidatorsHash, tvalHash,
|
|
)
|
|
}
|
|
return nil
|
|
}
|