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}