Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}