Skip to content

Commit

Permalink
Merge pull request #156 from ava-labs/off-chain-warp-messages
Browse files Browse the repository at this point in the history
Off chain warp messages
  • Loading branch information
cam-schultz authored Feb 7, 2024
2 parents f82374c + 273194b commit 9ba58ca
Show file tree
Hide file tree
Showing 20 changed files with 682 additions and 145 deletions.
5 changes: 5 additions & 0 deletions config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ type MessageProtocol int
const (
UNKNOWN_MESSAGE_PROTOCOL MessageProtocol = iota
TELEPORTER
OFF_CHAIN_REGISTRY
)

func (msg MessageProtocol) String() string {
switch msg {
case TELEPORTER:
return "teleporter"
case OFF_CHAIN_REGISTRY:
return "off-chain-registry"
default:
return "unknown"
}
Expand All @@ -52,6 +55,8 @@ func ParseMessageProtocol(msg string) MessageProtocol {
switch msg {
case "teleporter":
return TELEPORTER
case "off-chain-registry":
return OFF_CHAIN_REGISTRY
default:
return UNKNOWN_MESSAGE_PROTOCOL
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/ava-labs/avalanchego v1.10.18
github.com/ava-labs/coreth v0.12.10-rc.5
github.com/ava-labs/subnet-evm v0.5.11
github.com/ava-labs/teleporter v0.1.1-0.20240201160021-db9b809f0a88
github.com/ava-labs/teleporter v0.1.1-0.20240202165242-b0a4a335676e
github.com/ethereum/go-ethereum v1.12.0
github.com/onsi/ginkgo/v2 v2.15.0
github.com/onsi/gomega v1.31.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ github.com/ava-labs/subnet-evm v0.5.11 h1:82iddT/ahXNpwc3TBhxMDS1g6bt+Fmsb3kCyJr
github.com/ava-labs/subnet-evm v0.5.11/go.mod h1:Uv8eYNjj9Kf8S57yxoQd+IpZH/DRLvbm6yeygUDZuE4=
github.com/ava-labs/teleporter v0.1.1-0.20240201160021-db9b809f0a88 h1:yk4MBz/+betvpCc+iBNvJSxrqYagbvt3k2lkP/a9HG4=
github.com/ava-labs/teleporter v0.1.1-0.20240201160021-db9b809f0a88/go.mod h1:B6bFTJKtR2kD3wHLtAEtWa/EjlrZwLlOgKyvMw3NMNE=
github.com/ava-labs/teleporter v0.1.1-0.20240202165242-b0a4a335676e h1:i5GIu3paBUDh+08dyUYJmMNhOtEv0ztaTQxU8HJmDCI=
github.com/ava-labs/teleporter v0.1.1-0.20240202165242-b0a4a335676e/go.mod h1:B6bFTJKtR2kD3wHLtAEtWa/EjlrZwLlOgKyvMw3NMNE=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
Expand Down
4 changes: 2 additions & 2 deletions main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ func main() {
for _, msg := range cfg.ManualWarpMessages {
sourceBlockchainID := msg.GetSourceBlockchainID()

warpMessageInfo := vmtypes.WarpLogInfo{
warpLogInfo := vmtypes.WarpLogInfo{
SourceAddress: msg.GetSourceAddress(),
UnsignedMsgBytes: msg.GetUnsignedMessageBytes(),
}
manualWarpMessages[sourceBlockchainID] = append(manualWarpMessages[sourceBlockchainID], &warpMessageInfo)
manualWarpMessages[sourceBlockchainID] = append(manualWarpMessages[sourceBlockchainID], &warpLogInfo)
}

// Create relayers for each of the subnets configured as a source
Expand Down
16 changes: 12 additions & 4 deletions messages/message_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
"github.com/ava-labs/awm-relayer/config"
offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry"
"github.com/ava-labs/awm-relayer/messages/teleporter"
"github.com/ava-labs/awm-relayer/vms"
"github.com/ava-labs/awm-relayer/vms/vmtypes"
"github.com/ethereum/go-ethereum/common"
)

Expand All @@ -23,12 +23,14 @@ import (
type MessageManager interface {
// ShouldSendMessage returns true if the message should be sent to the destination chain
// If an error is returned, the boolean should be ignored by the caller.
ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationBlockchainID ids.ID) (bool, error)
ShouldSendMessage(unsignedMessage *warp.UnsignedMessage, destinationBlockchainID ids.ID) (bool, error)

// SendMessage sends the signed message to the destination chain. The payload parsed according to
// the VM rules is also passed in, since MessageManager does not assume any particular VM
SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationBlockchainID ids.ID) error
SendMessage(signedMessage *warp.Message, destinationBlockchainID ids.ID) error

// GetDestinationBlockchainID returns the destination chain ID of the destination chain for the given message
GetDestinationBlockchainID(warpMessageInfo *vmtypes.WarpMessageInfo) (ids.ID, error)
GetDestinationBlockchainID(unsignedMessage *warp.UnsignedMessage) (ids.ID, error)
}

// NewMessageManager constructs a MessageManager for a particular message protocol, defined by the message protocol address and config
Expand All @@ -48,6 +50,12 @@ func NewMessageManager(
messageProtocolConfig,
destinationClients,
)
case config.OFF_CHAIN_REGISTRY:
return offchainregistry.NewMessageManager(
logger,
messageProtocolConfig,
destinationClients,
)
default:
return nil, fmt.Errorf("invalid message format %s", format)
}
Expand Down
25 changes: 12 additions & 13 deletions messages/mocks/mock_message_manager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions messages/off-chain-registry/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package offchainregistry

import (
"fmt"

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

type Config struct {
TeleporterRegistryAddress string `json:"teleporter-registry-address"`
}

func (c *Config) Validate() error {
if !common.IsHexAddress(c.TeleporterRegistryAddress) {
return fmt.Errorf("invalid address for TeleporterRegistry: %s", c.TeleporterRegistryAddress)
}
return nil
}
173 changes: 173 additions & 0 deletions messages/off-chain-registry/message_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package offchainregistry

import (
"encoding/json"
"fmt"
"strings"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
"github.com/ava-labs/awm-relayer/config"
"github.com/ava-labs/awm-relayer/vms"
"github.com/ava-labs/subnet-evm/accounts/abi/bind"
"github.com/ava-labs/subnet-evm/ethclient"
teleporterregistry "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/upgrades/TeleporterRegistry"
"github.com/ethereum/go-ethereum/common"
"go.uber.org/zap"
)

var (
OffChainRegistrySourceAddress = common.HexToAddress("0x0000000000000000000000000000000000000000")
)

const (
addProtocolVersionGasLimit uint64 = 500_000
revertVersionNotFoundString = "TeleporterRegistry: version not found"
)

type messageManager struct {
logger logging.Logger
destinationClients map[ids.ID]vms.DestinationClient
registryAddress common.Address
}

func NewMessageManager(
logger logging.Logger,
messageProtocolConfig config.MessageProtocolConfig,
destinationClients map[ids.ID]vms.DestinationClient,
) (*messageManager, error) {
// Marshal the map and unmarshal into the off-chain registry config
data, err := json.Marshal(messageProtocolConfig.Settings)
if err != nil {
logger.Error("Failed to marshal off-chain registry config")
return nil, err
}
var messageConfig Config
if err := json.Unmarshal(data, &messageConfig); err != nil {
logger.Error("Failed to unmarshal off-chain registry config")
return nil, err
}

if err := messageConfig.Validate(); err != nil {
logger.Error(
"Invalid off-chain registry config.",
zap.Error(err),
)
return nil, err
}
return &messageManager{
logger: logger,
destinationClients: destinationClients,
registryAddress: common.HexToAddress(messageConfig.TeleporterRegistryAddress),
}, nil
}

// ShouldSendMessage returns false if any contract is already registered as the specified version in the TeleporterRegistry contract.
// This is because a single contract address can be registered to multiple versions, but each version may only map to a single contract address.
func (m *messageManager) ShouldSendMessage(unsignedMessage *warp.UnsignedMessage, destinationBlockchainID ids.ID) (bool, error) {
addressedPayload, err := warpPayload.ParseAddressedCall(unsignedMessage.Payload)
if err != nil {
m.logger.Error(
"Failed parsing addressed payload",
zap.Error(err),
)
return false, err
}
entry, destination, err := teleporterregistry.UnpackTeleporterRegistryWarpPayload(addressedPayload.Payload)
if err != nil {
m.logger.Error(
"Failed unpacking teleporter registry warp payload",
zap.Error(err),
)
return false, err
}
if destination != m.registryAddress {
m.logger.Info(
"Message is not intended for the configured registry",
zap.String("destination", destination.String()),
zap.String("configuredRegistry", m.registryAddress.String()),
)
return false, nil
}

// Get the correct destination client from the global map
destinationClient, ok := m.destinationClients[destinationBlockchainID]
if !ok {
return false, fmt.Errorf("relayer not configured to deliver to destination. destinationBlockchainID=%s", destinationBlockchainID.String())
}
client, ok := destinationClient.Client().(ethclient.Client)
if !ok {
panic(fmt.Sprintf("Destination client for chain %s is not an Ethereum client", destinationClient.DestinationBlockchainID().String()))
}

// Check if the version is already registered in the TeleporterRegistry contract.
registry, err := teleporterregistry.NewTeleporterRegistryCaller(m.registryAddress, client)
if err != nil {
m.logger.Error(
"Failed to create TeleporterRegistry caller",
zap.Error(err),
)
return false, err
}
address, err := registry.GetAddressFromVersion(&bind.CallOpts{}, entry.Version)
if err != nil {
if strings.Contains(err.Error(), revertVersionNotFoundString) {
return true, nil
}
m.logger.Error(
"Failed to get address from version",
zap.Error(err),
)
return false, err
}

m.logger.Info(
"Version is already registered in the TeleporterRegistry contract",
zap.String("version", entry.Version.String()),
zap.String("registeredAddress", address.String()),
)
return false, nil
}

func (m *messageManager) SendMessage(signedMessage *warp.Message, destinationBlockchainID ids.ID) error {
// Get the correct destination client from the global map
destinationClient, ok := m.destinationClients[destinationBlockchainID]
if !ok {
return fmt.Errorf("relayer not configured to deliver to destination. DestinationBlockchainID=%s", destinationBlockchainID)
}

// Construct the transaction call data to call the TeleporterRegistry contract.
// Only one off-chain registry Warp message is sent at a time, so we hardcode the index to 0 in the call.
callData, err := teleporterregistry.PackAddProtocolVersion(0)
if err != nil {
m.logger.Error(
"Failed packing receiveCrossChainMessage call data",
zap.String("destinationBlockchainID", destinationBlockchainID.String()),
zap.String("warpMessageID", signedMessage.ID().String()),
)
return err
}

err = destinationClient.SendTx(signedMessage, m.registryAddress.Hex(), addProtocolVersionGasLimit, callData)
if err != nil {
m.logger.Error(
"Failed to send tx.",
zap.String("destinationBlockchainID", destinationBlockchainID.String()),
zap.String("warpMessageID", signedMessage.ID().String()),
zap.Error(err),
)
return err
}
m.logger.Info(
"Sent message to destination chain",
zap.String("destinationBlockchainID", destinationBlockchainID.String()),
zap.String("warpMessageID", signedMessage.ID().String()),
)
return nil
}

func (m *messageManager) GetDestinationBlockchainID(unsignedMessage *warp.UnsignedMessage) (ids.ID, error) {
return unsignedMessage.SourceChainID, nil
}
Loading

0 comments on commit 9ba58ca

Please sign in to comment.