-
Notifications
You must be signed in to change notification settings - Fork 573
/
proposal_handle.go
134 lines (112 loc) · 5.4 KB
/
proposal_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
package types
import (
"reflect"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
clienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types"
"github.com/cosmos/ibc-go/modules/core/exported"
)
// CheckSubstituteAndUpdateState will try to update the client with the state of the
// substitute if and only if the proposal passes and one of the following conditions are
// satisfied:
// 1) AllowUpdateAfterMisbehaviour and IsFrozen() = true
// 2) AllowUpdateAfterExpiry=true and Expire(ctx.BlockTime) = true
//
// The following must always be true:
// - The substitute client is the same type as the subject client
// - The subject and substitute client states match in all parameters (expect frozen height, latest height, and chain-id)
//
// In case 1) before updating the client, the client will be unfrozen by resetting
// the FrozenHeight to the zero Height. If a client is frozen and AllowUpdateAfterMisbehaviour
// is set to true, the client will be unexpired even if AllowUpdateAfterExpiry is set to false.
// Note, that even if the subject is updated to the state of the substitute, an error may be
// returned if the updated client state is invalid or the client is expired.
func (cs ClientState) CheckSubstituteAndUpdateState(
ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore,
substituteClientStore sdk.KVStore, substituteClient exported.ClientState,
initialHeight exported.Height,
) (exported.ClientState, error) {
substituteClientState, ok := substituteClient.(*ClientState)
if !ok {
return nil, sdkerrors.Wrapf(
clienttypes.ErrInvalidClient, "expected type %T, got %T", &ClientState{}, substituteClient,
)
}
// substitute clients are not allowed to be upgraded during the voting period
// If an upgrade passes before the subject client has been updated, a new proposal must be created
// with an initial height that contains the new revision number.
if substituteClientState.GetLatestHeight().GetRevisionNumber() != initialHeight.GetRevisionNumber() {
return nil, sdkerrors.Wrapf(
clienttypes.ErrInvalidHeight, "substitute client revision number must equal initial height revision number (%d != %d)",
substituteClientState.GetLatestHeight().GetRevisionNumber(), initialHeight.GetRevisionNumber(),
)
}
if !IsMatchingClientState(cs, *substituteClientState) {
return nil, sdkerrors.Wrap(clienttypes.ErrInvalidSubstitute, "subject client state does not match substitute client state")
}
// get consensus state corresponding to client state to check if the client is expired
consensusState, err := GetConsensusState(subjectClientStore, cdc, cs.GetLatestHeight())
if err != nil {
return nil, sdkerrors.Wrapf(
err, "unexpected error: could not get consensus state from clientstore at height: %d", cs.GetLatestHeight(),
)
}
switch {
case !cs.FrozenHeight.IsZero():
if !cs.AllowUpdateAfterMisbehaviour {
return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unfrozen")
}
// unfreeze the client
cs.FrozenHeight = clienttypes.ZeroHeight()
case cs.IsExpired(consensusState.Timestamp, ctx.BlockTime()):
if !cs.AllowUpdateAfterExpiry {
return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client is not allowed to be unexpired")
}
default:
return nil, sdkerrors.Wrap(clienttypes.ErrUpdateClientFailed, "client cannot be updated with proposal")
}
// copy consensus states and processed time from substitute to subject
// starting from initial height and ending on the latest height (inclusive)
for i := initialHeight.GetRevisionHeight(); i <= substituteClientState.GetLatestHeight().GetRevisionHeight(); i++ {
height := clienttypes.NewHeight(substituteClientState.GetLatestHeight().GetRevisionNumber(), i)
consensusState, err := GetConsensusState(substituteClientStore, cdc, height)
if err != nil {
// not all consensus states will be filled in
continue
}
SetConsensusState(subjectClientStore, cdc, consensusState, height)
processedTime, found := GetProcessedTime(substituteClientStore, height)
if !found {
continue
}
SetProcessedTime(subjectClientStore, height, processedTime)
}
cs.LatestHeight = substituteClientState.LatestHeight
// validate the updated client and ensure it isn't expired
if err := cs.Validate(); err != nil {
return nil, sdkerrors.Wrap(err, "unexpected error: updated subject client state is invalid")
}
latestConsensusState, err := GetConsensusState(subjectClientStore, cdc, cs.GetLatestHeight())
if err != nil {
return nil, sdkerrors.Wrapf(
err, "unexpected error: could not get consensus state for updated subject client from clientstore at height: %d", cs.GetLatestHeight(),
)
}
if cs.IsExpired(latestConsensusState.Timestamp, ctx.BlockTime()) {
return nil, sdkerrors.Wrap(clienttypes.ErrInvalidClient, "updated subject client is expired")
}
return &cs, nil
}
// IsMatchingClientState returns true if all the client state parameters match
// except for frozen height, latest height, and chain-id.
func IsMatchingClientState(subject, substitute ClientState) bool {
// zero out parameters which do not need to match
subject.LatestHeight = clienttypes.ZeroHeight()
subject.FrozenHeight = clienttypes.ZeroHeight()
substitute.LatestHeight = clienttypes.ZeroHeight()
substitute.FrozenHeight = clienttypes.ZeroHeight()
subject.ChainId = ""
substitute.ChainId = ""
return reflect.DeepEqual(subject, substitute)
}