Skip to content

Commit

Permalink
Merge branch 'main' into ryan/price-feed-contract
Browse files Browse the repository at this point in the history
  • Loading branch information
rbajollari authored May 10, 2024
2 parents d42d254 + 0058691 commit f7d2764
Show file tree
Hide file tree
Showing 14 changed files with 3,217 additions and 0 deletions.
66 changes: 66 additions & 0 deletions relayer/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
BUILD_DIR ?= $(CURDIR)/build
COMMIT := $(shell git log -1 --format='%H')

all: test-unit install

.PHONY: all

###############################################################################
## Version ##
###############################################################################

ifeq (,$(VERSION))
VERSION := $(shell git describe --exact-match 2>/dev/null)
# if VERSION is empty, then populate it with branch's name and raw commit hash
ifeq (,$(VERSION))
VERSION := $(BRANCH)-$(COMMIT)
endif
endif

###############################################################################
## Build / Install ##
###############################################################################

ldflags = -X github.com/ojo-network/ojo-evm/relayer/cmd.Version=$(VERSION) \
-X github.com/ojo-network/ojo-evm/relayer/cmd.Commit=$(COMMIT)

ifeq ($(LINK_STATICALLY),true)
ldflags += -linkmode=external -extldflags "-Wl,-z,muldefs -static"
endif

build_tags += $(BUILD_TAGS)

BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)'

build: go.sum
@echo "--> Building..."
go build -mod=readonly -o $(BUILD_DIR)/ $(BUILD_FLAGS) ./...

install: go.sum
@echo "--> Installing..."
go install -mod=readonly $(BUILD_FLAGS) ./...

.PHONY: build install

###############################################################################
## Tests & Linting ##
###############################################################################

test-unit:
@echo "--> Running tests"
@go test -short -mod=readonly -race ./... -v

.PHONY: test-unit

test-integration:
@echo "--> Running Integration Tests"
@go test -mod=readonly ./tests/integration/... -v

.PHONY: test-integration

lint:
@echo "--> Running linter"
@go run github.com/golangci/golangci-lint/cmd/golangci-lint run --fix --timeout=8m

.PHONY: lint
189 changes: 189 additions & 0 deletions relayer/cmd/relayer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package cmd

import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/signal"
"strings"
"syscall"
"time"

"github.com/cosmos/cosmos-sdk/client/input"
"github.com/ojo-network/ojo-evm/relayer/config"
"github.com/ojo-network/ojo-evm/relayer/relayer"
"github.com/ojo-network/ojo-evm/relayer/relayer/client"
"github.com/ojo-network/ojo/app/params"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
)

const (
logLevelJSON = "json"
logLevelText = "text"

flagLogLevel = "log-level"
flagLogFormat = "log-format"

envVariablePass = "KEYRING_PASS"
)

var rootCmd = &cobra.Command{
Use: "relayer [config-file]",
Args: cobra.ExactArgs(1),
Short: "relayer is a tool that interacts with the GMP module to relay price feeds to EVM.",
Long: `A tool that anyone can use to trigger relays from the Ojo blockchain to a given EVM.
This is used to support Ojo's "Core" price feeds, which receive periodic push updates in addition
to the pull-based relayer events triggered by the two-way GMP calls.`,
RunE: relayerCmdHandler,
}

