client.gno
6.76 Kb · 201 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// RegisterCounterparty will register the IBC v2 counterparty info for the
62// given clientID. It must be called by the same relayer that called
63// CreateClient.
64func RegisterCounterparty(cur realm, clientID string, counterpartyMerklePrefix [][]byte, counterpartyClientID string) {
65 if !types.IsValidClientID(counterpartyClientID) {
66 panic("invalid counterparty client id")
67 }
68 c := store.getClient(clientID)
69 if c == nil {
70 panic(ufmt.Sprintf("client %s not found", clientID))
71 }
72 caller := unsafe.OriginCaller()
73 if c.creator != caller {
74 panic(ufmt.Sprintf("expected same signer as CreateClient submitter %s, got %s", c.creator, caller))
75 }
76 if c.counterpartyClientID != "" {
77 panic("cannot register counterparty once it is already set")
78 }
79 c.counterpartyClientID = counterpartyClientID
80 c.counterpartyMerklePrefix = counterpartyMerklePrefix
81}
82
83// UpdateClient will update the given IBC v2 light client with a new header.
84// Can also be used to submit a misbehavior (clientMessage can be a header or
85// a misbehavior, maybe split into 2 functions would make more sense here).
86func UpdateClient(cur realm, clientID string, clientMessage lightclient.ClientMessage) {
87 if err := clientMessage.ValidateBasic(); err != nil {
88 panic(err)
89 }
90 c := store.getClient(clientID)
91 if c == nil {
92 panic(ufmt.Sprintf("client %s not found", clientID))
93 }
94 ensureAuthorizedRelayer()
95 if c.typ != clientMessage.ClientType() {
96 panic("client type for client state and client message do not match")
97 }
98 if status := c.lightClient.Status(); status != lightclient.Active {
99 panic(ufmt.Sprintf("cannot update client (%s) with status %s", c.id, status))
100 }
101
102 err := c.lightClient.VerifyClientMessage(clientMessage)
103 if err != nil {
104 panic(err)
105 }
106
107 foundMisbehavior := c.lightClient.CheckForMisbehaviour(clientMessage)
108 if foundMisbehavior {
109 c.lightClient.UpdateStateOnMisbehaviour(clientMessage)
110
111 chain.Emit(types.EventTypeSubmitMisbehaviour,
112 types.AttributeKeyClientID, c.id,
113 types.AttributeKeyClientType, c.typ,
114 )
115 return
116 }
117
118 consensusHeights := c.lightClient.UpdateState(clientMessage)
119
120 // Emit update client event
121 var consensusHeightsStr string
122 for i, h := range consensusHeights {
123 consensusHeightsStr += h.String()
124 if i < len(consensusHeights)-1 {
125 consensusHeightsStr += ", "
126 }
127 }
128 chain.Emit(types.EventTypeUpdateClient,
129 types.AttributeKeyClientID, c.id,
130 types.AttributeKeyClientType, c.typ,
131 types.AttributeKeyConsensusHeights, consensusHeightsStr,
132 )
133}
134
135// UpgradeClient upgrades the client to a new client and consensus state,
136// verified by proofs that the counterparty chain committed to those states
137// at its UpgradePath under the current client's latest consensus root.
138func UpgradeClient(cur realm, clientID string, clientState, consensusState, proofUpgradeClient, proofUpgradeConsensusState any) {
139 if err := host.ClientIdentifierValidator(clientID); err != nil {
140 panic(err)
141 }
142 ensureAuthorizedRelayer()
143 c := store.getClient(clientID)
144 if c == nil {
145 panic(ufmt.Sprintf("client %s not found", clientID))
146 }
147 if status := c.lightClient.Status(); status != lightclient.Active {
148 panic(ufmt.Sprintf("cannot upgrade client (%s) with status %s", c.id, status))
149 }
150 if err := c.lightClient.VerifyUpgradeAndUpdateState(clientState, consensusState, proofUpgradeClient, proofUpgradeConsensusState); err != nil {
151 panic(err)
152 }
153 chain.Emit(types.EventTypeUpgradeClient,
154 types.AttributeKeyClientID, c.id,
155 types.AttributeKeyClientType, c.typ,
156 types.AttributeKeyConsensusHeight, c.lightClient.LatestHeight().String(),
157 )
158}
159
160// RecoverClient recovers a frozen or expired subject client using a healthy
161// substitute client that tracks the same counterparty chain.
162func RecoverClient(cur realm, subjectClientID, substituteClientID string) {
163 ensureAdminCaller()
164 if subjectClientID == substituteClientID {
165 panic("subject and substitute client IDs must differ")
166 }
167 subject := store.getClient(subjectClientID)
168 if subject == nil {
169 panic(ufmt.Sprintf("subject client %s not found", subjectClientID))
170 }
171 substitute := store.getClient(substituteClientID)
172 if substitute == nil {
173 panic(ufmt.Sprintf("substitute client %s not found", substituteClientID))
174 }
175 if subject.typ != substitute.typ {
176 panic(ufmt.Sprintf(
177 "subject client type %s does not match substitute client type %s",
178 subject.typ, substitute.typ,
179 ))
180 }
181 if status := subject.lightClient.Status(); status != lightclient.Frozen && status != lightclient.Expired {
182 panic(ufmt.Sprintf(
183 "cannot recover subject client %s with status %s",
184 subject.id, status,
185 ))
186 }
187 if status := substitute.lightClient.Status(); status != lightclient.Active {
188 panic(ufmt.Sprintf(
189 "substitute client %s must be Active, got %s",
190 substitute.id, status,
191 ))
192 }
193 if err := subject.lightClient.RecoverClient(substitute.lightClient); err != nil {
194 panic(err)
195 }
196 chain.Emit(types.EventTypeRecoverClient,
197 types.AttributeKeySubjectClientID, subject.id,
198 types.AttributeKeySubstituteClientID, substitute.id,
199 types.AttributeKeyClientType, subject.typ,
200 )
201}