packet.gno
8.20 Kb · 261 lines
1package types
2
3import (
4 "bytes"
5 "errors"
6 "strings"
7
8 "gno.land/p/aib/encoding/proto"
9 "gno.land/p/aib/ibc/host"
10 "gno.land/p/nt/ufmt/v0"
11)
12
13// PacketStatus specifies the status of a RecvPacketResult.
14type PacketStatus int32
15
16const (
17 // PACKET_STATUS_UNSPECIFIED indicates an unknown packet status.
18 PacketStatus_NONE PacketStatus = 0
19 // PACKET_STATUS_SUCCESS indicates a successful packet receipt.
20 PacketStatus_Success PacketStatus = 1
21 // PACKET_STATUS_FAILURE indicates a failed packet receipt.
22 PacketStatus_Failure PacketStatus = 2
23 // PACKET_STATUS_ASYNC indicates that the packet was received and its
24 // acknowledgement will be written later by the application.
25 PacketStatus_Async PacketStatus = 3
26)
27
28// Packet defines a type that carries data across different chains through IBC
29type Packet struct {
30 // number corresponds to the order of sends and receives, where a Packet
31 // with an earlier sequence number must be sent and received before a Packet
32 // with a later sequence number.
33 Sequence uint64
34 // identifies the sending client on the sending chain.
35 SourceClient string
36 // identifies the receiving client on the receiving chain.
37 DestinationClient string
38 // timeout timestamp in seconds after which the packet times out.
39 TimeoutTimestamp uint64
40 // a list of payloads, each one for a specific application.
41 Payloads []Payload
42}
43
44// NewPacket constructs a new packet.
45func NewPacket(sequence uint64, sourceClient, destinationClient string, timeoutTimestamp uint64, payloads ...Payload) Packet {
46 return Packet{
47 Sequence: sequence,
48 SourceClient: sourceClient,
49 DestinationClient: destinationClient,
50 TimeoutTimestamp: timeoutTimestamp,
51 Payloads: payloads,
52 }
53}
54
55const MaximumPayloadsSize = 262144 // 256 KiB. This is the maximum size of all payloads combined
56
57// ValidateBasic validates that a Packet satisfies the basic requirements.
58func (p Packet) ValidateBasic() error {
59 // Multi-payload packets are not supported: RecvPacket invokes the app
60 // callbacks in sequence and stops at the first failure without reverting
61 // the ones that already succeeded, so a partially applied packet would be
62 // acknowledged as failed and refunded by the counterparty. ibc-go enforces
63 // the same restriction, so enabling multi-payload has to be a deliberate
64 // change on both sides, with atomicity decided up front.
65 if len(p.Payloads) != 1 {
66 return ufmt.Errorf("payloads must contain exactly one payload")
67 }
68
69 totalPayloadsSize := 0
70 for i, pd := range p.Payloads {
71 if err := pd.ValidateBasic(); err != nil {
72 return ufmt.Errorf("invalid Payload #%d: %v", i, err)
73 }
74 totalPayloadsSize += len(pd.Value)
75 }
76
77 if totalPayloadsSize > MaximumPayloadsSize {
78 return ufmt.Errorf("packet data bytes cannot exceed %d bytes", MaximumPayloadsSize)
79 }
80
81 if err := host.ClientIdentifierValidator(p.SourceClient); err != nil {
82 return ufmt.Errorf("invalid source ID: %v", err)
83 }
84 if err := host.ClientIdentifierValidator(p.DestinationClient); err != nil {
85 return ufmt.Errorf("invalid destination ID: %v", err)
86 }
87
88 if p.Sequence == 0 {
89 return ufmt.Errorf("packet sequence cannot be 0")
90 }
91 if p.TimeoutTimestamp == 0 {
92 return ufmt.Errorf("packet timeout timestamp cannot be 0")
93 }
94
95 return nil
96}
97
98// ProtoMarshal returns the protobuf encoding of a Packet.
99//
100// message Packet {
101// uint64 sequence = 1;
102// string source_client = 2;
103// string destination_client = 3;
104// uint64 timeout_timestamp = 4;
105// repeated Payload payloads = 5 [(gogoproto.nullable) = false];
106// }
107func (p Packet) ProtoMarshal() (buf []byte) {
108 // Field 1: sequence (varint)
109 buf = proto.AppendVarint(buf, 1, uint64(p.Sequence))
110
111 // Field 2: source_client (length-delimited)
112 buf = proto.AppendLengthDelimited(buf, 2, []byte(p.SourceClient))
113
114 // Field 3: destination_client (length-delimited)
115 buf = proto.AppendLengthDelimited(buf, 3, []byte(p.DestinationClient))
116
117 // Field 4: timeout_timestamp (varint)
118 buf = proto.AppendVarint(buf, 4, p.TimeoutTimestamp)
119
120 // Field 5: payloads
121 for _, payload := range p.Payloads {
122 bz := payload.ProtoMarshal()
123 buf = proto.AppendLengthDelimited(buf, 5, bz)
124 }
125 return
126}
127
128type Payload struct {
129 // specifies the source port of the packet.
130 SourcePort string
131 // specifies the destination port of the packet.
132 DestinationPort string
133 // version of the specified application.
134 Version string
135 // the encoding used for the provided value.
136 Encoding string
137 // the raw bytes for the payload.
138 Value []byte
139}
140
141// NewPayload constructs a new Payload
142func NewPayload(sourcePort, destPort, version, encoding string, value []byte) Payload {
143 return Payload{
144 SourcePort: sourcePort,
145 DestinationPort: destPort,
146 Version: version,
147 Encoding: encoding,
148 Value: value,
149 }
150}
151
152// ValidateBasic validates a Payload.
153func (p Payload) ValidateBasic() error {
154 if err := host.PortIdentifierValidator(p.SourcePort); err != nil {
155 return ufmt.Errorf("invalid source port: %v", err)
156 }
157 if err := host.PortIdentifierValidator(p.DestinationPort); err != nil {
158 return ufmt.Errorf("invalid destination port: %v", err)
159 }
160 if strings.TrimSpace(p.Version) == "" {
161 return ufmt.Errorf("payload version cannot be empty")
162 }
163 if strings.TrimSpace(p.Encoding) == "" {
164 return ufmt.Errorf("payload encoding cannot be empty")
165 }
166 if len(p.Value) == 0 {
167 return ufmt.Errorf("payload value cannot be empty")
168 }
169 return nil
170}
171
172// ProtoMarshal returns the protobuf encoding of a Payload.
173//
174// message Payload {
175// string source_port = 1;
176// string destination_port = 2;
177// string version = 3;
178// string encoding = 4;
179// bytes value = 5;
180// }
181func (p Payload) ProtoMarshal() []byte {
182 var buf []byte
183
184 // Field 1: source_port (length-delimited)
185 buf = proto.AppendLengthDelimited(buf, 1, []byte(p.SourcePort))
186
187 // Field 2: destination_port (length-delimited)
188 buf = proto.AppendLengthDelimited(buf, 2, []byte(p.DestinationPort))
189
190 // Field 3: version (length-delimited)
191 buf = proto.AppendLengthDelimited(buf, 3, []byte(p.Version))
192
193 // Field 4: encoding (length-delimited)
194 buf = proto.AppendLengthDelimited(buf, 4, []byte(p.Encoding))
195
196 // Field 5: value (length-delimited)
197 buf = proto.AppendLengthDelimited(buf, 5, p.Value)
198
199 return buf
200}
201
202// RecvPacketResult speecifies the status of a packet as well as the acknowledgement bytes.
203type RecvPacketResult struct {
204 // status of the packet
205 Status PacketStatus
206 // acknowledgement of the packet
207 Acknowledgement []byte
208}
209
210// Acknowledgement contains a list of all ack results associated with a single packet.
211// In the case of a successful receive, the acknowledgement will contain an app acknowledgement
212// for each application that received a payload in the same order that the payloads were sent
213// in the packet.
214// If the receive is not successful, the acknowledgement will contain a single app acknowledgment
215// which will be a constant error acknowledgment as defined by the IBC v2 protocol.
216type Acknowledgement struct {
217 AppAcknowledgements [][]byte
218}
219
220// Validate performs a basic validation of the acknowledgement
221func (ack Acknowledgement) Validate() error {
222 // acknowledgement list should be non-empty
223 if len(ack.AppAcknowledgements) == 0 {
224 return errors.New("app acknowledgements must be non-empty")
225 }
226
227 for _, a := range ack.AppAcknowledgements {
228 // Each app acknowledgement should be non-empty
229 if len(a) == 0 {
230 return errors.New("app acknowledgement cannot be empty")
231 }
232
233 // Ensure that the app acknowledgement contains ErrorAcknowledgement
234 // **if and only if** the app acknowledgement list has a single element
235 if len(ack.AppAcknowledgements) > 1 {
236 if bytes.Equal(a, UniversalErrorAcknowledgement()) {
237 return errors.New("cannot have the error acknowledgement in multi acknowledgement list")
238 }
239 }
240 }
241
242 return nil
243}
244
245// Success returns true if the acknowledgement is successful
246// it implements the exported.Acknowledgement interface
247func (ack Acknowledgement) Success() bool {
248 return !bytes.Equal(ack.AppAcknowledgements[0], UniversalErrorAcknowledgement())
249}
250
251// ProtoMarshal returns the protobuf encoding of a Acknowledgement.
252//
253// message Acknowledgement {
254// repeated bytes app_acknowledgements = 1;
255// }
256func (ack Acknowledgement) ProtoMarshal() (buf []byte) {
257 for _, appAck := range ack.AppAcknowledgements {
258 buf = append(buf, proto.AppendLengthDelimited(nil, 1, appAck)...)
259 }
260 return
261}