Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

client.gno

7.58 Kb · 225 lines
  1package core
  2
  3import (
  4	"chain"
  5	"chain/runtime/unsafe"
  6
  7	"gno.land/p/aib/ibc/host"
  8	"gno.land/p/aib/ibc/lightclient"
  9	"gno.land/p/aib/ibc/types"
 10	"gno.land/p/nt/ufmt/v0"
 11)
 12
 13// CreateClient generates a new client identifier and invokes the associated
 14// light client module in order to initialize the client.
 15func CreateClient(cur realm, clientState lightclient.ClientState, consensusState lightclient.ConsensusState) string {
 16	if clientState.ClientType() != consensusState.ClientType() {
 17		panic("client type for client state and consensus state do not match")
 18	}
 19	if err := types.ValidateClientType(clientState.ClientType()); err != nil {
 20		panic(ufmt.Sprintf(
 21			"client type does not meet naming constraints: %v", err,
 22		))
 23	}
 24
 25	if err := clientState.ValidateBasic(); err != nil {
 26		panic(err)
 27	}
 28	if err := consensusState.ValidateBasic(); err != nil {
 29		panic(err)
 30	}
 31	relayer := ensureAuthorizedRelayer()
 32	c := store.addClient(clientState.ClientType(), relayer)
 33	err := c.lightClient.Initialize(clientState, consensusState)
 34	if err != nil {
 35		panic(err)
 36	}
 37	if status := c.lightClient.Status(); status != lightclient.Active {
 38		panic(ufmt.Sprintf("cannot create client (%s) with status %s", c.id, status))
 39	}
 40	// Emit create client event
 41	chain.Emit(types.EventTypeCreateClient,
 42		types.AttributeKeyClientID, c.id,
 43		types.AttributeKeyClientType, c.typ,
 44		types.AttributeKeyConsensusHeights, c.lightClient.LatestHeight().String(),
 45	)
 46	return c.id
 47}
 48
 49// ClientIDs returns the list of known client identifiers, sorted lexically by
 50// id (the underlying b+tree order). Intended for read-only callers (UIs,
 51// other realms building txlinks).
 52func ClientIDs() []string {
 53	ids := make([]string, 0, store.clientByID.Size())
 54	store.clientByID.IterateByOffset(0, store.clientByID.Size(), func(k string, _ any) bool {
 55		ids = append(ids, k)
 56		return false
 57	})
 58	return ids
 59}
 60
 61// ClientLatestHeight returns the latest (highest) consensus height the given
 62// client has been updated to, formatted as "<revision>/<height>". This is the
 63// client's latest trusted height. Returns "" if the client is unknown.
 64// Intended for read-only callers (UIs) that want to surface how up-to-date a
 65// client is.
 66func ClientLatestHeight(clientID string) string {
 67	c := store.getClient(clientID)
 68	if c == nil {
 69		return ""
 70	}
 71	return c.lightClient.LatestHeight().String()
 72}
 73
 74// ClientStatus returns the status of the given client (Active, Expired,
 75// Frozen, ...). Returns "" if the client is unknown. Intended for read-only
 76// callers (UIs) that want to surface whether a client is usable.
 77func ClientStatus(clientID string) string {
 78	c := store.getClient(clientID)
 79	if c == nil {
 80		return ""
 81	}
 82	return c.lightClient.Status()
 83}
 84
 85// RegisterCounterparty will register the IBC v2 counterparty info for the
 86// given clientID. It must be called by the same relayer that called
 87// CreateClient.
 88func RegisterCounterparty(cur realm, clientID string, counterpartyMerklePrefix [][]byte, counterpartyClientID string) {
 89	if !types.IsValidClientID(counterpartyClientID) {
 90		panic("invalid counterparty client id")
 91	}
 92	c := store.getClient(clientID)
 93	if c == nil {
 94		panic(ufmt.Sprintf("client %s not found", clientID))
 95	}
 96	caller := unsafe.OriginCaller()
 97	if c.creator != caller {
 98		panic(ufmt.Sprintf("expected same signer as CreateClient submitter %s, got %s", c.creator, caller))
 99	}
100	if c.counterpartyClientID != "" {
101		panic("cannot register counterparty once it is already set")
102	}
103	c.counterpartyClientID = counterpartyClientID
104	c.counterpartyMerklePrefix = counterpartyMerklePrefix
105}
106
107// UpdateClient will update the given IBC v2 light client with a new header.
108// Can also be used to submit a misbehavior (clientMessage can be a header or
109// a misbehavior, maybe split into 2 functions would make more sense here).
110func UpdateClient(cur realm, clientID string, clientMessage lightclient.ClientMessage) {
111	if err := clientMessage.ValidateBasic(); err != nil {
112		panic(err)
113	}
114	c := store.getClient(clientID)
115	if c == nil {
116		panic(ufmt.Sprintf("client %s not found", clientID))
117	}
118	ensureAuthorizedRelayer()
119	if c.typ != clientMessage.ClientType() {
120		panic("client type for client state and client message do not match")
121	}
122	if status := c.lightClient.Status(); status != lightclient.Active {
123		panic(ufmt.Sprintf("cannot update client (%s) with status %s", c.id, status))
124	}
125
126	err := c.lightClient.VerifyClientMessage(clientMessage)
127	if err != nil {
128		panic(err)
129	}
130
131	foundMisbehavior := c.lightClient.CheckForMisbehaviour(clientMessage)
132	if foundMisbehavior {
133		c.lightClient.UpdateStateOnMisbehaviour(clientMessage)
134
135		chain.Emit(types.EventTypeSubmitMisbehaviour,
136			types.AttributeKeyClientID, c.id,
137			types.AttributeKeyClientType, c.typ,
138		)
139		return
140	}
141
142	consensusHeights := c.lightClient.UpdateState(clientMessage)
143
144	// Emit update client event
145	var consensusHeightsStr string
146	for i, h := range consensusHeights {
147		consensusHeightsStr += h.String()
148		if i < len(consensusHeights)-1 {
149			consensusHeightsStr += ", "
150		}
151	}
152	chain.Emit(types.EventTypeUpdateClient,
153		types.AttributeKeyClientID, c.id,
154		types.AttributeKeyClientType, c.typ,
155		types.AttributeKeyConsensusHeights, consensusHeightsStr,
156	)
157}
158
159// UpgradeClient upgrades the client to a new client and consensus state,
160// verified by proofs that the counterparty chain committed to those states
161// at its UpgradePath under the current client's latest consensus root.
162func UpgradeClient(cur realm, clientID string, clientState, consensusState, proofUpgradeClient, proofUpgradeConsensusState any) {
163	if err := host.ClientIdentifierValidator(clientID); err != nil {
164		panic(err)
165	}
166	ensureAuthorizedRelayer()
167	c := store.getClient(clientID)
168	if c == nil {
169		panic(ufmt.Sprintf("client %s not found", clientID))
170	}
171	if status := c.lightClient.Status(); status != lightclient.Active {
172		panic(ufmt.Sprintf("cannot upgrade client (%s) with status %s", c.id, status))
173	}
174	if err := c.lightClient.VerifyUpgradeAndUpdateState(clientState, consensusState, proofUpgradeClient, proofUpgradeConsensusState); err != nil {
175		panic(err)
176	}
177	chain.Emit(types.EventTypeUpgradeClient,
178		types.AttributeKeyClientID, c.id,
179		types.AttributeKeyClientType, c.typ,
180		types.AttributeKeyConsensusHeight, c.lightClient.LatestHeight().String(),
181	)
182}
183
184// RecoverClient recovers a frozen or expired subject client using a healthy
185// substitute client that tracks the same counterparty chain.
186func RecoverClient(cur realm, subjectClientID, substituteClientID string) {
187	ensureAdminCaller()
188	if subjectClientID == substituteClientID {
189		panic("subject and substitute client IDs must differ")
190	}
191	subject := store.getClient(subjectClientID)
192	if subject == nil {
193		panic(ufmt.Sprintf("subject client %s not found", subjectClientID))
194	}
195	substitute := store.getClient(substituteClientID)
196	if substitute == nil {
197		panic(ufmt.Sprintf("substitute client %s not found", substituteClientID))
198	}
199	if subject.typ != substitute.typ {
200		panic(ufmt.Sprintf(
201			"subject client type %s does not match substitute client type %s",
202			subject.typ, substitute.typ,
203		))
204	}
205	if status := subject.lightClient.Status(); status != lightclient.Frozen && status != lightclient.Expired {
206		panic(ufmt.Sprintf(
207			"cannot recover subject client %s with status %s",
208			subject.id, status,
209		))
210	}
211	if status := substitute.lightClient.Status(); status != lightclient.Active {
212		panic(ufmt.Sprintf(
213			"substitute client %s must be Active, got %s",
214			substitute.id, status,
215		))
216	}
217	if err := subject.lightClient.RecoverClient(substitute.lightClient); err != nil {
218		panic(err)
219	}
220	chain.Emit(types.EventTypeRecoverClient,
221		types.AttributeKeySubjectClientID, subject.id,
222		types.AttributeKeySubstituteClientID, substitute.id,
223		types.AttributeKeyClientType, subject.typ,
224	)
225}