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

forward.gno

7.28 Kb · 208 lines
  1package ucs03_zkgm
  2
  3import (
  4	types "gno.land/p/onbloc/ibc/union/types"
  5
  6	z "gno.land/p/onbloc/ibc/union/zkgm"
  7	u256 "gno.land/p/onbloc/math/uint256"
  8	zkgm "gno.land/r/onbloc/ibc/union/apps/ucs03_zkgm"
  9	core "gno.land/r/onbloc/ibc/union/core"
 10)
 11
 12// verifyForward checks the forwarded instruction is allowed and the timeout is set, then verifies the inner instruction.
 13// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/app/ucs03-zkgm/src/contract.rs#L2969-L2995
 14func (v *ucs03ZkgmV1) verifyForward(_ int, rlm realm, funds *funds, channelId types.ChannelId, forward z.Forward) error {
 15	if !isAllowedForwardInstruction(forward.Instruction.Opcode) {
 16		return makeError(errInvalidForwardInstruction)
 17	}
 18
 19	if forward.TimeoutTimestamp == 0 {
 20		return makeError(errForwardZeroTimeout)
 21	}
 22
 23	return v.verifyInternal(0, rlm, funds, channelId, forward.Path, forward.Instruction)
 24}
 25
 26// executeForward builds and dispatches the next-hop packet, recording the parent for async ack settlement.
 27// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/app/ucs03-zkgm/src/contract.rs#L1392-L1497
 28func (v *ucs03ZkgmV1) executeForward(_ int, rlm realm, packet types.Packet, salt [32]byte, path *u256.Uint, forward z.Forward, intent bool) (types.RecvPacketResult, error) {
 29	// Recv bypasses verifyInternal (see imp.gno OnRecvPacket flow),
 30	// receive-side opcode check is the only enforcement point.
 31	// Do not remove it as redundant with verifyForward.
 32	if !isAllowedForwardInstruction(forward.Instruction.Opcode) {
 33		return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), makeError(errInvalidForwardInstruction)
 34	}
 35
 36	if intent {
 37		return core.NewRecvPacketResult(types.PacketStatusSuccess, types.CloneBytes(z.ACK_ERR_ONLY_MAKER)), nil
 38	}
 39
 40	childPacket, err := buildForwardChild(packet, path, salt, forward)
 41	if err != nil {
 42		return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
 43	}
 44
 45	// Pre-check what SendPacket would panic on, so a bad path degrades to a
 46	// failure ack instead of aborting the whole recv transaction.
 47	if err := verifyForwardChannel(rlm.PkgPath(), childPacket); err != nil {
 48		return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
 49	}
 50
 51	// Forward children build ZkgmPacket directly. Their salt is derived with
 52	// DeriveForwardSalt, not DeriveSenderSalt. Route the child through core so
 53	// it receives a normal packet commitment, then track its parent so child
 54	// resolution can write the deferred parent ack.
 55	childPacket = core.SendPacket(cross(rlm), childPacket.SourceChannelId, childPacket.TimeoutTimestamp, childPacket.Data)
 56	childHash := types.MustCommit(types.CommitPacket(childPacket))
 57	v.store.SetInFlightPacket(0, rlm, childHash.String(), packet)
 58	zkgm.EmitForwardInFlightSet(0, rlm, childHash, types.MustCommit(types.CommitPacket(packet)))
 59
 60	return core.NewRecvPacketResult(types.PacketStatusAsync, nil), nil
 61}
 62
 63// handleForwardChild detects a forwarded child packet resolving on ack/timeout
 64// and writes the captured parent's deferred acknowledgement through core.
 65func (v *ucs03ZkgmV1) handleForwardChild(_ int, rlm realm, packet types.Packet, zp z.ZkgmPacket, ack []byte) bool {
 66	if !z.IsForwardedPacket(zp.Salt) {
 67		return false
 68	}
 69
 70	key := forwardInFlightKey(packet)
 71
 72	parent, ok := v.store.GetInFlightPacket(key)
 73	if !ok {
 74		zkgm.EmitForwardInFlightPopped(0, rlm, types.MustCommit(types.CommitPacket(packet)), false)
 75		return false
 76	}
 77
 78	v.store.RemoveInFlightPacket(0, rlm, key)
 79	zkgm.EmitForwardInFlightPopped(0, rlm, types.MustCommit(types.CommitPacket(packet)), true)
 80	core.WriteAcknowledgement(cross(rlm), types.NewMsgWriteAcknowledgement(parent, ack))
 81
 82	return true
 83}
 84
 85// forwardInFlightKey derives the in-flight key from the child packet's commitment hash.
 86func forwardInFlightKey(packet types.Packet) string {
 87	return types.MustCommit(types.CommitPacket(packet)).String()
 88}
 89
 90// buildForwardChild builds the next-hop packet, advancing the channel path and namespacing the salt.
 91func buildForwardChild(packet types.Packet, path *u256.Uint, parentSalt [32]byte, forward z.Forward) (types.Packet, error) {
 92	if forward.TimeoutTimestamp == 0 {
 93		return core.NewPacket(0, 0, nil, 0), makeError(errForwardZeroTimeout)
 94	}
 95
 96	tailPath, prevDestChannel := z.DequeueChannelFromPath(forward.Path)
 97	if prevDestChannel == 0 {
 98		return core.NewPacket(0, 0, nil, 0), makeError(errForwardMissingPrevDest)
 99	}
100
101	continuationPath, nextSourceChannel := z.DequeueChannelFromPath(tailPath)
102	if nextSourceChannel == 0 {
103		return core.NewPacket(0, 0, nil, 0), makeError(errForwardMissingNextSource)
104	}
105
106	if packet.DestinationChannelId != types.ChannelId(prevDestChannel) {
107		return core.NewPacket(0, 0, nil, 0), makeError(errForwardPrevDestMismatch)
108	}
109
110	nextInstruction := forward.Instruction
111
112	if !continuationPath.IsZero() {
113		nextForward := z.Forward{
114			Path:             continuationPath,
115			TimeoutHeight:    0,
116			TimeoutTimestamp: forward.TimeoutTimestamp,
117			Instruction:      forward.Instruction,
118		}
119
120		forwardBytes, err := z.EncodeForward(nextForward)
121		if err != nil {
122			return core.NewPacket(0, 0, nil, 0), err
123		}
124
125		nextInstruction = z.Instruction{
126			Version: z.INSTR_VERSION_0,
127			Opcode:  z.OP_FORWARD,
128			Operand: forwardBytes,
129		}
130	}
131
132	intermediate, err := z.UpdateChannelPath(path, prevDestChannel)
133	if err != nil {
134		return core.NewPacket(0, 0, nil, 0), err
135	}
136
137	nextPath, err := z.UpdateChannelPath(intermediate, nextSourceChannel)
138	if err != nil {
139		return core.NewPacket(0, 0, nil, 0), err
140	}
141
142	childBytes, err := z.EncodeZkgmPacket(z.ZkgmPacket{
143		Salt:        z.DeriveForwardSalt(parentSalt),
144		Path:        nextPath,
145		Instruction: nextInstruction,
146	})
147	if err != nil {
148		return core.NewPacket(0, 0, nil, 0), err
149	}
150
151	sourceChannelId := types.ChannelId(nextSourceChannel)
152	sourceChannel, err := core.GetChannel(sourceChannelId)
153	if err != nil {
154		return core.NewPacket(0, 0, nil, 0), err
155	}
156
157	return core.NewPacket(sourceChannelId, sourceChannel.CounterpartyChannelId, childBytes, types.Timestamp(forward.TimeoutTimestamp)), nil
158}
159
160// verifyForwardChannel pre-checks every condition core.SendPacket would
161// otherwise panic on for childPacket.
162func verifyForwardChannel(ownPortId string, childPacket types.Packet) error {
163	sourceChannelId := childPacket.SourceChannelId
164
165	if !core.IsValidPacketDataSize(childPacket.Data) {
166		return makeError(errForwardChildPacketTooLarge)
167	}
168
169	owner, err := core.GetChannelOwner(sourceChannelId)
170	if err != nil {
171		return err
172	}
173
174	if owner != ownPortId {
175		return makeError(errForwardChannelNotOwned)
176	}
177
178	channel, err := core.GetChannel(sourceChannelId)
179	if err != nil {
180		return err
181	}
182
183	if channel.State != types.ChannelStateOpen {
184		return makeError(errForwardChannelNotOpen)
185	}
186
187	connection, err := core.GetConnection(channel.ConnectionId)
188	if err != nil {
189		return err
190	}
191
192	if connection.State != types.ConnectionStateOpen {
193		return makeError(errForwardConnectionNotOpen)
194	}
195
196	// Two different parents can derive the same child; SendPacket would
197	// otherwise panic with ErrPacketCommitmentAlreadyExists.
198	if core.GetPacketCommitmentExists(childPacket) {
199		return makeError(errForwardChildAlreadyCommitted)
200	}
201
202	return nil
203}
204
205// isAllowedForwardInstruction reports whether opcode may be forwarded.
206func isAllowedForwardInstruction(opcode uint8) bool {
207	return opcode == z.OP_CALL || opcode == z.OP_TOKEN_ORDER || opcode == z.OP_BATCH
208}