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

10.52 Kb · 288 lines
  1package cometbls
  2
  3import (
  4	"time"
  5
  6	aibtypes "gno.land/p/aib/ibc/types"
  7	"gno.land/p/aib/ics23"
  8	"gno.land/p/nt/bptree/v0"
  9	"gno.land/p/nt/ufmt/v0"
 10	"gno.land/p/onbloc/ibc/union/lightclient"
 11	"gno.land/p/onbloc/ibc/union/types"
 12)
 13
 14// CometblsLightClient is the stateful object implementing lightclient.Interface.
 15//
 16// It holds the client state and the consensus states keyed by height, mirroring
 17// the host store's per-client entry as a self-contained pure object. The core
 18// host routes by clientId to the stored light-client object; this object owns its
 19// own state and is verified directly against lightclient.Interface.
 20type CometblsLightClient struct {
 21	clientState            *ClientState
 22	consensusStateByHeight *bptree.BPTree // height:*ConsensusState
 23}
 24
 25var _ lightclient.Interface = (*CometblsLightClient)(nil)
 26
 27// NewCometblsLightClient builds the Gno object-form light client from decoded
 28// client and consensus state. Core ClientImpl adapters decode MsgCreateClient
 29// bytes before calling this constructor.
 30func NewCometblsLightClient(clientState *ClientState, consensusState *ConsensusState) (*CometblsLightClient, error) {
 31	client := &CometblsLightClient{
 32		clientState:            clientState,
 33		consensusStateByHeight: bptree.NewBPTree32(),
 34	}
 35	client.setConsensusState(clientState.LatestHeight, consensusState)
 36	return client, nil
 37}
 38
 39// Union reference:
 40// https://github.com/unionlabs/union/blob/1bb07590230e7c4d071f32ad7185be021a1a1789/cosmwasm/lightclient/cometbls/src/client.rs#L48-L72
 41func (c *CometblsLightClient) VerifyMembership(height uint64, key []byte, proof []byte, value []byte) error {
 42	clientState := c.clientState
 43
 44	consensusState, storageProof, merklePath, err := c.prepareProof(clientState, height, key, proof)
 45	if err != nil {
 46		return err
 47	}
 48
 49	return c.verifyChainedMembershipProof(consensusState.GetRoot().Hash, storageProof, merklePath, value, 0)
 50}
 51
 52// Union reference:
 53// https://github.com/unionlabs/union/blob/1bb07590230e7c4d071f32ad7185be021a1a1789/cosmwasm/lightclient/cometbls/src/client.rs#L75-L99
 54func (c *CometblsLightClient) VerifyNonMembership(height uint64, key []byte, proof []byte) error {
 55	clientState := c.clientState
 56
 57	consensusState, storageProof, merklePath, err := c.prepareProof(clientState, height, key, proof)
 58	if err != nil {
 59		return err
 60	}
 61
 62	// VerifyNonMembership verifies the absence of the key in the lowest subtree
 63	// and then chains inclusion proofs of all subroots up to the final root.
 64	nonexist := storageProof[0].GetNonexist()
 65	if nonexist == nil {
 66		return errorWithDetails(ErrInvalidProof, "commitment proof must be non-existence proof for verifying non-membership")
 67	}
 68
 69	subroot, err := nonexist.Calculate()
 70	if err != nil {
 71		return errorWithDetails(ErrInvalidProof, "could not calculate root for proof index 0, merkle tree is likely empty: "+err.Error())
 72	}
 73
 74	key0 := merklePath.KeyPath[len(merklePath.KeyPath)-1]
 75	if err := nonexist.Verify(ics23.GetSDKProofSpecs()[0], subroot, key0); err != nil {
 76		return errorWithDetails(ErrInvalidProof, "failed to verify non-membership proof: "+err.Error())
 77	}
 78
 79	// Verify the chained membership proof starting from index 1 with value =
 80	// subroot.
 81	return c.verifyChainedMembershipProof(consensusState.GetRoot().Hash, storageProof, merklePath, subroot, 1)
 82}
 83
 84// prepareProof performs the validation shared by VerifyMembership and
 85// VerifyNonMembership: the proof height must not exceed the latest height, the
 86// proof must decode, its length must match the SDK proof specs, and a consensus
 87// state must exist at the height. It returns the consensus state, decoded
 88// storage proof and the membership path for the key.
 89func (c *CometblsLightClient) prepareProof(clientState *ClientState, height uint64, key []byte, proof []byte) (*ConsensusState, []ics23.CommitmentProof, aibtypes.MerklePath, error) {
 90	if clientState.GetLatestRevisionHeight() < height {
 91		return nil, nil, aibtypes.MerklePath{}, errorWithDetails(
 92			ErrInvalidHeight,
 93			ufmt.Sprintf("client state height < proof height (%d < %d), please ensure the client has been updated", clientState.GetLatestRevisionHeight(), height),
 94		)
 95	}
 96
 97	storageProof, err := DecodeProofs(proof)
 98	if err != nil {
 99		return nil, nil, aibtypes.MerklePath{}, errorWithDetails(ErrInvalidProof, "failed to decode proof: "+err.Error())
100	}
101
102	specs := ics23.GetSDKProofSpecs()
103	if len(storageProof) != len(specs) {
104		return nil, nil, aibtypes.MerklePath{}, errorWithDetails(
105			ErrInvalidProof,
106			ufmt.Sprintf("length of specs: %d not equal to length of proof: %d", len(specs), len(storageProof)),
107		)
108	}
109
110	consensusState, found := c.getConsensusState(types.NewHeight(height))
111	if !found {
112		return nil, nil, aibtypes.MerklePath{}, errorWithDetails(ErrInvalidConsensus, "please ensure the proof was constructed against a height that exists on the client")
113	}
114
115	merklePath := aibtypes.MerklePath{KeyPath: [][]byte{moduleKey, makeStoreKey(clientState.ContractAddress, key)}}
116
117	return consensusState, storageProof, merklePath, nil
118}
119
120// Union reference:
121// https://github.com/unionlabs/union/blob/1bb07590230e7c4d071f32ad7185be021a1a1789/cosmwasm/lightclient/cometbls/src/client.rs#L143-L160
122func (c *CometblsLightClient) VerifyHeader(caller address, headerBytes []byte, relayer address) (types.StateUpdate, error) {
123	header, err := DecodeHeader(headerBytes)
124	if err != nil {
125		return types.StateUpdate{}, errorWithDetails(ErrInvalidHeader, "failed to decode header: "+err.Error())
126	}
127
128	consensusState, found := c.getConsensusState(*header.TrustedHeight)
129	if !found {
130		return types.StateUpdate{}, errorWithDetails(
131			ErrInvalidConsensus,
132			ufmt.Sprintf("could not get trusted consensus state for Header at TrustedHeight: %s", header.TrustedHeight),
133		)
134	}
135
136	headerHeight := header.GetHeight()
137	if consensusState, found := c.getConsensusState(headerHeight); found {
138		return makeStateUpdate(headerHeight, consensusState), nil
139	}
140
141	if err := c.verifyHeader(c.clientState, consensusState, header, time.Now()); err != nil {
142		return types.StateUpdate{}, err
143	}
144
145	return c.updateState(c.clientState, consensusState, header), nil
146}
147
148func (c *CometblsLightClient) VerifyCreation(caller address, relayer address) (types.ClientCreationResult, error) {
149	// CometBLS performs no creation-time validation and writes nothing, so the
150	// result is empty (mirrors Union's verify_creation).
151	return types.ClientCreationResult{}, nil
152}
153
154func (c *CometblsLightClient) Misbehaviour(caller address, misbehaviour []byte, relayer address) ([]byte, error) {
155	m, err := DecodeMisbehaviour(misbehaviour)
156	if err != nil {
157		return nil, errorWithDetails(ErrInvalidMisbehaviour, "failed to decode misbehaviour: "+err.Error())
158	}
159
160	if err := m.ValidateBasic(); err != nil {
161		return nil, err
162	}
163
164	if err := c.verifyMisbehaviour(m, time.Now()); err != nil {
165		return nil, err
166	}
167
168	// Freeze the client. The frozen height is a sentinel boolean (non-zero).
169	c.clientState.FrozenHeight = FrozenHeight
170
171	// Return the frozen client-state bytes so the host commits them back to its
172	// client-state mirror, matching union's misbehaviour handling.
173	csBytes, err := EncodeClientState(c.clientState)
174	if err != nil {
175		return nil, err
176	}
177
178	return csBytes, nil
179}
180
181// updateState creates or updates the consensus state for a verified header and
182// advances the latest height when the header is newer. It mirrors Union's
183// update_state in Union's cometbls client.
184// Union reference:
185// https://github.com/unionlabs/union/blob/1bb07590230e7c4d071f32ad7185be021a1a1789/cosmwasm/lightclient/cometbls/src/client.rs#L283-L309
186func (c *CometblsLightClient) updateState(clientState *ClientState, trustedConsensusState *ConsensusState, header *Header) types.StateUpdate {
187	untrustedHeight := header.GetHeight()
188
189	consensusState := &ConsensusState{
190		Timestamp:          trustedConsensusState.Timestamp,
191		Root:               trustedConsensusState.Root,
192		NextValidatorsHash: trustedConsensusState.NextValidatorsHash,
193	}
194
195	consensusState.Root = MerkleRoot{Hash: header.SignedHeader.AppHash}
196	consensusState.NextValidatorsHash = header.SignedHeader.NextValidatorsHash
197	// Normalized to nanoseconds to follow tendermint convention
198	consensusState.Timestamp = uint64(header.GetTime().UnixNano())
199
200	c.setConsensusState(untrustedHeight, consensusState)
201
202	stateUpdate := makeStateUpdate(untrustedHeight, consensusState)
203	if untrustedHeight.GT(clientState.LatestHeight) {
204		clientState.LatestHeight = untrustedHeight
205		if csBytes, err := EncodeClientState(clientState); err == nil {
206			stateUpdate.ClientStateBytes = csBytes
207		}
208	}
209
210	return stateUpdate
211}
212
213func makeStateUpdate(height types.Height, consensusState *ConsensusState) types.StateUpdate {
214	update := types.StateUpdate{Height: height.RevisionHeight}
215	if consBytes, err := EncodeConsensusState(consensusState); err == nil {
216		update.ConsensusStateBytes = consBytes
217	}
218
219	return update
220}
221
222func (c *CometblsLightClient) GetTimestamp() types.Timestamp {
223	lastConsState, found := c.getConsensusState(c.clientState.LatestHeight)
224	if !found {
225		return 0
226	}
227
228	return types.Timestamp(lastConsState.Timestamp)
229}
230
231func (c *CometblsLightClient) GetTimestampAtHeight(height uint64) (types.Timestamp, error) {
232	consensusState, found := c.getConsensusState(types.NewHeight(height))
233	if !found {
234		return 0, errorWithDetails(ErrInvalidConsensus, "no consensus state at height")
235	}
236
237	return types.Timestamp(consensusState.Timestamp), nil
238}
239
240func (c *CometblsLightClient) GetLatestHeight() uint64 {
241	return c.clientState.GetLatestRevisionHeight()
242}
243
244func (c *CometblsLightClient) GetCounterpartyChainID() string {
245	return c.clientState.ChainID
246}
247
248func (c *CometblsLightClient) Status() lightclient.Status {
249	if !c.clientState.FrozenHeight.IsZero() {
250		return lightclient.Frozen
251	}
252
253	// get latest consensus state to check for expiry
254	lastConsState, found := c.getConsensusState(c.clientState.LatestHeight)
255	if !found {
256		// if the client state does not have an associated consensus state for its
257		// latest height then it must be expired
258		return lightclient.Expired
259	}
260
261	if c.clientState.IsExpired(lastConsState.Timestamp, uint64(time.Now().UnixNano())) {
262		return lightclient.Expired
263	}
264
265	return lightclient.Active
266}
267
268func (c *CometblsLightClient) hasConsensusState(height types.Height) bool {
269	return c.consensusStateByHeight.Has(heightKey(height))
270}
271
272func (c *CometblsLightClient) getConsensusState(height types.Height) (*ConsensusState, bool) {
273	v := c.consensusStateByHeight.Get(heightKey(height))
274	if v == nil {
275		return nil, false
276	}
277
278	consensusState, ok := v.(*ConsensusState)
279	if !ok {
280		return nil, false
281	}
282
283	return consensusState, true
284}
285
286func (c *CometblsLightClient) setConsensusState(height types.Height, consensusState *ConsensusState) {
287	c.consensusStateByHeight.Set(heightKey(height), consensusState)
288}