client.gno
7.07 Kb · 213 lines
1package core
2
3import (
4 "gno.land/p/onbloc/ibc/union/lightclient"
5 "gno.land/p/onbloc/ibc/union/types"
6 core "gno.land/r/onbloc/ibc/union/core"
7)
8
9// RegisterClient stores a light client implementation for a client type.
10// CreateClient calls it to produce a fresh instance from the submitted state
11// bytes. Callers that pass nil are rejected as unauthorized.
12// Union reference:
13// https://github.com/unionlabs/union/blob/1bb07590230e7c4d071f32ad7185be021a1a1789/cosmwasm/core/src/contract.rs#L849-L865
14func (c *coreV1) RegisterClient(_ int, rlm realm, clientType types.ClientType, clientImpl lightclient.ClientImpl) {
15 assertIsRlmCurrent(0, rlm)
16
17 if clientImpl == nil {
18 panic("caller not authorized to register client: unauthorized")
19 }
20 c.store.RegisterClient(0, rlm, clientType, clientImpl)
21 core.EmitRegisterClient(0, rlm, clientType)
22}
23
24// HasClient reports whether a light client is registered for a client type.
25func (c *coreV1) HasClient(clientType types.ClientType) bool {
26 return c.store.HasRegisteredClient(clientType)
27}
28
29// CreateClient assigns a client id for a registered light client type, stores
30// the submitted state bytes, then lets the light client verify creation against
31// that freshly stored state.
32func (c *coreV1) CreateClient(_ int, rlm realm, msg types.MsgCreateClient) {
33 assertIsRlmCurrent(0, rlm)
34
35 creator := rlm.Previous().Address()
36 relayer := resolveRelayer(creator, msg.Relayer)
37
38 clientImpl, ok := c.store.ClientRegistry(msg.ClientType)
39 if !ok {
40 panic("client type not found")
41 }
42
43 lc, err := clientImpl(msg.ClientStateBytes, msg.ConsensusStateBytes)
44 if err != nil {
45 panic("create client: " + err.Error())
46 }
47
48 clientId := c.store.AddClient(0, rlm, msg.ClientType, creator, lc)
49 c.initClient(0, rlm, clientId, msg.ClientStateBytes, msg.ConsensusStateBytes, relayer)
50
51 core.EmitCreateClient(0, rlm, clientId, msg.ClientType, lc.GetCounterpartyChainID())
52}
53
54// initClient mirrors the submitted client/consensus state for a stored client,
55// runs the light client's creation verification, then persists any state
56// overwrite and storage writes the verification returns. It returns the height
57// the consensus state was stored at.
58func (c *coreV1) initClient(_ int, rlm realm, clientId types.ClientId, clientStateBytes, consensusStateBytes []byte, relayer address) types.Height {
59 lc, ok := c.store.GetLightClient(clientId)
60 if !ok {
61 panic("client " + clientId.String() + " not found")
62 }
63
64 c.store.SaveClientState(0, rlm, clientId, clientStateBytes)
65
66 height := types.NewHeight(lc.GetLatestHeight())
67 c.store.SaveConsensusState(0, rlm, clientId, height, consensusStateBytes)
68
69 // union threads info.sender as the caller; gno derives the authenticated
70 // caller from the realm (internal calls preserve the entry frame).
71 result, err := lc.VerifyCreation(rlm.Previous().Address(), relayer)
72 if err != nil {
73 panic("create client: " + err.Error())
74 }
75
76 if len(result.ClientStateBytes) != 0 {
77 c.store.SaveClientState(0, rlm, clientId, result.ClientStateBytes)
78 }
79
80 for k, v := range result.StorageWrites {
81 c.store.SaveClientStore(0, rlm, clientId, k, v)
82 }
83
84 return height
85}
86
87// UpdateClient verifies a client message against the light client object, which
88// updates its own state, and mirrors the resulting state bytes for queries and
89// counterparty proofs.
90func (c *coreV1) UpdateClient(_ int, rlm realm, msg types.MsgUpdateClient) {
91 assertIsRlmCurrent(0, rlm)
92
93 lc, err := c.getActiveClient(msg.ClientId)
94 if err != nil {
95 panic(err)
96 }
97
98 caller := rlm.Previous().Address()
99 relayer := resolveRelayer(caller, msg.Relayer)
100
101 update, err := lc.VerifyHeader(caller, msg.ClientMessage, relayer)
102 if err != nil {
103 panic("update client: " + err.Error())
104 }
105
106 height := types.NewHeight(update.Height)
107
108 if len(update.ClientStateBytes) != 0 {
109 c.store.SaveClientState(0, rlm, msg.ClientId, update.ClientStateBytes)
110 }
111
112 // Union stores consensus_state_bytes unconditionally because the field is not
113 // optional in the update response.
114 c.store.SaveConsensusState(0, rlm, msg.ClientId, height, update.ConsensusStateBytes)
115
116 for k, v := range update.StorageWrites {
117 c.store.SaveClientStore(0, rlm, msg.ClientId, k, v)
118 }
119
120 core.EmitUpdateClient(0, rlm, msg.ClientId, height)
121}
122
123func (c *coreV1) ForceUpdateClient(_ int, rlm realm, msg types.MsgForceUpdateClient) {
124 assertIsRlmCurrent(0, rlm)
125
126 clientImpl, ok := c.store.GetClientImpl(msg.ClientId)
127 if !ok {
128 panic(makeError(core.ErrClientNotFound, msg.ClientId.String()))
129 }
130
131 next, err := clientImpl(msg.ClientStateBytes, msg.ConsensusStateBytes)
132 if err != nil {
133 panic("force update client: " + err.Error())
134 }
135
136 c.store.SetLightClient(0, rlm, msg.ClientId, next)
137
138 caller := rlm.Previous().Address()
139 height := c.initClient(0, rlm, msg.ClientId, msg.ClientStateBytes, msg.ConsensusStateBytes, caller)
140
141 core.EmitForceUpdateClient(0, rlm, msg.ClientId, height)
142}
143
144// Misbehaviour verifies a misbehaviour message against an active light client and
145// freezes it. Mirroring union (contract.rs L1048-L1093), the host commits the
146// frozen client-state bytes the light client returns back to the client-state
147// mirror / ClientStatePath commitment before emitting the event.
148// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/core/src/contract.rs#L1048-L1093
149func (c *coreV1) Misbehaviour(_ int, rlm realm, msg types.MsgMisbehaviour) {
150 assertIsRlmCurrent(0, rlm)
151
152 lc, err := c.getActiveClient(msg.ClientId)
153 if err != nil {
154 panic(err)
155 }
156
157 caller := rlm.Previous().Address()
158 relayer := resolveRelayer(caller, msg.Relayer)
159
160 clientStateBytes, err := lc.Misbehaviour(caller, msg.ClientMessage, relayer)
161 if err != nil {
162 panic("misbehaviour: " + err.Error())
163 }
164
165 if len(clientStateBytes) != 0 {
166 c.store.SaveClientState(0, rlm, msg.ClientId, clientStateBytes)
167 }
168
169 core.EmitMisbehaviour(0, rlm, msg.ClientId)
170}
171
172// verifyMembership checks a membership proof against a registered, active light
173// client. Shared by the connection/channel handshakes.
174func (c *coreV1) verifyMembership(clientId types.ClientId, height types.Height, proof, key, value []byte) {
175 lc, err := c.getActiveClient(clientId)
176 if err != nil {
177 panic(err)
178 }
179
180 err = lc.VerifyMembership(height.RevisionHeight, key, proof, value)
181 if err != nil {
182 panic(err)
183 }
184}
185
186// getClient returns the light client registered under clientId, or an error
187// when no client is registered for it. The caller (entrypoint) decides whether
188// a miss reverts.
189func (c *coreV1) getClient(clientId types.ClientId) (lightclient.Interface, error) {
190 lc, ok := c.store.GetLightClient(clientId)
191 if !ok {
192 return nil, makeError(core.ErrClientNotFound, clientId.String())
193 }
194
195 return lc, nil
196}
197
198// getActiveClient returns the light client registered under clientId, or an
199// error when it is missing or not in the Active status. It centralizes the
200// "registered and active" precondition shared by the update, misbehaviour, and
201// proof-verification paths.
202func (c *coreV1) getActiveClient(clientId types.ClientId) (lightclient.Interface, error) {
203 lc, err := c.getClient(clientId)
204 if err != nil {
205 return nil, err
206 }
207
208 if lc.Status() != lightclient.Active {
209 return nil, makeError(core.ErrClientNotActive)
210 }
211
212 return lc, nil
213}