forward.gno
5.89 Kb · 157 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 // Forward children build ZkgmPacket directly. Their salt is derived with
46 // DeriveForwardSalt, not DeriveSenderSalt. Route the child through core so
47 // it receives a normal packet commitment, then track its parent so child
48 // resolution can write the deferred parent ack.
49 childPacket = core.SendPacket(cross(rlm), childPacket.SourceChannelId, childPacket.TimeoutTimestamp, childPacket.Data)
50 childHash := types.MustCommit(types.CommitPacket(childPacket))
51 v.store.SetInFlightPacket(0, rlm, childHash.String(), packet)
52 zkgm.EmitForwardInFlightSet(0, rlm, childHash, types.MustCommit(types.CommitPacket(packet)))
53
54 return core.NewRecvPacketResult(types.PacketStatusAsync, nil), nil
55}
56
57// handleForwardChild detects a forwarded child packet resolving on ack/timeout
58// and writes the captured parent's deferred acknowledgement through core.
59func (v *ucs03ZkgmV1) handleForwardChild(_ int, rlm realm, packet types.Packet, zp z.ZkgmPacket, ack []byte) bool {
60 if !z.IsForwardedPacket(zp.Salt) {
61 return false
62 }
63
64 key := forwardInFlightKey(packet)
65
66 parent, ok := v.store.GetInFlightPacket(key)
67 if !ok {
68 zkgm.EmitForwardInFlightPopped(0, rlm, types.MustCommit(types.CommitPacket(packet)), false)
69 return false
70 }
71
72 v.store.RemoveInFlightPacket(0, rlm, key)
73 zkgm.EmitForwardInFlightPopped(0, rlm, types.MustCommit(types.CommitPacket(packet)), true)
74 core.WriteAcknowledgement(cross(rlm), types.NewMsgWriteAcknowledgement(parent, ack))
75
76 return true
77}
78
79// forwardInFlightKey derives the in-flight key from the child packet's commitment hash.
80func forwardInFlightKey(packet types.Packet) string {
81 return types.MustCommit(types.CommitPacket(packet)).String()
82}
83
84// buildForwardChild builds the next-hop packet, advancing the channel path and namespacing the salt.
85func buildForwardChild(packet types.Packet, path *u256.Uint, parentSalt [32]byte, forward z.Forward) (types.Packet, error) {
86 if forward.TimeoutTimestamp == 0 {
87 return core.NewPacket(0, 0, nil, 0), makeError(errForwardZeroTimeout)
88 }
89
90 tailPath, prevDestChannel := z.DequeueChannelFromPath(forward.Path)
91 if prevDestChannel == 0 {
92 return core.NewPacket(0, 0, nil, 0), makeError(errForwardMissingPrevDest)
93 }
94
95 continuationPath, nextSourceChannel := z.DequeueChannelFromPath(tailPath)
96 if nextSourceChannel == 0 {
97 return core.NewPacket(0, 0, nil, 0), makeError(errForwardMissingNextSource)
98 }
99
100 if packet.DestinationChannelId != types.ChannelId(prevDestChannel) {
101 return core.NewPacket(0, 0, nil, 0), makeError(errForwardPrevDestMismatch)
102 }
103
104 nextInstruction := forward.Instruction
105
106 if !continuationPath.IsZero() {
107 nextForward := z.Forward{
108 Path: continuationPath,
109 TimeoutHeight: 0,
110 TimeoutTimestamp: forward.TimeoutTimestamp,
111 Instruction: forward.Instruction,
112 }
113
114 forwardBytes, err := z.EncodeForward(nextForward)
115 if err != nil {
116 return core.NewPacket(0, 0, nil, 0), err
117 }
118
119 nextInstruction = z.Instruction{
120 Version: z.INSTR_VERSION_0,
121 Opcode: z.OP_FORWARD,
122 Operand: forwardBytes,
123 }
124 }
125
126 intermediate, err := z.UpdateChannelPath(path, prevDestChannel)
127 if err != nil {
128 return core.NewPacket(0, 0, nil, 0), err
129 }
130
131 nextPath, err := z.UpdateChannelPath(intermediate, nextSourceChannel)
132 if err != nil {
133 return core.NewPacket(0, 0, nil, 0), err
134 }
135
136 childBytes, err := z.EncodeZkgmPacket(z.ZkgmPacket{
137 Salt: z.DeriveForwardSalt(parentSalt),
138 Path: nextPath,
139 Instruction: nextInstruction,
140 })
141 if err != nil {
142 return core.NewPacket(0, 0, nil, 0), err
143 }
144
145 sourceChannelId := types.ChannelId(nextSourceChannel)
146 sourceChannel, err := core.GetChannel(sourceChannelId)
147 if err != nil {
148 return core.NewPacket(0, 0, nil, 0), err
149 }
150
151 return core.NewPacket(sourceChannelId, sourceChannel.CounterpartyChannelId, childBytes, types.Timestamp(forward.TimeoutTimestamp)), nil
152}
153
154// isAllowedForwardInstruction reports whether opcode may be forwarded.
155func isAllowedForwardInstruction(opcode uint8) bool {
156 return opcode == z.OP_CALL || opcode == z.OP_TOKEN_ORDER || opcode == z.OP_BATCH
157}