state.gno
9.04 Kb · 278 lines
1package tendermint
2
3import (
4 "bytes"
5 "crypto/sha256"
6 "errors"
7 "strings"
8 "time"
9
10 "gno.land/p/aib/encoding/proto"
11 "gno.land/p/aib/ibc/lightclient"
12 "gno.land/p/aib/ibc/types"
13 "gno.land/p/aib/ics23"
14 "gno.land/p/nt/ufmt/v0"
15)
16
17// ClientState from Tendermint tracks the current validator set, latest height,
18// and a possible frozen height.
19type ClientState struct {
20 ChainID string
21 TrustLevel Fraction
22 // duration of the period since the LatestTimestamp during which the
23 // submitted headers are valid for upgrade
24 TrustingPeriod time.Duration
25 // duration of the staking unbonding period
26 UnbondingPeriod time.Duration
27 // defines how much new (untrusted) header's Time can drift into the future.
28 MaxClockDrift time.Duration
29 // Block height when the client was frozen due to a misbehaviour
30 FrozenHeight types.Height
31 // Latest height the client was updated to
32 LatestHeight types.Height
33 // Proof specifications used in verifying counterparty state
34 ProofSpecs []*ics23.ProofSpec
35
36 // Path at which next upgraded client will be committed.
37 // Each element corresponds to the key for a single CommitmentProof in the
38 // chained proof. NOTE: ClientState must stored under
39 // `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored
40 // under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using
41 // the default upgrade module, upgrade_path should be []string{"upgrade",
42 // "upgradedIBCState"}`
43 UpgradePath []string
44}
45
46func NewClientState(chainID string, trustLevel Fraction,
47 trustingPeriod, ubdPeriod, maxClockDrift time.Duration,
48 latestHeight types.Height, specs []*ics23.ProofSpec, upgradePath []string) *ClientState {
49 return &ClientState{
50 ChainID: chainID,
51 TrustLevel: trustLevel,
52 TrustingPeriod: trustingPeriod,
53 UnbondingPeriod: ubdPeriod,
54 MaxClockDrift: maxClockDrift,
55 LatestHeight: latestHeight,
56 FrozenHeight: types.ZeroHeight(),
57 ProofSpecs: specs,
58 UpgradePath: upgradePath,
59 }
60}
61
62type Fraction struct {
63 Numerator uint64
64 Denominator uint64
65}
66
67func NewFraction(numerator, denominator uint64) Fraction {
68 return Fraction{
69 Numerator: numerator,
70 Denominator: denominator,
71 }
72}
73
74// DefaultTrustLevel is the tendermint light client default trust level
75var DefaultTrustLevel = NewFraction(1, 3)
76
77// ConsensusState defines the consensus state from Tendermint.
78type ConsensusState struct {
79 // timestamp that corresponds to the block height in which the ConsensusState
80 // was stored.
81 Timestamp time.Time
82 // commitment root (i.e app hash)
83 Root MerkleRoot
84 NextValidatorsHash []byte
85
86 processedTime time.Time
87 processedHeight types.Height
88}
89
90func (cs ConsensusState) Equal(other ConsensusState) bool {
91 return cs.Timestamp.Equal(other.Timestamp) &&
92 bytes.Equal(cs.Root.Hash, other.Root.Hash) &&
93 bytes.Equal(cs.NextValidatorsHash, other.NextValidatorsHash)
94}
95
96type MerkleRoot struct {
97 Hash []byte
98}
99
100func NewMerkleRoot(hash []byte) MerkleRoot {
101 return MerkleRoot{Hash: hash}
102}
103
104// Empty returns true if the root is empty
105func (mr MerkleRoot) Empty() bool {
106 return len(mr.Hash) == 0
107}
108
109const (
110 // MaxChainIDLen is a maximum length of the chain ID.
111 MaxChainIDLen = 50
112 // SentinelRoot is used as a stand-in root value for the consensus state set at the upgrade height
113 SentinelRoot = "sentinel_root"
114 // ClientStateTypeURL is the proto Any type URL counterparties use when
115 // storing the upgraded ClientState via cdc.MarshalInterface.
116 ClientStateTypeURL = "/ibc.lightclients.tendermint.v1.ClientState"
117 // ConsensusStateTypeURL is the proto Any type URL counterparties use
118 // when storing the upgraded ConsensusState via cdc.MarshalInterface.
119 ConsensusStateTypeURL = "/ibc.lightclients.tendermint.v1.ConsensusState"
120)
121
122// Implements lightclient.ClientState
123func (ClientState) ClientType() string {
124 return lightclient.Tendermint
125}
126
127// Implements lightclient.ClientState
128func (cs ClientState) ValidateBasic() error {
129 if strings.TrimSpace(cs.ChainID) == "" {
130 return ufmt.Errorf("chainID cannot be empty string")
131 }
132
133 if len(cs.ChainID) > MaxChainIDLen {
134 return ufmt.Errorf("chainID is too long; got: %d, max: %d", len(cs.ChainID), MaxChainIDLen)
135 }
136
137 if err := ValidateTrustLevel(cs.TrustLevel); err != nil {
138 return err
139 }
140 if cs.TrustingPeriod <= 0 {
141 return errors.New("trusting period must be greater than zero")
142 }
143 if cs.UnbondingPeriod <= 0 {
144 return errors.New("unbonding period must be greater than zero")
145 }
146 if cs.MaxClockDrift <= 0 {
147 return errors.New("max clock drift must be greater than zero")
148 }
149
150 // the latest height revision number must match the chain id revision number
151 if cs.LatestHeight.RevisionNumber != types.ParseChainID(cs.ChainID) {
152 return ufmt.Errorf("latest height revision number must match chain id revision number (%d != %d)", cs.LatestHeight.RevisionNumber, types.ParseChainID(cs.ChainID))
153 }
154 if cs.LatestHeight.RevisionHeight == 0 {
155 return ufmt.Errorf("tendermint client's latest height revision height cannot be zero")
156 }
157 if cs.TrustingPeriod >= cs.UnbondingPeriod {
158 return ufmt.Errorf("trusting period (%s) should be < unbonding period (%s)", cs.TrustingPeriod, cs.UnbondingPeriod)
159 }
160
161 if cs.ProofSpecs == nil {
162 return ufmt.Errorf("proof specs cannot be nil for tm client")
163 }
164 for i, spec := range cs.ProofSpecs {
165 if spec == nil {
166 return ufmt.Errorf("proof spec cannot be nil at index: %d", i)
167 }
168 }
169 // UpgradePath may be empty, but if it isn't, each key must be non-empty
170 for i, k := range cs.UpgradePath {
171 if strings.TrimSpace(k) == "" {
172 return ufmt.Errorf("key in upgrade path at index %d cannot be empty", i)
173 }
174 }
175
176 return nil
177}
178
179// ValidateTrustLevel checks that trustLevel is within the allowed range [1/3,
180// 1]. If not, it returns an error. 1/3 is the minimum amount of trust needed
181// which does not break the security model.
182func ValidateTrustLevel(lvl Fraction) error {
183 if lvl.Numerator*3 < lvl.Denominator || // < 1/3
184 lvl.Numerator > lvl.Denominator || // > 1
185 lvl.Denominator == 0 {
186 return ufmt.Errorf("trustLevel must be within [1/3, 1], given %v", lvl)
187 }
188 return nil
189}
190
191// Implements lightclient.ConsensusState
192func (ConsensusState) ClientType() string {
193 return lightclient.Tendermint
194}
195
196// Implements lightclient.ConsensusState
197func (cs ConsensusState) ValidateBasic() error {
198 if cs.Root.Empty() {
199 return errors.New("root cannot be empty")
200 }
201 if err := validateHash(cs.NextValidatorsHash); err != nil {
202 return ufmt.Errorf("next validators hash is invalid: %v", err)
203 }
204 if cs.Timestamp.Unix() <= 0 {
205 return errors.New("timestamp must be a positive Unix time")
206 }
207 return nil
208}
209
210// validateHash returns an error if the hash is not empty, but its
211// size != tmhash.Size.
212func validateHash(h []byte) error {
213 if len(h) > 0 && len(h) != sha256.Size {
214 return ufmt.Errorf("expected size to be %d bytes, got %d bytes",
215 sha256.Size,
216 len(h),
217 )
218 }
219 return nil
220}
221
222func (cs ClientState) ProtoMarshal() (bz []byte) {
223 if cs.ChainID != "" {
224 bz = proto.AppendLengthDelimited(bz, 1, []byte(cs.ChainID))
225 }
226 // Fields 2..7 are marked `(gogoproto.nullable) = false` in ibc-go's
227 // .proto, so they're emitted even when zero (as length-0 messages).
228 bz = proto.AppendAlwaysLengthDelimited(bz, 2, cs.TrustLevel.ProtoMarshal())
229 bz = proto.AppendAlwaysLengthDelimited(bz, 3, proto.DurationMarshal(cs.TrustingPeriod))
230 bz = proto.AppendAlwaysLengthDelimited(bz, 4, proto.DurationMarshal(cs.UnbondingPeriod))
231 bz = proto.AppendAlwaysLengthDelimited(bz, 5, proto.DurationMarshal(cs.MaxClockDrift))
232 bz = proto.AppendAlwaysLengthDelimited(bz, 6, cs.FrozenHeight.ProtoMarshal())
233 bz = proto.AppendAlwaysLengthDelimited(bz, 7, cs.LatestHeight.ProtoMarshal())
234 for _, spec := range cs.ProofSpecs {
235 bz = proto.AppendLengthDelimited(bz, 8, spec.ProtoMarshal())
236 }
237 for _, path := range cs.UpgradePath {
238 bz = proto.AppendLengthDelimited(bz, 9, []byte(path))
239 }
240 return
241}
242
243// ZeroCustomFields returns a copy of the ClientState with all
244// client-customizable fields zeroed. Only the chain-specified fields
245// (ChainID, UnbondingPeriod, LatestHeight, ProofSpecs, UpgradePath) are
246// preserved. The counterparty chain commits to this zeroed form when
247// scheduling an upgrade, so verification on the upgrade proof must be
248// performed against the same shape.
249func (cs ClientState) ZeroCustomFields() ClientState {
250 return ClientState{
251 ChainID: cs.ChainID,
252 UnbondingPeriod: cs.UnbondingPeriod,
253 LatestHeight: cs.LatestHeight,
254 ProofSpecs: cs.ProofSpecs,
255 UpgradePath: cs.UpgradePath,
256 }
257}
258
259func (f Fraction) ProtoMarshal() (bz []byte) {
260 bz = proto.AppendVarint(bz, 1, f.Numerator)
261 bz = proto.AppendVarint(bz, 2, f.Denominator)
262 return
263}
264
265func (cs ConsensusState) ProtoMarshal() (bz []byte) {
266 // Timestamp and Root are `(gogoproto.nullable) = false` in ibc-go;
267 // always-emit semantics. NextValidatorsHash is plain bytes — proto3
268 // default applies (omitted when empty).
269 bz = proto.AppendAlwaysLengthDelimited(bz, 1, proto.TimeMarshal(cs.Timestamp))
270 bz = proto.AppendAlwaysLengthDelimited(bz, 2, cs.Root.ProtoMarshal())
271 bz = proto.AppendLengthDelimited(bz, 3, cs.NextValidatorsHash)
272 return
273}
274
275func (mr MerkleRoot) ProtoMarshal() (bz []byte) {
276 bz = proto.AppendLengthDelimited(bz, 1, mr.Hash)
277 return
278}