Some checks failed
CodeQL / Analyze (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
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
188 lines
9.3 KiB
Go
188 lines
9.3 KiB
Go
package tendermint
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
sdkmath "cosmossdk.io/math"
|
|
storetypes "cosmossdk.io/store/types"
|
|
upgradetypes "cosmossdk.io/x/upgrade/types"
|
|
|
|
"git.cw.tr/mukan-network/mukan-sdk/codec"
|
|
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
|
|
|
|
clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
|
|
commitmenttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/23-commitment/types"
|
|
commitmenttypesv2 "git.cw.tr/mukan-network/mukan-ibc/modules/core/23-commitment/types/v2"
|
|
"git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
|
|
)
|
|
|
|
// VerifyUpgradeAndUpdateState checks if the upgraded client has been committed by the current client
|
|
// It will zero out all client-specific fields and verify all data in client state that must
|
|
// be the same across all valid Tendermint clients for the new chain.
|
|
// Note, if there is a decrease in the UnbondingPeriod, then the TrustingPeriod, despite being a client-specific field
|
|
// is scaled down by the same ratio.
|
|
// VerifyUpgrade will return an error if:
|
|
// - the upgradedClient is not a Tendermint ClientState
|
|
// - the latest height of the client state does not have the same revision number or has a greater
|
|
// height than the committed client.
|
|
// - the height of upgraded client is not greater than that of current client
|
|
// - the latest height of the new client does not match or is greater than the height in committed client
|
|
// - any Tendermint chain specified parameter in upgraded client such as ChainID, UnbondingPeriod,
|
|
// and ProofSpecs do not match parameters set by committed client
|
|
func (cs ClientState) VerifyUpgradeAndUpdateState(
|
|
ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore,
|
|
upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState,
|
|
upgradeClientProof, upgradeConsStateProof []byte,
|
|
) error {
|
|
if len(cs.UpgradePath) == 0 {
|
|
return errorsmod.Wrap(clienttypes.ErrInvalidUpgradeClient, "cannot upgrade client, no upgrade path set")
|
|
}
|
|
|
|
// upgraded client state and consensus state must be IBC tendermint client state and consensus state
|
|
// this may be modified in the future to upgrade to a new IBC tendermint type
|
|
// counterparty must also commit to the upgraded consensus state at a sub-path under the upgrade path specified
|
|
tmUpgradeClient, ok := upgradedClient.(*ClientState)
|
|
if !ok {
|
|
return errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "upgraded client must be Tendermint client. expected: %T got: %T",
|
|
&ClientState{}, upgradedClient)
|
|
}
|
|
|
|
tmUpgradeConsState, ok := upgradedConsState.(*ConsensusState)
|
|
if !ok {
|
|
return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "upgraded consensus state must be Tendermint consensus state. expected %T, got: %T",
|
|
&ConsensusState{}, upgradedConsState)
|
|
}
|
|
|
|
// unmarshal proofs
|
|
var merkleProofClient, merkleProofConsState commitmenttypes.MerkleProof
|
|
if err := cdc.Unmarshal(upgradeClientProof, &merkleProofClient); err != nil {
|
|
return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "could not unmarshal client merkle proof: %v", err)
|
|
}
|
|
if err := cdc.Unmarshal(upgradeConsStateProof, &merkleProofConsState); err != nil {
|
|
return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "could not unmarshal consensus state merkle proof: %v", err)
|
|
}
|
|
|
|
// last height of current counterparty chain must be client's latest height
|
|
lastHeight := cs.LatestHeight
|
|
|
|
// Must prove against latest consensus state to ensure we are verifying against latest upgrade plan
|
|
// This verifies that upgrade is intended for the provided revision, since committed client must exist
|
|
// at this consensus state
|
|
consState, found := GetConsensusState(clientStore, cdc, lastHeight)
|
|
if !found {
|
|
return errorsmod.Wrap(clienttypes.ErrConsensusStateNotFound, "could not retrieve consensus state for lastHeight")
|
|
}
|
|
|
|
// Verify client proof
|
|
bz, err := cdc.MarshalInterface(tmUpgradeClient.ZeroCustomFields())
|
|
if err != nil {
|
|
return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "could not marshal client state: %v", err)
|
|
}
|
|
// construct clientState Merkle path
|
|
upgradeClientPath := constructUpgradeClientMerklePath(cs.UpgradePath, lastHeight)
|
|
if err := merkleProofClient.VerifyMembership(cs.ProofSpecs, consState.GetRoot(), upgradeClientPath, bz); err != nil {
|
|
return errorsmod.Wrapf(err, "client state proof failed. Path: %s", upgradeClientPath.GetKeyPath())
|
|
}
|
|
|
|
// Verify consensus state proof
|
|
bz, err = cdc.MarshalInterface(upgradedConsState)
|
|
if err != nil {
|
|
return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "could not marshal consensus state: %v", err)
|
|
}
|
|
// construct consensus state Merkle path
|
|
upgradeConsStatePath := constructUpgradeConsStateMerklePath(cs.UpgradePath, lastHeight)
|
|
if err := merkleProofConsState.VerifyMembership(cs.ProofSpecs, consState.GetRoot(), upgradeConsStatePath, bz); err != nil {
|
|
return errorsmod.Wrapf(err, "consensus state proof failed. Path: %s", upgradeConsStatePath.GetKeyPath())
|
|
}
|
|
|
|
trustingPeriod := cs.TrustingPeriod
|
|
if tmUpgradeClient.UnbondingPeriod < cs.UnbondingPeriod {
|
|
trustingPeriod = calculateNewTrustingPeriod(trustingPeriod, cs.UnbondingPeriod, tmUpgradeClient.UnbondingPeriod)
|
|
}
|
|
|
|
// Construct new client state and consensus state
|
|
// Relayer chosen client parameters are ignored.
|
|
// All chain-chosen parameters come from committed client, all client-chosen parameters
|
|
// come from current client.
|
|
newClientState := NewClientState(
|
|
tmUpgradeClient.ChainId, cs.TrustLevel, trustingPeriod, tmUpgradeClient.UnbondingPeriod,
|
|
cs.MaxClockDrift, tmUpgradeClient.LatestHeight, tmUpgradeClient.ProofSpecs, tmUpgradeClient.UpgradePath,
|
|
)
|
|
|
|
if err := newClientState.Validate(); err != nil {
|
|
return errorsmod.Wrap(err, "updated client state failed basic validation")
|
|
}
|
|
|
|
// The new consensus state is merely used as a trusted kernel against which headers on the new
|
|
// chain can be verified. The root is just a stand-in sentinel value as it cannot be known in advance, thus no proof verification will pass.
|
|
// The timestamp and the NextValidatorsHash of the consensus state is the blocktime and NextValidatorsHash
|
|
// of the last block committed by the old chain. This will allow the first block of the new chain to be verified against
|
|
// the last validators of the old chain so long as it is submitted within the TrustingPeriod of this client.
|
|
// NOTE: We do not set processed time for this consensus state since this consensus state should not be used for packet verification
|
|
// as the root is empty. The next consensus state submitted using update will be usable for packet-verification.
|
|
newConsState := NewConsensusState(
|
|
tmUpgradeConsState.Timestamp, commitmenttypes.NewMerkleRoot([]byte(SentinelRoot)), tmUpgradeConsState.NextValidatorsHash,
|
|
)
|
|
|
|
setClientState(clientStore, cdc, newClientState)
|
|
setConsensusState(clientStore, cdc, newConsState, newClientState.LatestHeight)
|
|
setConsensusMetadata(ctx, clientStore, tmUpgradeClient.LatestHeight)
|
|
|
|
return nil
|
|
}
|
|
|
|
// construct MerklePath for the committed client from upgradePath
|
|
func constructUpgradeClientMerklePath(upgradePath []string, lastHeight exported.Height) commitmenttypesv2.MerklePath {
|
|
// copy all elements from upgradePath except final element
|
|
clientPath := make([]string, len(upgradePath)-1)
|
|
copy(clientPath, upgradePath)
|
|
|
|
// append lastHeight and `upgradedClient` to last key of upgradePath and use as lastKey of clientPath
|
|
// this will create the IAVL key that is used to store client in upgrade store
|
|
lastKey := upgradePath[len(upgradePath)-1]
|
|
appendedKey := fmt.Sprintf("%s/%d/%s", lastKey, lastHeight.GetRevisionHeight(), upgradetypes.KeyUpgradedClient)
|
|
|
|
clientPath = append(clientPath, appendedKey)
|
|
|
|
var clientKey [][]byte
|
|
for _, part := range clientPath {
|
|
clientKey = append(clientKey, []byte(part))
|
|
}
|
|
|
|
return commitmenttypes.NewMerklePath(clientKey...)
|
|
}
|
|
|
|
// construct MerklePath for the committed consensus state from upgradePath
|
|
func constructUpgradeConsStateMerklePath(upgradePath []string, lastHeight exported.Height) commitmenttypesv2.MerklePath {
|
|
// copy all elements from upgradePath except final element
|
|
consPath := make([]string, len(upgradePath)-1)
|
|
copy(consPath, upgradePath)
|
|
|
|
// append lastHeight and `upgradedClient` to last key of upgradePath and use as lastKey of clientPath
|
|
// this will create the IAVL key that is used to store client in upgrade store
|
|
lastKey := upgradePath[len(upgradePath)-1]
|
|
appendedKey := fmt.Sprintf("%s/%d/%s", lastKey, lastHeight.GetRevisionHeight(), upgradetypes.KeyUpgradedConsState)
|
|
|
|
consPath = append(consPath, appendedKey)
|
|
|
|
var consStateKey [][]byte
|
|
for _, part := range consPath {
|
|
consStateKey = append(consStateKey, []byte(part))
|
|
}
|
|
|
|
return commitmenttypes.NewMerklePath(consStateKey...)
|
|
}
|
|
|
|
// calculateNewTrustingPeriod converts the provided durations to decimal representation to avoid floating-point precision issues
|
|
// and calculates the new trusting period, decreasing it by the ratio between the original and new unbonding period.
|
|
func calculateNewTrustingPeriod(trustingPeriod, originalUnbonding, newUnbonding time.Duration) time.Duration {
|
|
origUnbondingDec := sdkmath.LegacyNewDec(originalUnbonding.Nanoseconds())
|
|
newUnbondingDec := sdkmath.LegacyNewDec(newUnbonding.Nanoseconds())
|
|
trustingPeriodDec := sdkmath.LegacyNewDec(trustingPeriod.Nanoseconds())
|
|
|
|
// compute new trusting period: trustingPeriod * newUnbonding / originalUnbonding
|
|
newTrustingPeriodDec := trustingPeriodDec.Mul(newUnbondingDec).Quo(origUnbondingDec)
|
|
return time.Duration(newTrustingPeriodDec.TruncateInt64())
|
|
}
|