-
Notifications
You must be signed in to change notification settings - Fork 585
/
misbehaviour_handle.go
174 lines (150 loc) · 7.56 KB
/
misbehaviour_handle.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package tendermint
import (
"bytes"
"context"
"reflect"
"time"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
cmttypes "github.com/cometbft/cometbft/types"
clienttypes "github.com/cosmos/ibc-go/v9/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v9/modules/core/exported"
)
// CheckForMisbehaviour detects duplicate height misbehaviour and BFT time violation misbehaviour
// in a submitted Header message and verifies the correctness of a submitted Misbehaviour ClientMessage
func (ClientState) CheckForMisbehaviour(ctx context.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, msg exported.ClientMessage) bool {
switch msg := msg.(type) {
case *Header:
tmHeader := msg
consState := tmHeader.ConsensusState()
// Check if the Client store already has a consensus state for the header's height
// If the consensus state exists, and it matches the header then we return early
// since header has already been submitted in a previous UpdateClient.
if existingConsState, found := GetConsensusState(clientStore, cdc, tmHeader.GetHeight()); found {
// This header has already been submitted and the necessary state is already stored
// in client store, thus we can return early without further validation.
if reflect.DeepEqual(existingConsState, tmHeader.ConsensusState()) { //nolint:gosimple
return false
}
// A consensus state already exists for this height, but it does not match the provided header.
// The assumption is that Header has already been validated. Thus we can return true as misbehaviour is present
return true
}
// Check that consensus state timestamps are monotonic
prevCons, prevOk := GetPreviousConsensusState(clientStore, cdc, tmHeader.GetHeight())
nextCons, nextOk := GetNextConsensusState(clientStore, cdc, tmHeader.GetHeight())
// if previous consensus state exists, check consensus state time is greater than previous consensus state time
// if previous consensus state is not before current consensus state return true
if prevOk && !prevCons.Timestamp.Before(consState.Timestamp) {
return true
}
// if next consensus state exists, check consensus state time is less than next consensus state time
// if next consensus state is not after current consensus state return true
if nextOk && !nextCons.Timestamp.After(consState.Timestamp) {
return true
}
case *Misbehaviour:
// if heights are equal check that this is valid misbehaviour of a fork
// otherwise if heights are unequal check that this is valid misbehavior of BFT time violation
if msg.Header1.GetHeight().EQ(msg.Header2.GetHeight()) {
blockID1, err := cmttypes.BlockIDFromProto(&msg.Header1.SignedHeader.Commit.BlockID)
if err != nil {
return false
}
blockID2, err := cmttypes.BlockIDFromProto(&msg.Header2.SignedHeader.Commit.BlockID)
if err != nil {
return false
}
// Ensure that Commit Hashes are different
if !bytes.Equal(blockID1.Hash, blockID2.Hash) {
return true
}
} else if !msg.Header1.SignedHeader.Header.Time.After(msg.Header2.SignedHeader.Header.Time) {
// Header1 is at greater height than Header2, therefore Header1 time must be less than or equal to
// Header2 time in order to be valid misbehaviour (violation of monotonic time).
return true
}
}
return false
}
// verifyMisbehaviour determines whether or not two conflicting
// headers at the same height would have convinced the light client.
//
// NOTE: consensusState1 is the trusted consensus state that corresponds to the TrustedHeight
// of misbehaviour.Header1
// Similarly, consensusState2 is the trusted consensus state that corresponds
// to misbehaviour.Header2
// Misbehaviour sets frozen height to {0, 1} since it is only used as a boolean value (zero or non-zero).
func (cs *ClientState) verifyMisbehaviour(ctx context.Context, clientStore storetypes.KVStore, cdc codec.BinaryCodec, misbehaviour *Misbehaviour) error {
// Regardless of the type of misbehaviour, ensure that both headers are valid and would have been accepted by light-client
// Retrieve trusted consensus states for each Header in misbehaviour
tmConsensusState1, found := GetConsensusState(clientStore, cdc, misbehaviour.Header1.TrustedHeight)
if !found {
return errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "could not get trusted consensus state from clientStore for Header1 at TrustedHeight: %s", misbehaviour.Header1.TrustedHeight)
}
tmConsensusState2, found := GetConsensusState(clientStore, cdc, misbehaviour.Header2.TrustedHeight)
if !found {
return errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "could not get trusted consensus state from clientStore for Header2 at TrustedHeight: %s", misbehaviour.Header2.TrustedHeight)
}
// Check the validity of the two conflicting headers against their respective
// trusted consensus states
// NOTE: header height and commitment root assertions are checked in
// misbehaviour.ValidateBasic by the client keeper and msg.ValidateBasic
// by the base application.
sdkCtx := sdk.UnwrapSDKContext(ctx) // TODO: https://github.com/cosmos/ibc-go/issues/5917
if err := checkMisbehaviourHeader(
cs, tmConsensusState1, misbehaviour.Header1, sdkCtx.BlockTime(),
); err != nil {
return errorsmod.Wrap(err, "verifying Header1 in Misbehaviour failed")
}
if err := checkMisbehaviourHeader(
cs, tmConsensusState2, misbehaviour.Header2, sdkCtx.BlockTime(),
); err != nil {
return errorsmod.Wrap(err, "verifying Header2 in Misbehaviour failed")
}
return nil
}
// checkMisbehaviourHeader checks that a Header in Misbehaviour is valid misbehaviour given
// a trusted ConsensusState
func checkMisbehaviourHeader(
clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time,
) error {
tmTrustedValset, err := cmttypes.ValidatorSetFromProto(header.TrustedValidators)
if err != nil {
return errorsmod.Wrap(err, "trusted validator set is not tendermint validator set type")
}
tmCommit, err := cmttypes.CommitFromProto(header.Commit)
if err != nil {
return errorsmod.Wrap(err, "commit is not tendermint commit type")
}
// check the trusted fields for the header against ConsensusState
if err := checkTrustedHeader(header, consState); err != nil {
return err
}
// assert that the age of the trusted consensus state is not older than the trusting period
if currentTimestamp.Sub(consState.Timestamp) >= clientState.TrustingPeriod {
return errorsmod.Wrapf(
ErrTrustingPeriodExpired,
"current timestamp minus the latest consensus state timestamp is greater than or equal to the trusting period (%d >= %d)",
currentTimestamp.Sub(consState.Timestamp), clientState.TrustingPeriod,
)
}
chainID := clientState.GetChainID()
// If chainID is in revision format, then set revision number of chainID with the revision number
// of the misbehaviour header
// NOTE: misbehaviour verification is not supported for chains which upgrade to a new chainID without
// strictly following the chainID revision format
if clienttypes.IsRevisionFormat(chainID) {
chainID, _ = clienttypes.SetRevisionNumber(chainID, header.GetHeight().GetRevisionNumber())
}
// - ValidatorSet must have TrustLevel similarity with trusted FromValidatorSet
// - ValidatorSets on both headers are valid given the last trusted ValidatorSet
if err := tmTrustedValset.VerifyCommitLightTrusting(
chainID, tmCommit, clientState.TrustLevel.ToTendermint(),
); err != nil {
return errorsmod.Wrapf(clienttypes.ErrInvalidMisbehaviour, "validator set in header has too much change from trusted validator set: %v", err)
}
return nil
}