agents/types/message.go
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
}