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}