func init() {
// We need to set our bech32 address prefix because it was moved
// out of ojo's init function.
// Ref: https://github.com/ojo-network/ojo/pull/63
params.SetAddressPrefixes()
rootCmd.PersistentFlags().String(flagLogLevel, zerolog.InfoLevel.String(), "logging level")
rootCmd.PersistentFlags().String(flagLogFormat, logLevelText, "logging format; must be either json or text")
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func relayerCmdHandler(cmd *cobra.Command, args []string) error {
logLvlStr, err := cmd.Flags().GetString(flagLogLevel)
if err != nil {
return err
}

logLvl, err := zerolog.ParseLevel(logLvlStr)
if err != nil {
return err
}

logFormatStr, err := cmd.Flags().GetString(flagLogFormat)
if err != nil {
return err
}

var logWriter io.Writer
switch strings.ToLower(logFormatStr) {
case logLevelJSON:
logWriter = os.Stderr

case logLevelText:
logWriter = zerolog.ConsoleWriter{Out: os.Stderr}

default:
return fmt.Errorf("invalid logging format: %s", logFormatStr)
}

cfg, err := config.LoadConfigFromFlags(args[0], "")
if err != nil {
return err
}

ctx, cancel := context.WithCancel(cmd.Context())
g, ctx := errgroup.WithContext(ctx)

logger := zerolog.New(logWriter).Level(logLvl).With().Timestamp().Logger()

// listen for and trap any OS signal to gracefully shutdown and exit
trapSignal(cancel, logger)

// Gather pass via env variable || std input
keyringPass, err := getKeyringPassword()
if err != nil {
return err
}

// placeholder rpc timeout
rpcTimeout := time.Second * 10

relayerClient, err := client.NewRelayerClient(
ctx,
logger,
cfg.Account.ChainID,
cfg.Keyring.Backend,
cfg.Keyring.Dir,
keyringPass,
cfg.RPC.TMRPCEndpoint,
rpcTimeout,
cfg.Account.Address,
cfg.RPC.GRPCEndpoint,
cfg.Gas,
)
if err != nil {
return err
}

relayer, err := relayer.New(logger, relayerClient, cfg)
if err != nil {
return err
}

g.Go(func() error {
// start the process that observes and publishes exchange prices
return startRelayer(ctx, logger, relayer)
})

// Block main process until all spawned goroutines have gracefully exited and
// signal has been captured in the main process or if an error occurs.
return g.Wait()
}

// trapSignal will listen for any OS signal and invoke Done on the main
// WaitGroup allowing the main process to gracefully exit.
func trapSignal(cancel context.CancelFunc, logger zerolog.Logger) {
sigCh := make(chan os.Signal, 1)

signal.Notify(sigCh, syscall.SIGTERM)
signal.Notify(sigCh, syscall.SIGINT)

go func() {
sig := <-sigCh
logger.Info().Str("signal", sig.String()).Msg("caught signal; shutting down...")
cancel()
}()
}

func getKeyringPassword() (string, error) {
reader := bufio.NewReader(os.Stdin)

pass := os.Getenv(envVariablePass)
if pass == "" {
return input.GetString("Enter keyring password", reader)
}
return pass, nil
}

func startRelayer(ctx context.Context, logger zerolog.Logger, r *relayer.Relayer) error {
srvErrCh := make(chan error, 1)

go func() {
logger.Info().Msg("starting relayer..")
srvErrCh <- r.Start(ctx)
}()

for {
select {
case <-ctx.Done():
logger.Info().Msg("shutting down relayer..")
return nil

case err := <-srvErrCh:
logger.Err(err).Msg("error starting the relayer relayer")
r.Stop()
return err
}
}
}
73 changes: 73 additions & 0 deletions relayer/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package config

import (
"errors"
"time"

"github.com/go-playground/validator/v10"
)

const (
SampleNodeConfigPath = "relayer.toml"
)

var (
validate = validator.New()

// ErrEmptyConfigPath defines a sentinel error for an empty config path.
ErrEmptyConfigPath = errors.New("empty configuration file path")
)

type (
// Config defines all necessary configuration parameters.
Config struct {
ConfigDir string `mapstructure:"config_dir"`
Account Account `mapstructure:"account" validate:"required,gt=0,dive,required"`
Keyring Keyring `mapstructure:"keyring" validate:"required,gt=0,dive,required"`
RPC RPC `mapstructure:"rpc" validate:"required,gt=0,dive,required"`
Gas uint64 `mapstructure:"gas"`
Relayer Relayer `mapstructure:"relayer" validate:"required,gt=0,dive,required"`
Assets []Assets `mapstructure:"assets" validate:"required,gt=0,dive,required"`
}

// Account defines account related configuration that is related to the Ojo
// network and transaction signing functionality.
Account struct {
ChainID string `mapstructure:"chain_id" validate:"required"`
Address string `mapstructure:"address" validate:"required"`
}

// Keyring defines the required Ojo keyring configuration.
Keyring struct {
Backend string `mapstructure:"backend" validate:"required"`
Dir string `mapstructure:"dir" validate:"required"`
}

// RPC defines RPC configuration of both the Ojo gRPC and Tendermint nodes.
RPC struct {
TMRPCEndpoint string `mapstructure:"tmrpc_endpoint" validate:"required"`
GRPCEndpoint string `mapstructure:"grpc_endpoint" validate:"required"`
RPCTimeout string `mapstructure:"rpc_timeout" validate:"required"`
}

Relayer struct {
Interval time.Duration `mapstructure:"interval" validate:"required"`
Deviation float64 `mapstructure:"deviation" validate:"required"`
Destination string `mapstructure:"destination" validate:"required"`
Contract string `mapstructure:"contract" validate:"required"`
Tokens string `mapstructure:"tokens" validate:"required"`
}

Assets struct {
Denom string `mapstructure:"denom" validate:"required"`
}
)

// Validate returns an error if the Config object is invalid.
func (c Config) Validate() (err error) {
return validate.Struct(c)
}

// noop
func (c *Config) setDefaults() {
}
Loading

0 comments on commit f7d2764

Please sign in to comment.