From 858f07bec8532acaf8d44fcd16ce36f6b3e8ecca Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Tue, 2 Apr 2024 15:29:11 +0000 Subject: [PATCH 1/5] redis support --- README.md | 6 ++++- config/config.go | 2 ++ config/keys.go | 1 + database/redis.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 4 +++ main/main.go | 26 +++++++++++++----- 7 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 database/redis.go diff --git a/README.md b/README.md index 03314318..4a77b99f 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ The relayer binary accepts a path to a JSON configuration file as the sole argum ### Configuration -The relayer is configured via a JSON file, the path to which is passed in via the `--config-file` command line argument. The following configuration options are available: +The relayer is configured via a JSON file, the path to which is passed in via the `--config-file` command line argument. Top level configuration options are also able to be set via environment variable. To get the environment variable corresponding to a key, upper case the key and change the delimiter from "-" to "_". For example, `LOG_LEVEL` sets the `"log-level"` JSON key. The following configuration options are available: `"log-level": "verbo" | "debug" | "info" | "warn" | "error" | "fatal" | "panic"` @@ -133,6 +133,10 @@ The relayer is configured via a JSON file, the path to which is passed in via th - The path to the directory in which the relayer will store its state. Defaults to `./awm-relayer-storage`. +`"redis-url": string` + +- The URL of the Redis server to use to manage state. This URL should specify the user, password, host, port, DB index, and protocol version. For example, `"redis://user:password@localhost:6379/0?protocol=3"`. Overrides `storage-location` if provided. + `"process-missed-blocks": boolean` - Whether or not to process missed blocks after restarting. Defaults to `true`. If set to false, the relayer will start processing blocks from the chain head. diff --git a/config/config.go b/config/config.go index e9665f8f..c9768a72 100644 --- a/config/config.go +++ b/config/config.go @@ -128,6 +128,7 @@ type Config struct { PChainAPIURL string `mapstructure:"p-chain-api-url" json:"p-chain-api-url"` InfoAPIURL string `mapstructure:"info-api-url" json:"info-api-url"` StorageLocation string `mapstructure:"storage-location" json:"storage-location"` + RedisURL string `mapstructure:"redis-url" json:"redis-url"` APIPort uint16 `mapstructure:"api-port" json:"api-port"` MetricsPort uint16 `mapstructure:"metrics-port" json:"metrics-port"` @@ -174,6 +175,7 @@ func BuildConfig(v *viper.Viper) (Config, bool, error) { cfg.PChainAPIURL = v.GetString(PChainAPIURLKey) cfg.InfoAPIURL = v.GetString(InfoAPIURLKey) cfg.StorageLocation = v.GetString(StorageLocationKey) + cfg.RedisURL = v.GetString(RedisURLKey) cfg.ProcessMissedBlocks = v.GetBool(ProcessMissedBlocksKey) cfg.APIPort = v.GetUint16(APIPortKey) cfg.MetricsPort = v.GetUint16(MetricsPortKey) diff --git a/config/keys.go b/config/keys.go index 0bc9f299..ca5808b6 100644 --- a/config/keys.go +++ b/config/keys.go @@ -15,6 +15,7 @@ const ( DestinationBlockchainsKey = "destination-blockchains" AccountPrivateKeyKey = "account-private-key" StorageLocationKey = "storage-location" + RedisURLKey = "redis-url" ProcessMissedBlocksKey = "process-missed-blocks" ManualWarpMessagesKey = "manual-warp-messages" ) diff --git a/database/redis.go b/database/redis.go new file mode 100644 index 00000000..3b222fdd --- /dev/null +++ b/database/redis.go @@ -0,0 +1,69 @@ +package database + +import ( + "context" + "strings" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ethereum/go-ethereum/common" + "github.com/redis/go-redis/v9" + "go.uber.org/zap" +) + +type RedisDatabase struct { + logger logging.Logger + client *redis.Client +} + +func NewRedisDatabase(logger logging.Logger, redisURL string, relayerIDs []RelayerID) (*RedisDatabase, error) { + opts, err := redis.ParseURL(redisURL) + if err != nil { + logger.Error("Failed to parse Redis URL", zap.Error(err), zap.String("url", redisURL)) + return nil, err + } + + // Create a new Redis client. + // The server address, password, db index, and protocol version are extracted from the URL + // Request timeouts use the default value of 3 seconds + client := redis.NewClient(opts) + return &RedisDatabase{ + logger: logger, + client: client, + }, nil +} + +func (r *RedisDatabase) Get(relayerID common.Hash, key DataKey) ([]byte, error) { + ctx := context.Background() + compositeKey := constructCompositeKey(relayerID, key) + val, err := r.client.Get(ctx, compositeKey).Result() + if err != nil { + r.logger.Debug("Error retrieving key from Redis", + zap.String("key", compositeKey), + zap.Error(err)) + if err == redis.Nil { + return nil, ErrKeyNotFound + } + return nil, err + } + return []byte(val), nil +} + +func (r *RedisDatabase) Put(relayerID common.Hash, key DataKey, value []byte) error { + ctx := context.Background() + compositeKey := constructCompositeKey(relayerID, key) + + // Persistently store the value in Redis + err := r.client.Set(ctx, compositeKey, value, 0).Err() + if err != nil { + r.logger.Error("Error storing key in Redis", + zap.String("key", compositeKey), + zap.Error(err)) + return err + } + return nil +} + +func constructCompositeKey(relayerID common.Hash, key DataKey) string { + const keyDelimiter = "-" + return strings.Join([]string{relayerID.Hex(), key.String()}, keyDelimiter) +} diff --git a/go.mod b/go.mod index 7a32b96a..fa8eaec6 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/cockroachdb/redact v1.1.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 // indirect github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect @@ -59,6 +60,7 @@ require ( github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/otiai10/copy v1.11.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/redis/go-redis/v9 v9.5.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/afero v1.9.5 // indirect diff --git a/go.sum b/go.sum index d025bc01..23b524fc 100644 --- a/go.sum +++ b/go.sum @@ -157,6 +157,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2U github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= @@ -492,6 +494,8 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= diff --git a/main/main.go b/main/main.go index f49cf7cd..c87d5a74 100644 --- a/main/main.go +++ b/main/main.go @@ -169,13 +169,25 @@ func main() { } // Initialize the database - db, err := database.NewJSONFileStorage(logger, cfg.StorageLocation, database.GetConfigRelayerIDs(&cfg)) - if err != nil { - logger.Error( - "Failed to create database", - zap.Error(err), - ) - panic(err) + var db database.RelayerDatabase + if cfg.RedisURL != "" { + db, err = database.NewRedisDatabase(logger, cfg.RedisURL, database.GetConfigRelayerIDs(&cfg)) + if err != nil { + logger.Error( + "Failed to create Redis database", + zap.Error(err), + ) + panic(err) + } + } else { + db, err = database.NewJSONFileStorage(logger, cfg.StorageLocation, database.GetConfigRelayerIDs(&cfg)) + if err != nil { + logger.Error( + "Failed to create JSON database", + zap.Error(err), + ) + panic(err) + } } manualWarpMessages := make(map[ids.ID][]*vmtypes.WarpLogInfo) From 523db484d4c13473795c0c434cb762695e0d3bf7 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 3 Apr 2024 18:38:56 +0000 Subject: [PATCH 2/5] copyright + static check --- database/redis.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/database/redis.go b/database/redis.go index 3b222fdd..ed5c8ba1 100644 --- a/database/redis.go +++ b/database/redis.go @@ -1,3 +1,6 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + package database import ( @@ -10,6 +13,8 @@ import ( "go.uber.org/zap" ) +var _ RelayerDatabase = &RedisDatabase{} + type RedisDatabase struct { logger logging.Logger client *redis.Client From 7aea9c9b12bca9191d01bce505f397244a586662 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 3 Apr 2024 20:38:58 +0000 Subject: [PATCH 3/5] reorder log --- database/redis.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/database/redis.go b/database/redis.go index ed5c8ba1..8e7a9d95 100644 --- a/database/redis.go +++ b/database/redis.go @@ -23,7 +23,11 @@ type RedisDatabase struct { func NewRedisDatabase(logger logging.Logger, redisURL string, relayerIDs []RelayerID) (*RedisDatabase, error) { opts, err := redis.ParseURL(redisURL) if err != nil { - logger.Error("Failed to parse Redis URL", zap.Error(err), zap.String("url", redisURL)) + logger.Error( + "Failed to parse Redis URL", + zap.String("url", redisURL), + zap.Error(err), + ) return nil, err } From 488895cd75efc5ddc464cbefa356bff7d9bc6cb8 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 3 Apr 2024 20:39:16 +0000 Subject: [PATCH 4/5] generic db ctr --- database/database.go | 26 ++++++++++++++++++++++++++ main/main.go | 28 ++++++++-------------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/database/database.go b/database/database.go index 0ab3753f..8cd31998 100644 --- a/database/database.go +++ b/database/database.go @@ -9,11 +9,13 @@ import ( "strings" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/pkg/errors" + "go.uber.org/zap" ) var ( @@ -45,6 +47,30 @@ type RelayerDatabase interface { Put(relayerID common.Hash, key DataKey, value []byte) error } +func NewDatabase(logger logging.Logger, cfg *config.Config) (RelayerDatabase, error) { + if cfg.RedisURL != "" { + db, err := NewRedisDatabase(logger, cfg.RedisURL, GetConfigRelayerIDs(cfg)) + if err != nil { + logger.Error( + "Failed to create Redis database", + zap.Error(err), + ) + return nil, err + } + return db, nil + } else { + db, err := NewJSONFileStorage(logger, cfg.StorageLocation, GetConfigRelayerIDs(cfg)) + if err != nil { + logger.Error( + "Failed to create JSON database", + zap.Error(err), + ) + return nil, err + } + return db, nil + } +} + // Returns true if an error returned by a RelayerDatabase indicates the requested key was not found func IsKeyNotFoundError(err error) bool { return errors.Is(err, ErrRelayerIDNotFound) || errors.Is(err, ErrKeyNotFound) diff --git a/main/main.go b/main/main.go index c87d5a74..540559ba 100644 --- a/main/main.go +++ b/main/main.go @@ -169,25 +169,13 @@ func main() { } // Initialize the database - var db database.RelayerDatabase - if cfg.RedisURL != "" { - db, err = database.NewRedisDatabase(logger, cfg.RedisURL, database.GetConfigRelayerIDs(&cfg)) - if err != nil { - logger.Error( - "Failed to create Redis database", - zap.Error(err), - ) - panic(err) - } - } else { - db, err = database.NewJSONFileStorage(logger, cfg.StorageLocation, database.GetConfigRelayerIDs(&cfg)) - if err != nil { - logger.Error( - "Failed to create JSON database", - zap.Error(err), - ) - panic(err) - } + db, err := database.NewDatabase(logger, &cfg) + if err != nil { + logger.Error( + "Failed to create database", + zap.Error(err), + ) + panic(err) } manualWarpMessages := make(map[ids.ID][]*vmtypes.WarpLogInfo) @@ -279,7 +267,7 @@ func runRelayer( cfg, ) if err != nil { - return fmt.Errorf("Failed to create relayer instance: %w", err) + return fmt.Errorf("failed to create relayer instance: %w", err) } logger.Info( "Created relayer", From 41a637ad5b842970bc2ead9ab8f03ff77b3f0361 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 3 Apr 2024 20:45:48 +0000 Subject: [PATCH 5/5] clarify comment --- database/redis.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/redis.go b/database/redis.go index 8e7a9d95..fe134366 100644 --- a/database/redis.go +++ b/database/redis.go @@ -33,7 +33,7 @@ func NewRedisDatabase(logger logging.Logger, redisURL string, relayerIDs []Relay // Create a new Redis client. // The server address, password, db index, and protocol version are extracted from the URL - // Request timeouts use the default value of 3 seconds + // If not provided in the URL, request timeouts use the default value of 3 seconds client := redis.NewClient(opts) return &RedisDatabase{ logger: logger,