Skip to content

Commit

Permalink
Move warp/payload from Subnet-EVM
Browse files Browse the repository at this point in the history
Move warp/payload types to this repo so it can be easily reused from other
subnets.
  • Loading branch information
nytzuga committed Sep 29, 2023
1 parent f79d609 commit f13427e
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 0 deletions.
44 changes: 44 additions & 0 deletions vms/platformvm/warp/payload/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Payload

An Avalanche Unsigned Warp Message already includes a `networkID`, `sourceChainID`, and `payload` field. The `payload` field is parsed into one of the types included in this package to be further handled by the VM.

## AddressedPayload

AddressedPayload:
```
+---------------------+--------+----------------------------------+
| codecID : uint16 | 2 bytes |
+---------------------+--------+----------------------------------+
| typeID : uint32 | 4 bytes |
+---------------------+--------+----------------------------------+
| sourceAddress : []byte | 4 + len(address) |
+---------------------+--------+----------------------------------+
| payload : []byte | 4 + len(payload) |
+---------------------+--------+----------------------------------+
| 14 + len(payload) + len(address) |
+----------------------------------+
```

- `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000`
- `typeID` is the payload type identifier and is `0x00000000` for `AddressedPayload`
- `sourceAddress` is the address that called `sendWarpPrecompile` on the source chain
- `payload` is an arbitrary byte array payload

## BlockHashPayload

BlockHashPayload:
```
+-----------------+----------+-----------+
| codecID : uint16 | 2 bytes |
+-----------------+----------+-----------+
| typeID : uint32 | 4 bytes |
+-----------------+----------+-----------+
| blockHash : [32]byte | 32 bytes |
+-----------------+----------+-----------+
| 38 bytes |
+-----------+
```

- `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000`
- `typeID` is the payload type identifier and is `0x00000001` for `BlockHashPayload`
- `blockHash` is a blockHash from the `sourceChainID`. A signed block hash payload indicates that the signer has accepted the block on the source chain.
56 changes: 56 additions & 0 deletions vms/platformvm/warp/payload/addressed_payload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package payload

import "fmt"

// AddressedPayload defines the format for delivering a point to point message across VMs
// ie. (ChainA, AddressA) -> (ChainB, AddressB)
type AddressedPayload struct {
SourceAddress []byte `serialize:"true"`
Payload []byte `serialize:"true"`

bytes []byte
}

// NewAddressedPayload creates a new *AddressedPayload and initializes it.
func NewAddressedPayload(sourceAddress []byte, payload []byte) (*AddressedPayload, error) {
ap := &AddressedPayload{
SourceAddress: sourceAddress,
Payload: payload,
}
return ap, ap.initialize()
}

// ParseAddressedPayload converts a slice of bytes into an initialized
// AddressedPayload.
func ParseAddressedPayload(b []byte) (*AddressedPayload, error) {
var unmarshalledPayloadIntf any
if _, err := c.Unmarshal(b, &unmarshalledPayloadIntf); err != nil {
return nil, err
}
payload, ok := unmarshalledPayloadIntf.(*AddressedPayload)
if !ok {
return nil, fmt.Errorf("%w: %T", errWrongType, unmarshalledPayloadIntf)
}
payload.bytes = b
return payload, nil
}

// initialize recalculates the result of Bytes().
func (a *AddressedPayload) initialize() error {
payloadIntf := any(a)
bytes, err := c.Marshal(codecVersion, &payloadIntf)
if err != nil {
return fmt.Errorf("couldn't marshal warp addressed payload: %w", err)
}
a.bytes = bytes
return nil
}

// Bytes returns the binary representation of this payload. It assumes that the
// payload is initialized from either NewAddressedPayload or ParseAddressedPayload.
func (a *AddressedPayload) Bytes() []byte {
return a.bytes
}
57 changes: 57 additions & 0 deletions vms/platformvm/warp/payload/block_hash_payload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package payload

import (
"fmt"

"github.com/ava-labs/avalanchego/ids"
)

// BlockHashPayload includes the block hash
type BlockHashPayload struct {
BlockHash ids.ID `serialize:"true"`

bytes []byte
}

// NewBlockHashPayload creates a new *BlockHashPayload and initializes it.
func NewBlockHashPayload(blockHash ids.ID) (*BlockHashPayload, error) {
bhp := &BlockHashPayload{
BlockHash: blockHash,
}
return bhp, bhp.initialize()
}

// ParseBlockHashPayload converts a slice of bytes into an initialized
// BlockHashPayload
func ParseBlockHashPayload(b []byte) (*BlockHashPayload, error) {
var unmarshalledPayloadIntf any
if _, err := c.Unmarshal(b, &unmarshalledPayloadIntf); err != nil {
return nil, err
}
payload, ok := unmarshalledPayloadIntf.(*BlockHashPayload)
if !ok {
return nil, fmt.Errorf("%w: %T", errWrongType, unmarshalledPayloadIntf)
}
payload.bytes = b
return payload, nil
}

