synapsecns/sanguine

View on GitHub
agents/types/message.go

Summary

Maintainability
A
0 mins
Test Coverage
package types

import (
    "bytes"
    "encoding/gob"
    "fmt"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
)

const (
    // MessageBodyOffset is the message body offset.
    MessageBodyOffset = MessageHeaderSize
)

// Message is an interface that contains the message.
//
//nolint:interfacebloat
type Message interface {
    // Header gets the message header
    Header() Header
    // BaseMessage is the base message if the flag indicates the type is a base message
    BaseMessage() BaseMessage
    // Body gets the message body
    Body() []byte

    // OriginDomain returns the Slip-44 ID
    OriginDomain() uint32
    // Nonce is the count of all previous messages to the destination
    Nonce() uint32
    // DestinationDomain is the slip-44 id of the destination
    DestinationDomain() uint32
    // ToLeaf converts a leaf to a keccac256
    ToLeaf() (leaf [32]byte, err error)
    // OptimisticSeconds gets the optimistic seconds count
    OptimisticSeconds() uint32
}

// messageImpl implements a message. It is used for testutils. Real messages are emitted by the contract.
type messageImpl struct {
    header      Header
    baseMessage BaseMessage
    body        []byte
}

const headerOffset uint16 = 0

// NewMessageFromBaseMessage creates a generic message given a base message.
func NewMessageFromBaseMessage(header Header, baseMessage BaseMessage) (Message, error) {
    if header.Flag() != MessageFlagBase {
        return nil, fmt.Errorf("header flag is not base")
    }

    baseMessageBytes, err := EncodeBaseMessage(baseMessage)
    if err != nil {
        return nil, fmt.Errorf("could not encode base message: %w", err)
    }

    return &messageImpl{
        header:      header,
        baseMessage: baseMessage,
        body:        baseMessageBytes,
    }, nil
}

// NewMessageFromManagerMessage creates a generic message given a manager message.
func NewMessageFromManagerMessage(header Header, payload []byte) (Message, error) {
    if header.Flag() != MessageFlagManager {
        return nil, fmt.Errorf("header flag is not manager")
    }

    return &messageImpl{
        header:      header,
        baseMessage: nil,
        body:        payload,
    }, nil
}

// NewMessage creates a new message from fields passed in.
// TODO: Set up different initializers. One for BaseMessage and one for ManagerMessage.
func NewMessage(header Header, baseMessage BaseMessage, body []byte) Message {
    return &messageImpl{
        header:      header,
        baseMessage: baseMessage,
        body:        body,
    }
}

func (m messageImpl) Header() Header {
    return m.header
}

func (m messageImpl) OriginDomain() uint32 {
    return m.Header().OriginDomain()
}

func (m messageImpl) Nonce() uint32 {
    return m.Header().Nonce()
}

func (m messageImpl) DestinationDomain() uint32 {
    return m.Header().DestinationDomain()
}

func (m messageImpl) OptimisticSeconds() uint32 {
    return m.Header().OptimisticSeconds()
}

func (m messageImpl) BaseMessage() BaseMessage {
    return m.baseMessage
}

func (m messageImpl) Body() []byte {
    return m.body
}

// ToLeaf converts a message to an encoded leaf.
func (m messageImpl) ToLeaf() (leaf [32]byte, err error) {
    var toHash []byte
    if m.Header().Flag() == MessageFlagBase {
        leaf, err = m.BaseMessage().Leaf()
        if err != nil {
            return common.Hash{}, fmt.Errorf("could not get leaf: %w", err)
        }

        toHash = leaf[:]
    } else {
        toHash = crypto.Keccak256(m.Body())
    }

    headerLeaf, err := m.Header().Leaf()
    if err != nil {
        return common.Hash{}, fmt.Errorf("could not get header leaf: %w", err)
    }

    rawLeaf := crypto.Keccak256(headerLeaf[:], toHash)
    copy(leaf[:], rawLeaf)

    return leaf, nil
}

// CommittedMessage is the message that got committed.
type CommittedMessage interface {
    // Message is the fully detailed message that was committed
    Message() []byte
    // Leaf gets a leaf
    Leaf() [32]byte
    // Encode encodes a message
    Encode() ([]byte, error)
}

// NewCommittedMessage creates a new committed message.
func NewCommittedMessage(message []byte) CommittedMessage {
    return committedMessageImpl{
        message: message,
    }
}

// commitedMessageImpl contains the implementation of a committed message.
type committedMessageImpl struct {
    committedRoot common.Hash
    message       []byte
}

// CommittedMessageEncoder is used to export fields for struct encoding.
type CommittedMessageEncoder struct {
    CommittedRoot common.Hash
    Message       []byte
}

// DecodeCommittedMessage decodes a committed message into a struct.
func DecodeCommittedMessage(rawMessage []byte) (CommittedMessage, error) {
    dec := gob.NewDecoder(bytes.NewReader(rawMessage))

    var msg CommittedMessageEncoder
    err := dec.Decode(&msg)
    if err != nil {
        return nil, fmt.Errorf("could not decode message: %w", err)
    }

    decoded := committedMessageImpl{
        committedRoot: msg.CommittedRoot,
        message:       msg.Message,
    }

    return decoded, nil
}

// Encode encodes a committed message.
// Deprecated: will be removed.
func (c committedMessageImpl) Encode() ([]byte, error) {
    newCommittedMessage := CommittedMessageEncoder{
        CommittedRoot: c.committedRoot,
        Message:       c.message,
    }

    var res bytes.Buffer
    enc := gob.NewEncoder(&res)

    err := enc.Encode(newCommittedMessage)
    if err != nil {
        return nil, fmt.Errorf("could not encode %T: %w", newCommittedMessage, err)
    }
    return res.Bytes(), nil
}

// Leaf gets the leaf.
func (c committedMessageImpl) Leaf() (leaf [32]byte) {
    rawLeaf := crypto.Keccak256(c.message)
    copy(leaf[:], rawLeaf)

    return leaf
}

// Message gets the message.
func (c committedMessageImpl) Message() []byte {
    return c.message
}