Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}