// initialize recalculates the result of Bytes().
func (b *BlockHashPayload) initialize() error {
payloadIntf := any(b)
bytes, err := c.Marshal(codecVersion, &payloadIntf)
if err != nil {
return fmt.Errorf("couldn't marshal block hash payload: %w", err)
}
b.bytes = bytes
return nil
}

// Bytes returns the binary representation of this payload. It assumes that the
// payload is initialized from either NewBlockHashPayload or ParseBlockHashPayload.
func (b *BlockHashPayload) Bytes() []byte {
return b.bytes
}
43 changes: 43 additions & 0 deletions vms/platformvm/warp/payload/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package payload

import (
"errors"

"github.com/ava-labs/avalanchego/codec"
"github.com/ava-labs/avalanchego/codec/linearcodec"
"github.com/ava-labs/avalanchego/utils/units"
"github.com/ava-labs/avalanchego/utils/wrappers"
)

var errWrongType = errors.New("wrong payload type")

const (
codecVersion = 0

MaxMessageSize = 24 * units.KiB

// Note: Modifying this variable can have subtle implications on memory
// usage when parsing malformed payloads.
MaxSliceLen = 24 * units.KiB
)

// Codec does serialization and deserialization for Warp messages.
var c codec.Manager

func init() {
c = codec.NewManager(MaxMessageSize)
lc := linearcodec.NewCustomMaxLength(MaxSliceLen)

errs := wrappers.Errs{}
errs.Add(
lc.RegisterType(&AddressedPayload{}),
lc.RegisterType(&BlockHashPayload{}),
c.RegisterCodec(codecVersion, lc),
)
if errs.Errored() {
panic(errs.Err)
}
}
105 changes: 105 additions & 0 deletions vms/platformvm/warp/payload/payload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package payload

import (
"encoding/base64"
"testing"

"github.com/ava-labs/avalanchego/codec"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils"

"github.com/stretchr/testify/require"
)

func TestAddressedPayload(t *testing.T) {
require := require.New(t)
shortID := ids.GenerateTestShortID()

addressedPayload, err := NewAddressedPayload(
shortID[:],
[]byte{1, 2, 3},
)
require.NoError(err)

addressedPayloadBytes := addressedPayload.Bytes()
addressedPayload2, err := ParseAddressedPayload(addressedPayloadBytes)
require.NoError(err)
require.Equal(addressedPayload, addressedPayload2)
}

func TestParseAddressedPayloadJunk(t *testing.T) {
require := require.New(t)
_, err := ParseAddressedPayload(utils.RandomBytes(1024))
require.ErrorIs(err, codec.ErrUnknownVersion)
}

func TestParseAddressedPayload(t *testing.T) {
base64Payload := "AAAAAAAAAAAAEAECAwAAAAAAAAAAAAAAAAAAAAADCgsM"
payload := &AddressedPayload{
SourceAddress: []byte{1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Payload: []byte{10, 11, 12},
}

require.NoError(t, payload.initialize())

require.Equal(t, base64Payload, base64.StdEncoding.EncodeToString(payload.Bytes()))

parsedPayload, err := ParseAddressedPayload(payload.Bytes())
require.NoError(t, err)
require.Equal(t, payload, parsedPayload)
}

func TestBlockHashPayload(t *testing.T) {
require := require.New(t)

blockHashPayload, err := NewBlockHashPayload(ids.GenerateTestID())
require.NoError(err)

blockHashPayloadBytes := blockHashPayload.Bytes()
blockHashPayload2, err := ParseBlockHashPayload(blockHashPayloadBytes)
require.NoError(err)
require.Equal(blockHashPayload, blockHashPayload2)
}

func TestParseBlockHashPayloadJunk(t *testing.T) {
require := require.New(t)
_, err := ParseBlockHashPayload(utils.RandomBytes(1024))
require.ErrorIs(err, codec.ErrUnknownVersion)
}

func TestParseBlockHashPayload(t *testing.T) {
base64Payload := "AAAAAAABBAUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
payload := &BlockHashPayload{
BlockHash: ids.ID{4, 5, 6},
}

require.NoError(t, payload.initialize())

require.Equal(t, base64Payload, base64.StdEncoding.EncodeToString(payload.Bytes()))

parsedPayload, err := ParseBlockHashPayload(payload.Bytes())
require.NoError(t, err)
require.Equal(t, payload, parsedPayload)
}

func TestParseWrongPayloadType(t *testing.T) {
require := require.New(t)
blockHashPayload, err := NewBlockHashPayload(ids.GenerateTestID())
require.NoError(err)

shortID := ids.GenerateTestShortID()
addressedPayload, err := NewAddressedPayload(
shortID[:],
[]byte{1, 2, 3},
)
require.NoError(err)

_, err = ParseAddressedPayload(blockHashPayload.Bytes())
require.ErrorIs(err, errWrongType)

_, err = ParseBlockHashPayload(addressedPayload.Bytes())
require.ErrorIs(err, errWrongType)
}

0 comments on commit f13427e

Please sign in to comment.