packet.gno
7.77 Kb · 255 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 if len(p.Payloads) == 0 {
60 return ufmt.Errorf("payload length must be greater than 0")
61 }
62
63 totalPayloadsSize := 0
64 for i, pd := range p.Payloads {
65 if err := pd.ValidateBasic(); err != nil {
66 return ufmt.Errorf("invalid Payload #%d: %v", i, err)
67 }
68 totalPayloadsSize += len(pd.Value)
69 }
70
71 if totalPayloadsSize > MaximumPayloadsSize {
72 return ufmt.Errorf("packet data bytes cannot exceed %d bytes", MaximumPayloadsSize)
73 }
74
75 if err := host.ClientIdentifierValidator(p.SourceClient); err != nil {
76 return ufmt.Errorf("invalid source ID: %v", err)
77 }
78 if err := host.ClientIdentifierValidator(p.DestinationClient); err != nil {
79 return ufmt.Errorf("invalid destination ID: %v", err)
80 }
81
82 if p.Sequence == 0 {
83 return ufmt.Errorf("packet sequence cannot be 0")
84 }
85 if p.TimeoutTimestamp == 0 {
86 return ufmt.Errorf("packet timeout timestamp cannot be 0")
87 }
88
89 return nil
90}
91
92// ProtoMarshal returns the protobuf encoding of a Packet.
93//
94// message Packet {
95// uint64 sequence = 1;
96// string source_client = 2;
97// string destination_client = 3;
98// uint64 timeout_timestamp = 4;
99// repeated Payload payloads = 5 [(gogoproto.nullable) = false];
100// }
101func (p Packet) ProtoMarshal() (buf []byte) {
102 // Field 1: sequence (varint)
103 buf = proto.AppendVarint(buf, 1, uint64(p.Sequence))
104
105 // Field 2: source_client (length-delimited)
106 buf = proto.AppendLengthDelimited(buf, 2, []byte(p.SourceClient))
107
108 // Field 3: destination_client (length-delimited)
109 buf = proto.AppendLengthDelimited(buf, 3, []byte(p.DestinationClient))
110
111 // Field 4: timeout_timestamp (varint)
112 buf = proto.AppendVarint(buf, 4, p.TimeoutTimestamp)
113
114 // Field 5: payloads
115 for _, payload := range p.Payloads {
116 bz := payload.ProtoMarshal()
117 buf = proto.AppendLengthDelimited(buf, 5, bz)
118 }
119 return
120}
121
122type Payload struct {
123 // specifies the source port of the packet.
124 SourcePort string
125 // specifies the destination port of the packet.
126 DestinationPort string
127 // version of the specified application.
128 Version string
129 // the encoding used for the provided value.
130 Encoding string
131 // the raw bytes for the payload.
132 Value []byte
133}
134
135// NewPayload constructs a new Payload
136func NewPayload(sourcePort, destPort, version, encoding string, value []byte) Payload {
137 return Payload{
138 SourcePort: sourcePort,
139 DestinationPort: destPort,
140 Version: version,
141 Encoding: encoding,
142 Value: value,
143 }
144}
145
146// ValidateBasic validates a Payload.
147func (p Payload) ValidateBasic() error {
148 if err := host.PortIdentifierValidator(p.SourcePort); err != nil {
149 return ufmt.Errorf("invalid source port: %v", err)
150 }
151 if err := host.PortIdentifierValidator(p.DestinationPort); err != nil {
152 return ufmt.Errorf("invalid destination port: %v", err)
153 }
154 if strings.TrimSpace(p.Version) == "" {
155 return ufmt.Errorf("payload version cannot be empty")
156 }
157 if strings.TrimSpace(p.Encoding) == "" {
158 return ufmt.Errorf("payload encoding cannot be empty")
159 }
160 if len(p.Value) == 0 {
161 return ufmt.Errorf("payload value cannot be empty")
162 }
163 return nil
164}
165
166// ProtoMarshal returns the protobuf encoding of a Payload.
167//
168// message Payload {
169// string source_port = 1;
170// string destination_port = 2;
171// string version = 3;
172// string encoding = 4;
173// bytes value = 5;
174// }
175func (p Payload) ProtoMarshal() []byte {
176 var buf []byte
177
178 // Field 1: source_port (length-delimited)
179 buf = proto.AppendLengthDelimited(buf, 1, []byte(p.SourcePort))
180
181 // Field 2: destination_port (length-delimited)
182 buf = proto.AppendLengthDelimited(buf, 2, []byte(p.DestinationPort))
183
184 // Field 3: version (length-delimited)
185 buf = proto.AppendLengthDelimited(buf, 3, []byte(p.Version))
186
187 // Field 4: encoding (length-delimited)
188 buf = proto.AppendLengthDelimited(buf, 4, []byte(p.Encoding))
189
190 // Field 5: value (length-delimited)
191 buf = proto.AppendLengthDelimited(buf, 5, p.Value)
192
193 return buf
194}
195
196// RecvPacketResult speecifies the status of a packet as well as the acknowledgement bytes.
197type RecvPacketResult struct {
198 // status of the packet
199 Status PacketStatus
200 // acknowledgement of the packet
201 Acknowledgement []byte
202}
203
204// Acknowledgement contains a list of all ack results associated with a single packet.
205// In the case of a successful receive, the acknowledgement will contain an app acknowledgement
206// for each application that received a payload in the same order that the payloads were sent
207// in the packet.
208// If the receive is not successful, the acknowledgement will contain a single app acknowledgment
209// which will be a constant error acknowledgment as defined by the IBC v2 protocol.
210type Acknowledgement struct {
211 AppAcknowledgements [][]byte
212}
213
214// Validate performs a basic validation of the acknowledgement
215func (ack Acknowledgement) Validate() error {
216 // acknowledgement list should be non-empty
217 if len(ack.AppAcknowledgements) == 0 {
218 return errors.New("app acknowledgements must be non-empty")
219 }
220
221 for _, a := range ack.AppAcknowledgements {
222 // Each app acknowledgement should be non-empty
223 if len(a) == 0 {
224 return errors.New("app acknowledgement cannot be empty")
225 }
226
227 // Ensure that the app acknowledgement contains ErrorAcknowledgement
228 // **if and only if** the app acknowledgement list has a single element
229 if len(ack.AppAcknowledgements) > 1 {
230 if bytes.Equal(a, UniversalErrorAcknowledgement()) {
231 return errors.New("cannot have the error acknowledgement in multi acknowledgement list")
232 }
233 }
234 }
235
236 return nil
237}
238
239// Success returns true if the acknowledgement is successful
240// it implements the exported.Acknowledgement interface
241func (ack Acknowledgement) Success() bool {
242 return !bytes.Equal(ack.AppAcknowledgements[0], UniversalErrorAcknowledgement())
243}
244
245// ProtoMarshal returns the protobuf encoding of a Acknowledgement.
246//
247// message Acknowledgement {
248// repeated bytes app_acknowledgements = 1;
249// }
250func (ack Acknowledgement) ProtoMarshal() (buf []byte) {
251 for _, appAck := range ack.AppAcknowledgements {
252 buf = append(buf, proto.AppendLengthDelimited(nil, 1, appAck)...)
253 }
254 return
255}