Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}