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.84 Kb · 294 lines
  1package cometbls
  2
  3import (
  4	"time"
  5
  6	aibtypes "gno.land/p/onbloc/deps/ibc/types"
  7	"gno.land/p/onbloc/deps/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
148// Union reference (verify_creation is a no-op there):
149// https://github.com/unionlabs/union/blob/1bb07590230e7c4d071f32ad7185be021a1a1789/cosmwasm/lightclient/cometbls/src/client.rs#L134-L141
150func (c *CometblsLightClient) VerifyCreation(caller address, relayer address) (types.ClientCreationResult, error) {
151	// Unlike Union, we validate basic invariants (ClientState.Validate), but
152	// like Union we skip bounding TrustingPeriod (see Validate). Writes nothing.
153	if err := c.clientState.Validate(); err != nil {
154		return types.ClientCreationResult{}, err
155	}
156
157	return types.ClientCreationResult{}, nil
158}
159
160func (c *CometblsLightClient) Misbehaviour(caller address, misbehaviour []byte, relayer address) ([]byte, error) {
161	m, err := DecodeMisbehaviour(misbehaviour)
162	if err != nil {
163		return nil, errorWithDetails(ErrInvalidMisbehaviour, "failed to decode misbehaviour: "+err.Error())
164	}
165
166	if err := m.ValidateBasic(); err != nil {
167		return nil, err
168	}
169
170	if err := c.verifyMisbehaviour(m, time.Now()); err != nil {
171		return nil, err
172	}
173
174	// Freeze the client. The frozen height is a sentinel boolean (non-zero).
175	c.clientState.FrozenHeight = FrozenHeight
176
177	// Return the frozen client-state bytes so the host commits them back to its
178	// client-state mirror, matching union's misbehaviour handling.
179	csBytes, err := EncodeClientState(c.clientState)
180	if err != nil {
181		return nil, err
182	}
183
184	return csBytes, nil
185}
186
187// updateState creates or updates the consensus state for a verified header and
188// advances the latest height when the header is newer. It mirrors Union's
189// update_state in Union's cometbls client.
190// Union reference:
191// https://github.com/unionlabs/union/blob/1bb07590230e7c4d071f32ad7185be021a1a1789/cosmwasm/lightclient/cometbls/src/client.rs#L283-L309
192func (c *CometblsLightClient) updateState(clientState *ClientState, trustedConsensusState *ConsensusState, header *Header) types.StateUpdate {
193	untrustedHeight := header.GetHeight()
194
195	consensusState := &ConsensusState{
196		Timestamp:          trustedConsensusState.Timestamp,
197		Root:               trustedConsensusState.Root,
198		NextValidatorsHash: trustedConsensusState.NextValidatorsHash,
199	}
200
201	consensusState.Root = MerkleRoot{Hash: header.SignedHeader.AppHash}
202	consensusState.NextValidatorsHash = header.SignedHeader.NextValidatorsHash
203	// Normalized to nanoseconds to follow tendermint convention
204	consensusState.Timestamp = uint64(header.GetTime().UnixNano())
205
206	c.setConsensusState(untrustedHeight, consensusState)
207
208	stateUpdate := makeStateUpdate(untrustedHeight, consensusState)
209	if untrustedHeight.GT(clientState.LatestHeight) {
210		clientState.LatestHeight = untrustedHeight
211		if csBytes, err := EncodeClientState(clientState); err == nil {
212			stateUpdate.ClientStateBytes = csBytes
213		}
214	}
215
216	return stateUpdate
217}
218
219func makeStateUpdate(height types.Height, consensusState *ConsensusState) types.StateUpdate {
220	update := types.StateUpdate{Height: height.RevisionHeight}
221	if consBytes, err := EncodeConsensusState(consensusState); err == nil {
222		update.ConsensusStateBytes = consBytes
223	}
224
225	return update
226}
227
228func (c *CometblsLightClient) GetTimestamp() types.Timestamp {
229	lastConsState, found := c.getConsensusState(c.clientState.LatestHeight)
230	if !found {
231		return 0
232	}
233
234	return types.Timestamp(lastConsState.Timestamp)
235}
236
237func (c *CometblsLightClient) GetTimestampAtHeight(height uint64) (types.Timestamp, error) {
238	consensusState, found := c.getConsensusState(types.NewHeight(height))
239	if !found {
240		return 0, errorWithDetails(ErrInvalidConsensus, "no consensus state at height")
241	}
242
243	return types.Timestamp(consensusState.Timestamp), nil
244}
245
246func (c *CometblsLightClient) GetLatestHeight() uint64 {
247	return c.clientState.GetLatestRevisionHeight()
248}
249
250func (c *CometblsLightClient) GetCounterpartyChainID() string {
251	return c.clientState.ChainID
252}
253
254func (c *CometblsLightClient) Status() lightclient.Status {
255	if !c.clientState.FrozenHeight.IsZero() {
256		return lightclient.Frozen
257	}
258
259	// get latest consensus state to check for expiry
260	lastConsState, found := c.getConsensusState(c.clientState.LatestHeight)
261	if !found {
262		// if the client state does not have an associated consensus state for its
263		// latest height then it must be expired
264		return lightclient.Expired
265	}
266
267	if c.clientState.IsExpired(lastConsState.Timestamp, uint64(time.Now().UnixNano())) {
268		return lightclient.Expired
269	}
270
271	return lightclient.Active
272}
273
274func (c *CometblsLightClient) hasConsensusState(height types.Height) bool {
275	return c.consensusStateByHeight.Has(heightKey(height))
276}
277
278func (c *CometblsLightClient) getConsensusState(height types.Height) (*ConsensusState, bool) {
279	v := c.consensusStateByHeight.Get(heightKey(height))
280	if v == nil {
281		return nil, false
282	}
283
284	consensusState, ok := v.(*ConsensusState)
285	if !ok {
286		return nil, false
287	}
288
289	return consensusState, true
290}
291
292func (c *CometblsLightClient) setConsensusState(height types.Height, consensusState *ConsensusState) {
293	c.consensusStateByHeight.Set(heightKey(height), consensusState)
294}