From 46ced194ebf849011ab957dd7e816cd3d7f0048e Mon Sep 17 00:00:00 2001 From: mrekucci <4932785+mrekucci@users.noreply.github.com> Date: Mon, 11 Mar 2024 19:58:17 -0300 Subject: [PATCH] refactor: change logger to slog --- standard/bridge-v1/cmd/relayer/main.go | 265 ++++++++++-------- standard/bridge-v1/cmd/user_cli/main.go | 172 +++++++----- standard/bridge-v1/deploy_contracts.sh | 30 +- standard/bridge-v1/emulator/main.go | 98 ++++--- .../example_config/relayer_config.yml | 12 +- standard/bridge-v1/go.mod | 11 +- standard/bridge-v1/go.sum | 26 +- standard/bridge-v1/pkg/relayer/listener.go | 91 +++--- standard/bridge-v1/pkg/relayer/relayer.go | 81 ++++-- standard/bridge-v1/pkg/relayer/transactor.go | 91 +++--- .../bridge-v1/pkg/shared/{tx.go => client.go} | 117 ++++---- standard/bridge-v1/pkg/transfer/transfer.go | 107 ++++--- standard/bridge-v1/pkg/util/util.go | 55 ++++ 13 files changed, 698 insertions(+), 458 deletions(-) rename standard/bridge-v1/pkg/shared/{tx.go => client.go} (65%) create mode 100644 standard/bridge-v1/pkg/util/util.go diff --git a/standard/bridge-v1/cmd/relayer/main.go b/standard/bridge-v1/cmd/relayer/main.go index 725788c..66b4dfc 100644 --- a/standard/bridge-v1/cmd/relayer/main.go +++ b/standard/bridge-v1/cmd/relayer/main.go @@ -5,166 +5,170 @@ import ( "os" "os/signal" "path/filepath" - "standard-bridge/pkg/relayer" + "slices" "strings" "syscall" "time" + "standard-bridge/pkg/relayer" + "standard-bridge/pkg/util" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" - "gopkg.in/yaml.v2" + "github.com/urfave/cli/v2/altsrc" ) const ( - defaultHTTPPort = 8080 + defaultHTTPPort = 8080 + defaultConfigDir = "~/.mev-commit-bridge" + defaultKeyFile = "key" ) var ( optionConfig = &cli.StringFlag{ - Name: "config", - Usage: "path to relayer config file", - Required: false, // Can also set config via env var - EnvVars: []string{"STANDARD_BRIDGE_RELAYER_CONFIG"}, + Name: "config", + Usage: "path to relayer config file", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_CONFIG"}, } -) - -func main() { - app := &cli.App{ - Name: "standard-bridge-relayer", - Usage: "Entry point for relayer of mev-commit standard bridge", - Commands: []*cli.Command{ - { - Name: "start", - Usage: "Start standard bridge relayer", - Flags: []cli.Flag{ - optionConfig, - }, - Action: func(c *cli.Context) error { - return start(c) - }, - }, - }} - if err := app.Run(os.Args); err != nil { - fmt.Fprintf(app.Writer, "exited with error: %v\n", err) - } -} + optionPrivKeyFile = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "priv-key-file", + Usage: "path to private key file", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_PRIV_KEY_FILE"}, + Value: filepath.Join(defaultConfigDir, defaultKeyFile), + }) -func loadConfigFromEnv() config { - cfg := config{ - PrivKeyFilePath: os.Getenv("PRIVATE_KEY_FILE_PATH"), - LogLevel: os.Getenv("LOG_LEVEL"), - L1RPCUrl: os.Getenv("L1_RPC_URL"), - SettlementRPCUrl: os.Getenv("SETTLEMENT_RPC_URL"), - L1ContractAddr: os.Getenv("L1_CONTRACT_ADDR"), - SettlementContractAddr: os.Getenv("SETTLEMENT_CONTRACT_ADDR"), - } - return cfg -} + optionLogFmt = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "log-fmt", + Usage: "log format to use, options are 'text' or 'json'", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_LOG_FMT"}, + Value: "text", + Action: func(_ *cli.Context, s string) error { + if !slices.Contains([]string{"text", "json"}, s) { + return fmt.Errorf("invalid value: -log-fmt=%q", s) + } + return nil + }, + }) -func loadConfigFromFile(cfg *config, filePath string) error { - buf, err := os.ReadFile(filePath) - if err != nil { - return fmt.Errorf("failed to read config file at: %s, %w", filePath, err) - } - if err := yaml.Unmarshal(buf, cfg); err != nil { - return fmt.Errorf("failed to unmarshal config file at: %s, %w", filePath, err) - } - return nil -} + optionLogLevel = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "log-level", + Usage: "log level to use, options are 'debug', 'info', 'warn', 'error'", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_LOG_LEVEL"}, + Value: "info", + Action: func(_ *cli.Context, s string) error { + if !slices.Contains([]string{"debug", "info", "warn", "error"}, s) { + return fmt.Errorf("invalid value: -log-level=%q", s) + } + return nil + }, + }) -func setupLogging(logLevel string) { - lvl, err := zerolog.ParseLevel(logLevel) - if err != nil { - log.Fatal().Err(err).Msg("failed to parse log level") - } - zerolog.SetGlobalLevel(lvl) - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix - log.Logger = log.Output(os.Stdout).With().Caller().Logger() -} + optionLogTags = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "log-tags", + Usage: "log tags is a comma-separated list of pairs that will be inserted into each log line", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_LOG_TAGS"}, + Action: func(ctx *cli.Context, s string) error { + for i, p := range strings.Split(s, ",") { + if len(strings.Split(p, ":")) != 2 { + return fmt.Errorf("invalid log-tags at index %d, expecting ", i) + } + } + return nil + }, + }) -type config struct { - PrivKeyFilePath string `yaml:"priv_key_file_path" json:"priv_key_file_path"` - LogLevel string `yaml:"log_level" json:"log_level"` - L1RPCUrl string `yaml:"l1_rpc_url" json:"l1_rpc_url"` - SettlementRPCUrl string `yaml:"settlement_rpc_url" json:"settlement_rpc_url"` - L1ContractAddr string `yaml:"l1_contract_addr" json:"l1_contract_addr"` - SettlementContractAddr string `yaml:"settlement_contract_addr" json:"settlement_contract_addr"` -} + optionL1RPCUrl = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "l1-rpc-url", + Usage: "URL for L1 RPC", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_L1_RPC_URL"}, + }) -func checkConfig(cfg *config) error { - if cfg.PrivKeyFilePath == "" { - return fmt.Errorf("priv_key_file_path is required") - } + optionSettlementRPCUrl = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "settlement-rpc-url", + Usage: "URL for settlement RPC", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL"}, + Value: "http://localhost:8545", + }) - if cfg.LogLevel == "" { - cfg.LogLevel = "info" - } + optionL1ContractAddr = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "l1-contract-addr", + Usage: "address of the L1 gateway contract", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_L1_CONTRACT_ADDR"}, + }) - if cfg.L1RPCUrl == "" { - return fmt.Errorf("l1_rpc_url is required") - } + optionSettlementContractAddr = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "settlement-contract-addr", + Usage: "address of the settlement gateway contract", + EnvVars: []string{"STANDARD_BRIDGE_RELAYER_SETTLEMENT_CONTRACT_ADDR"}, + }) +) - if cfg.SettlementRPCUrl == "" { - return fmt.Errorf("settlement_rpc_url is required") +func main() { + flags := []cli.Flag{ + optionConfig, + optionPrivKeyFile, + optionLogFmt, + optionLogLevel, + optionLogTags, + optionL1RPCUrl, + optionSettlementRPCUrl, + optionL1ContractAddr, + optionSettlementContractAddr, } - if cfg.L1ContractAddr == "" { - return fmt.Errorf("oracle_contract_addr is required") + app := &cli.App{ + Name: "standard-bridge-relayer", + Usage: "Entry point for relayer of mev-commit standard bridge", + Commands: []*cli.Command{{ + Name: "start", + Usage: "Start standard bridge relayer", + Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc(optionConfig.Name)), + Flags: flags, + Action: start, + }}, } - if cfg.SettlementContractAddr == "" { - return fmt.Errorf("preconf_contract_addr is required") + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(app.Writer, "exited with error: %v\n", err) } - - return nil } +// start is the entrypoint of the cli app. func start(c *cli.Context) error { - cfg := loadConfigFromEnv() - - configFilePath := c.String(optionConfig.Name) - if configFilePath == "" { - log.Info().Msg("env var config will be used") - } else { - log.Info().Str("config_file", configFilePath).Msg( - "overriding env var config with file") - if err := loadConfigFromFile(&cfg, configFilePath); err != nil { - log.Fatal().Err(err).Msg("failed to load config provided as file") - } - } - - if err := checkConfig(&cfg); err != nil { - log.Fatal().Err(err).Msg("invalid config") + logger, err := util.NewLogger( + c.String(optionLogLevel.Name), + c.String(optionLogFmt.Name), + c.String(optionLogTags.Name), + c.App.Writer, + ) + if err != nil { + return fmt.Errorf("failed to create logger: %w", err) } - setupLogging(cfg.LogLevel) - - privKeyFilePath := cfg.PrivKeyFilePath - - if strings.HasPrefix(privKeyFilePath, "~/") { - homeDir, err := os.UserHomeDir() - if err != nil { - log.Err(err).Msg("failed to get user home dir") - } - privKeyFilePath = filepath.Join(homeDir, privKeyFilePath[2:]) + privKeyFile, err := resolveFilePath(c.String(optionPrivKeyFile.Name)) + if err != nil { + return fmt.Errorf("failed to get private key file path: %w", err) } - privKey, err := crypto.LoadECDSA(privKeyFilePath) + privKey, err := crypto.LoadECDSA(privKeyFile) if err != nil { - log.Err(err).Msg("failed to load private key") + return fmt.Errorf("failed to load private key: %w", err) } - r := relayer.NewRelayer(&relayer.Options{ + r, err := relayer.NewRelayer(&relayer.Options{ + Ctx: c.Context, + Logger: logger.With("component", "relayer"), PrivateKey: privKey, - L1RPCUrl: cfg.L1RPCUrl, - SettlementRPCUrl: cfg.SettlementRPCUrl, - L1ContractAddr: common.HexToAddress(cfg.L1ContractAddr), - SettlementContractAddr: common.HexToAddress(cfg.SettlementContractAddr), + L1RPCUrl: c.String(optionL1RPCUrl.Name), + SettlementRPCUrl: c.String(optionSettlementRPCUrl.Name), + L1ContractAddr: common.HexToAddress(c.String(optionL1ContractAddr.Name)), + SettlementContractAddr: common.HexToAddress(c.String(optionSettlementContractAddr.Name)), }) + if err != nil { + return err + } interruptSigChan := make(chan os.Signal, 1) signal.Notify(interruptSigChan, os.Interrupt, syscall.SIGTERM) @@ -174,7 +178,7 @@ func start(c *cli.Context) error { case <-interruptSigChan: case <-c.Done(): } - fmt.Fprintf(c.App.Writer, "shutting down...\n") + logger.Info("shutting down...") closedAllSuccessfully := make(chan struct{}) go func() { @@ -182,14 +186,31 @@ func start(c *cli.Context) error { err := r.TryCloseAll() if err != nil { - log.Error().Err(err).Msg("failed to close all routines and db connection") + logger.Error("failed to close all routines and db connection", "error", err) } }() select { case <-closedAllSuccessfully: case <-time.After(5 * time.Second): - log.Error().Msg("failed to close all in time") + logger.Error("failed to close all in time", "error", err) } return nil } + +func resolveFilePath(path string) (string, error) { + if path == "" { + return "", fmt.Errorf("path is empty") + } + + if strings.HasPrefix(path, "~") { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + + return filepath.Join(home, path[1:]), nil + } + + return path, nil +} diff --git a/standard/bridge-v1/cmd/user_cli/main.go b/standard/bridge-v1/cmd/user_cli/main.go index df11cd3..b07434f 100644 --- a/standard/bridge-v1/cmd/user_cli/main.go +++ b/standard/bridge-v1/cmd/user_cli/main.go @@ -3,22 +3,26 @@ package main import ( "context" "crypto/ecdsa" + "errors" "fmt" + "log/slog" "math/big" "os" - "standard-bridge/pkg/shared" - transfer "standard-bridge/pkg/transfer" "strconv" "strings" + "standard-bridge/pkg/shared" + "standard-bridge/pkg/transfer" + "standard-bridge/pkg/util" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" ) +var errNoPendingTransactionFound = errors.New("no pending transaction found") + func main() { app := &cli.App{ Name: "bridge-cli", @@ -43,9 +47,7 @@ func main() { Usage: "Automatically cancel existing pending transactions", }, }, - Action: func(c *cli.Context) error { - return bridgeToSettlement(c) - }, + Action: bridgeToSettlement, }, { Name: "bridge-to-l1", @@ -66,9 +68,7 @@ func main() { Usage: "Automatically cancel existing pending transactions", }, }, - Action: func(c *cli.Context) error { - return bridgeToL1(c) - }, + Action: bridgeToL1, }, }, } @@ -77,11 +77,44 @@ func main() { } } +func loadConfig() (*envConfig, error) { + cfg, err := loadConfigFromEnv() + if err != nil { + return nil, fmt.Errorf("failed to load config: %w", err) + } + if err := checkEnvConfig(cfg); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + return cfg, nil +} + func bridgeToSettlement(c *cli.Context) error { - config := preTransfer(c) + cfg, err := loadConfig() + if err != nil { + return err + } + logger, err := util.NewLogger(cfg.LogLevel, "text", "", os.Stdout) + if err != nil { + return err + } + config, err := preTransfer(c, cfg) + if err != nil { + return err + } autoCancel := c.Bool("cancel-pending") - handlePendingTxes(context.Background(), config.PrivateKey, config.L1RPCUrl, autoCancel) + ok, err := handlePendingTxes(c.Context, logger.With("component", "l1_eth_client"), config.PrivateKey, config.L1RPCUrl, autoCancel) + switch { + case err == nil && !ok: + logger.Info("user chose not to cancel pending transactions, exiting...") + return nil + case errors.Is(err, errNoPendingTransactionFound): + // Do nothing. + case err != nil: + return err + } + t, err := transfer.NewTransferToSettlement( + logger.With("component", "settlement_transfer"), config.Amount, config.DestAddress, config.PrivateKey, @@ -91,20 +124,42 @@ func bridgeToSettlement(c *cli.Context) error { config.SettlementContractAddr, ) if err != nil { - log.Fatal().Err(err).Msg("failed to create transfer to settlement") + return fmt.Errorf("failed to create transfer to settlement: %w", err) } - err = t.Start(context.Background()) + err = t.Start(c.Context) if err != nil { - log.Fatal().Err(err).Msg("failed to start transfer to settlement") + return fmt.Errorf("failed to start transfer to settlement: %w", err) } return nil } func bridgeToL1(c *cli.Context) error { - config := preTransfer(c) + cfg, err := loadConfig() + if err != nil { + return err + } + logger, err := util.NewLogger(cfg.LogLevel, "text", "", os.Stdout) + if err != nil { + return err + } + config, err := preTransfer(c, cfg) + if err != nil { + return err + } autoCancel := c.Bool("cancel-pending") - handlePendingTxes(context.Background(), config.PrivateKey, config.SettlementRPCUrl, autoCancel) + ok, err := handlePendingTxes(c.Context, logger.With("component", "settlement_eth_client"), config.PrivateKey, config.SettlementRPCUrl, autoCancel) + switch { + case err == nil && !ok: + logger.Info("user chose not to cancel pending transactions, exiting...") + return nil + case errors.Is(err, errNoPendingTransactionFound): + // Do nothing. + case err != nil: + return err + } + t, err := transfer.NewTransferToL1( + logger.With("component", "l1_transfer"), config.Amount, config.DestAddress, config.PrivateKey, @@ -114,11 +169,11 @@ func bridgeToL1(c *cli.Context) error { config.SettlementContractAddr, ) if err != nil { - log.Fatal().Err(err).Msg("failed to create transfer to L1") + return fmt.Errorf("failed to create transfer to L1: %w", err) } - err = t.Start(context.Background()) + err = t.Start(c.Context) if err != nil { - log.Fatal().Err(err).Msg("failed to start transfer to L1") + return fmt.Errorf("failed to start transfer to L1: %w", err) } return nil } @@ -133,31 +188,24 @@ type preTransferConfig struct { SettlementContractAddr common.Address } -func preTransfer(c *cli.Context) preTransferConfig { - cfg := loadConfigFromEnv() - - if err := checkEnvConfig(&cfg); err != nil { - log.Fatal().Err(err).Msg("invalid config") - } - setupLogging(cfg.LogLevel) - +func preTransfer(c *cli.Context, cfg *envConfig) (*preTransferConfig, error) { privKeyTrimmed := strings.TrimPrefix(cfg.PrivKey, "0x") privKey, err := crypto.HexToECDSA(privKeyTrimmed) if err != nil { - log.Err(err).Msg("failed to load private key") + return nil, errors.New("failed to load private key") } amount := c.Int("amount") if amount <= 0 { - log.Fatal().Msg("amount must be greater than 0") + return nil, errors.New("amount must be greater than 0") } destAddr := c.String("dest-addr") if !common.IsHexAddress(destAddr) { - log.Fatal().Msg("dest-addr must be a valid hex address") + return nil, errors.New("dest-addr must be a valid hex address") } - return preTransferConfig{ + return &preTransferConfig{ Amount: big.NewInt(int64(amount)), DestAddress: common.HexToAddress(destAddr), PrivateKey: privKey, @@ -165,7 +213,7 @@ func preTransfer(c *cli.Context) preTransferConfig { L1RPCUrl: cfg.L1RPCUrl, L1ContractAddr: common.HexToAddress(cfg.L1ContractAddr), SettlementContractAddr: common.HexToAddress(cfg.SettlementContractAddr), - } + }, nil } type envConfig struct { @@ -179,18 +227,18 @@ type envConfig struct { SettlementContractAddr string } -func loadConfigFromEnv() envConfig { +func loadConfigFromEnv() (*envConfig, error) { l1ChainID := os.Getenv("L1_CHAIN_ID") l1ChainIDInt, err := strconv.Atoi(l1ChainID) if err != nil { - log.Fatal().Err(err).Msg("failed to convert L1_CHAIN_ID to int") + return nil, fmt.Errorf("failed to convert L1_CHAIN_ID to int: %w", err) } settlementChainID := os.Getenv("SETTLEMENT_CHAIN_ID") settlementChainIDInt, err := strconv.Atoi(settlementChainID) if err != nil { - log.Fatal().Err(err).Msg("failed to convert SETTLEMENT_CHAIN_ID to int") + return nil, fmt.Errorf("failed to convert SETTLEMENT_CHAIN_ID to int: %w", err) } - cfg := envConfig{ + return &envConfig{ PrivKey: os.Getenv("PRIVATE_KEY"), LogLevel: os.Getenv("LOG_LEVEL"), L1RPCUrl: os.Getenv("L1_RPC_URL"), @@ -199,8 +247,7 @@ func loadConfigFromEnv() envConfig { SettlementChainID: settlementChainIDInt, L1ContractAddr: os.Getenv("L1_CONTRACT_ADDR"), SettlementContractAddr: os.Getenv("SETTLEMENT_CONTRACT_ADDR"), - } - return cfg + }, nil } func checkEnvConfig(cfg *envConfig) error { @@ -231,7 +278,7 @@ func checkEnvConfig(cfg *envConfig) error { return fmt.Errorf("failed to get l1 chain id: %v", err) } if obtainedL1ChainID.Cmp(big.NewInt(int64(cfg.L1ChainID))) != 0 { - log.Fatal().Msgf("l1 chain id mismatch. Expected: %d, Obtained: %d", cfg.L1ChainID, obtainedL1ChainID) + return fmt.Errorf("l1 chain id mismatch. Expected: %d, Obtained: %d", cfg.L1ChainID, obtainedL1ChainID) } settlementClient, err := ethclient.Dial(cfg.SettlementRPCUrl) if err != nil { @@ -242,53 +289,48 @@ func checkEnvConfig(cfg *envConfig) error { return fmt.Errorf("failed to get settlement chain id: %v", err) } if obtainedSettlementChainID.Cmp(big.NewInt(int64(cfg.SettlementChainID))) != 0 { - log.Fatal().Msgf("settlement chain id mismatch. Expected: %d, Obtained: %d", cfg.SettlementChainID, obtainedSettlementChainID) + return fmt.Errorf("settlement chain id mismatch. Expected: %d, Obtained: %d", cfg.SettlementChainID, obtainedSettlementChainID) } return nil } -func setupLogging(logLevel string) { - lvl, err := zerolog.ParseLevel(logLevel) - if err != nil { - log.Fatal().Err(err).Msg("failed to parse log level") - } - zerolog.SetGlobalLevel(lvl) - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) -} - func handlePendingTxes( ctx context.Context, + logger *slog.Logger, privateKey *ecdsa.PrivateKey, url string, autoCancel bool, -) { - ethClient, err := ethclient.Dial(url) +) (bool, error) { + rawClient, err := ethclient.Dial(url) if err != nil { - log.Fatal().Err(err).Msg("failed to connect to eth client") + return false, fmt.Errorf("failed to connect to eth client: %w", err) } - exist, err := shared.PendingTransactionsExist(ctx, privateKey, ethClient) + ethClient := shared.NewETHClient(logger, rawClient) + + exist, err := ethClient.PendingTransactionsExist(ctx, privateKey) if err != nil { - log.Fatal().Err(err).Msg("failed to check pending transactions") + return false, fmt.Errorf("failed to check pending transactions: %w", err) } if !exist { - log.Info().Msg("No pending transactions exist for signing account.") - return + return false, errNoPendingTransactionFound } if autoCancel { - log.Info().Msg("Automatically cancelling existing pending transactions.") - shared.CancelPendingTxes(ctx, privateKey, ethClient) - return + if err := ethClient.CancelPendingTxes(ctx, privateKey); err != nil { + return false, fmt.Errorf("fail to cancel pending transaction(s): %w", err) + } + return true, nil } fmt.Println("Pending transactions exist for signing account. Do you want to cancel them? (y/n)") var response string _, err = fmt.Scanln(&response) if err != nil { - log.Fatal().Err(err).Msg("failed to read user input") + return false, fmt.Errorf("failed to read user input: %w", err) } if strings.ToLower(response) == "y" { - shared.CancelPendingTxes(ctx, privateKey, ethClient) - return + if err := ethClient.CancelPendingTxes(ctx, privateKey); err != nil { + return false, fmt.Errorf("fail to cancel pending transaction(s): %w", err) + } + return true, nil } - log.Fatal().Msg("User chose not to cancel pending transactions. Exiting.") + return false, nil } diff --git a/standard/bridge-v1/deploy_contracts.sh b/standard/bridge-v1/deploy_contracts.sh index 26a6c4b..d8f9ea6 100755 --- a/standard/bridge-v1/deploy_contracts.sh +++ b/standard/bridge-v1/deploy_contracts.sh @@ -1,7 +1,7 @@ #!/bin/sh L1_CHAIN_ID=${L1_CHAIN_ID:-"17000"} # Holesky -L1_RPC_URL=${L1_RPC_URL:-"https://ethereum-holesky.publicnode.com"} +STANDARD_BRIDGE_RELAYER_L1_RPC_URL=${STANDARD_BRIDGE_RELAYER_L1_RPC_URL:-"https://ethereum-holesky.publicnode.com"} SETTLEMENT_CHAIN_ID=${SETTLEMENT_CHAIN_ID:-"17864"} SETTLEMENT_DEPLOYER_PRIVKEY=${DEPLOYER_PRIVKEY:-"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"} # Same default deployer as core contracts @@ -27,11 +27,11 @@ ARTIFACT_OUT_PATH=$(realpath "$ARTIFACT_OUT_PATH") fail_if_not_set() { if [ -z "$1" ]; then - echo "Error: Required environment variable not set (one of SETTLEMENT_RPC_URL, RELAYER_PRIVKEY)" + echo "Error: Required environment variable not set (one of STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL, RELAYER_PRIVKEY)" exit 1 fi } -fail_if_not_set "${SETTLEMENT_RPC_URL}" +fail_if_not_set "${STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL}" fail_if_not_set "${RELAYER_PRIVKEY}" RELAYER_ADDR=$("$CAST_BIN_PATH" wallet address "$RELAYER_PRIVKEY") @@ -75,43 +75,43 @@ check_balance() { fi } -check_chain_id "$L1_RPC_URL" "$L1_CHAIN_ID" -check_chain_id "$SETTLEMENT_RPC_URL" "$SETTLEMENT_CHAIN_ID" +check_chain_id "$STANDARD_BRIDGE_RELAYER_L1_RPC_URL" "$L1_CHAIN_ID" +check_chain_id "$STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL" "$SETTLEMENT_CHAIN_ID" -check_create2 "$L1_RPC_URL" -check_create2 "$SETTLEMENT_RPC_URL" +check_create2 "$STANDARD_BRIDGE_RELAYER_L1_RPC_URL" +check_create2 "$STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL" SETTLEMENT_DEPLOYER_ADDR=$("$CAST_BIN_PATH" wallet address "$SETTLEMENT_DEPLOYER_PRIVKEY") -check_balance "$SETTLEMENT_RPC_URL" "$SETTLEMENT_DEPLOYER_ADDR" +check_balance "$STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL" "$SETTLEMENT_DEPLOYER_ADDR" "$CAST_BIN_PATH" send \ - --rpc-url "$SETTLEMENT_RPC_URL" \ + --rpc-url "$STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL" \ --private-key "$SETTLEMENT_DEPLOYER_PRIVKEY" \ "$RELAYER_ADDR" \ --value 100ether -check_balance "$SETTLEMENT_RPC_URL" "$RELAYER_ADDR" -check_balance "$L1_RPC_URL" "$RELAYER_ADDR" +check_balance "$STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL" "$RELAYER_ADDR" +check_balance "$STANDARD_BRIDGE_RELAYER_L1_RPC_URL" "$RELAYER_ADDR" # Create/fund a new L1 deployer to avoid L1Gateway contract addr collision on Holeksy L1_DEPLOYER_PRIVKEY=$($CAST_BIN_PATH wallet new | grep 'Private key' | awk '{ print $NF }') L1_DEPLOYER_ADDR=$($CAST_BIN_PATH wallet address "$L1_DEPLOYER_PRIVKEY") echo "New L1 deployer to be funded by relayer: $L1_DEPLOYER_ADDR" $CAST_BIN_PATH send \ - --rpc-url "$L1_RPC_URL" \ + --rpc-url "$STANDARD_BRIDGE_RELAYER_L1_RPC_URL" \ --private-key "$RELAYER_PRIVKEY" \ "$L1_DEPLOYER_ADDR" \ --value 0.5ether EXPECTED_WHITELIST_ADDR="0x57508f0B0f3426758F1f3D63ad4935a7c9383620" -check_balance "$SETTLEMENT_RPC_URL" "$EXPECTED_WHITELIST_ADDR" +check_balance "$STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL" "$EXPECTED_WHITELIST_ADDR" echo "changing directory to $CONTRACTS_PATH and running deploy scripts for standard bridge" cd "$CONTRACTS_PATH" || exit RELAYER_ADDR="$RELAYER_ADDR" $FORGE_BIN_PATH script \ "scripts/DeployStandardBridge.s.sol:DeploySettlementGateway" \ - --rpc-url "$SETTLEMENT_RPC_URL" \ + --rpc-url "$STANDARD_BRIDGE_RELAYER_SETTLEMENT_RPC_URL" \ --private-key "$SETTLEMENT_DEPLOYER_PRIVKEY" \ --broadcast \ --chain-id "$SETTLEMENT_CHAIN_ID" \ @@ -123,7 +123,7 @@ mv SettlementGatewayArtifact.json "$ARTIFACT_OUT_PATH" RELAYER_ADDR="$RELAYER_ADDR" $FORGE_BIN_PATH script \ "scripts/DeployStandardBridge.s.sol:DeployL1Gateway" \ - --rpc-url "$L1_RPC_URL" \ + --rpc-url "$STANDARD_BRIDGE_RELAYER_L1_RPC_URL" \ --private-key "$L1_DEPLOYER_PRIVKEY" \ --broadcast \ --chain-id "$L1_CHAIN_ID" \ diff --git a/standard/bridge-v1/emulator/main.go b/standard/bridge-v1/emulator/main.go index 28f38c4..e6e4650 100644 --- a/standard/bridge-v1/emulator/main.go +++ b/standard/bridge-v1/emulator/main.go @@ -3,23 +3,22 @@ package main import ( "context" "crypto/rand" + "fmt" + "log/slog" "math/big" mathrand "math/rand" - "os" "time" - "github.com/rs/zerolog/log" - "standard-bridge/pkg/shared" - transfer "standard-bridge/pkg/transfer" + "standard-bridge/pkg/transfer" + "standard-bridge/pkg/util" + "github.com/DataDog/datadog-api-client-go/api/v2/datadog" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" - - datadog "github.com/DataDog/datadog-api-client-go/api/v2/datadog" ) const ( @@ -30,22 +29,31 @@ const ( ) func main() { + logger, err := util.NewLogger(slog.LevelDebug.String(), "text", "", os.Stdout) + if err != nil { + fmt.Printf("failed to create logger: %v\n", err) + os.Exit(1) + } privateKeyString := os.Getenv("PRIVATE_KEY") if privateKeyString == "" { - log.Fatal().Msg("PRIVATE_KEY env var is required") + logger.Error("PRIVATE_KEY env var is required") + os.Exit(1) } privateKey, err := crypto.HexToECDSA(privateKeyString) if err != nil { - log.Fatal().Err(err).Msg("Failed to parse private key") + logger.Error("failed to parse private key") + os.Exit(1) } transferAddressString := os.Getenv("ACCOUNT_ADDR") if transferAddressString == "" { - log.Fatal().Msg("ACCOUNT_ADDR env var is required") + logger.Error("ACCOUNT_ADDR env var is required") + os.Exit(1) } if !common.IsHexAddress(transferAddressString) { - log.Fatal().Msg("ACCOUNT_ADDR is not a valid address") + logger.Error("ACCOUNT_ADDR is not a valid address") + os.Exit(1) } transferAddr := common.HexToAddress(transferAddressString) @@ -68,21 +76,30 @@ func main() { // Construct two eth clients and cancel all pending txes on both chains l1Client, err := ethclient.Dial(l1RPCUrl) if err != nil { - log.Fatal().Err(err).Msg("failed to dial l1 rpc") + logger.Error("failed to dial l1 rpc", "error", err) + os.Exit(1) } settlementClient, err := ethclient.Dial(settlementRPCUrl) if err != nil { - log.Fatal().Err(err).Msg("failed to dial settlement rpc") + logger.Error("failed to dial settlement rpc", "error", err) + os.Exit(1) + } + if err := shared.NewETHClient(logger.With("component", "l1_eth_client"), l1Client).CancelPendingTxes(ctx, privateKey); err != nil { + logger.Error("failed to cancel pending L1 transactions") + os.Exit(1) + } + if err := shared.NewETHClient(logger.With("component", "settlement_eth_client"), settlementClient).CancelPendingTxes(ctx, privateKey); err != nil { + logger.Error("failed to cancel pending settlement transactions") + os.Exit(1) } - shared.CancelPendingTxes(ctx, privateKey, l1Client) - shared.CancelPendingTxes(ctx, privateKey, settlementClient) for { // Generate a random amount of wei in [0.01, 10] ETH maxWei := new(big.Int).Mul(big.NewInt(10), big.NewInt(params.Ether)) randWeiValue, err := rand.Int(rand.Reader, maxWei) if err != nil { - log.Fatal().Err(err).Msg("Failed to generate random value") + logger.Error("failed to generate random value", "error", err) + os.Exit(1) } if randWeiValue.Cmp(big.NewInt(params.Ether/100)) < 0 { // Enforce minimum value of 0.01 ETH @@ -93,6 +110,7 @@ func main() { // Create and start the transfer to the settlement chain tSettlement, err := transfer.NewTransferToSettlement( + logger.With("component", "settlement_transfer"), randWeiValue, transferAddr, privateKey, @@ -102,25 +120,28 @@ func main() { settlementContractAddr, ) if err != nil { - postMetricToDatadog(ctx, apiClient, "bridging.failure", 0, - []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "17864"}, - ) + tags := []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "17864"} + if err := postMetricToDatadog(ctx, apiClient, "bridging.failure", 0, tags); err != nil { + logger.Error("failed to post metric", "error", err) + } time.Sleep(time.Minute) continue } startTime := time.Now() err = tSettlement.Start(ctx) if err != nil { - postMetricToDatadog(ctx, apiClient, "bridging.failure", time.Since(startTime).Seconds(), - []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "17864"}, - ) + tags := []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "17864"} + if err := postMetricToDatadog(ctx, apiClient, "bridging.failure", time.Since(startTime).Seconds(), tags); err != nil { + logger.Error("failed to post metric", "error", err) + } time.Sleep(time.Minute) continue } completionTimeSec := time.Since(startTime).Seconds() - postMetricToDatadog(ctx, apiClient, "bridging.success", completionTimeSec, - []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "17864"}, - ) + tags := []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "17864"} + if err := postMetricToDatadog(ctx, apiClient, "bridging.success", completionTimeSec, tags); err != nil { + logger.Error("failed to post metric", "error", err) + } // Sleep for random interval between 0 and 5 seconds time.Sleep(time.Duration(mathrand.Intn(6)) * time.Second) @@ -131,6 +152,7 @@ func main() { // Create and start the transfer back to L1 with the same amount tL1, err := transfer.NewTransferToL1( + logger.With("component", "l1_transfer"), amountBack, transferAddr, privateKey, @@ -140,32 +162,35 @@ func main() { settlementContractAddr, ) if err != nil { - postMetricToDatadog(ctx, apiClient, "bridging.failure", 0, - []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "39999"}, - ) + tags := []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "39999"} + if err := postMetricToDatadog(ctx, apiClient, "bridging.failure", 0, tags); err != nil { + logger.Error("failed to post metric", "error", err) + } time.Sleep(time.Minute) continue } startTime = time.Now() err = tL1.Start(ctx) if err != nil { - postMetricToDatadog(ctx, apiClient, "bridging.failure", time.Since(startTime).Seconds(), - []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "39999"}, - ) + tags := []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "39999"} + if err := postMetricToDatadog(ctx, apiClient, "bridging.failure", time.Since(startTime).Seconds(), tags); err != nil { + logger.Error("failed to post metric", "error", err) + } time.Sleep(time.Minute) continue } completionTimeSec = time.Since(startTime).Seconds() - postMetricToDatadog(ctx, apiClient, "bridging.success", completionTimeSec, - []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "39999"}, - ) + tags = []string{"environment:bridge_test", "account_addr:" + transferAddressString, "to_chain_id:" + "39999"} + if err := postMetricToDatadog(ctx, apiClient, "bridging.success", completionTimeSec, tags); err != nil { + logger.Error("failed to post metric", "error", err) + } // Sleep for random interval between 0 and 5 seconds time.Sleep(time.Duration(mathrand.Intn(6)) * time.Second) } } -func postMetricToDatadog(ctx context.Context, client *datadog.APIClient, metricName string, value float64, tags []string) { +func postMetricToDatadog(ctx context.Context, client *datadog.APIClient, metricName string, value float64, tags []string) error { now := time.Now().Unix() point := datadog.MetricPoint{ Timestamp: datadog.PtrInt64(now), @@ -181,8 +206,5 @@ func postMetricToDatadog(ctx context.Context, client *datadog.APIClient, metricN Series: []datadog.MetricSeries{series}, } _, _, err := client.MetricsApi.SubmitMetrics(ctx, payload) - if err != nil { - log.Fatal().Err(err).Msg("Failed to post metric to Datadog") - } - log.Debug().Msgf("Posted metric %s with value %f and tags %v", metricName, value, tags) + return err } diff --git a/standard/bridge-v1/example_config/relayer_config.yml b/standard/bridge-v1/example_config/relayer_config.yml index 6097003..292f7a5 100644 --- a/standard/bridge-v1/example_config/relayer_config.yml +++ b/standard/bridge-v1/example_config/relayer_config.yml @@ -1,6 +1,6 @@ -priv_key_file_path: "example_config/relayer_key" -log_level: "debug" -l1_rpc_url: "http://l1-bootnode:8545" -settlement_rpc_url: "http://sl-bootnode:8545" -l1_contract_addr: "0x1a18dfEc4f2B66207b1Ad30aB5c7A0d62Ef4A40b" -settlement_contract_addr: "0xc1f93bE11D7472c9B9a4d87B41dD0a491F1fbc75" +priv-key-file: "example_config/relayer_key" +log-level: "debug" +l1-rpc-url: "http://l1-bootnode:8545" +settlement-rpc-url: "http://sl-bootnode:8545" +l1-contract-addr: "0x1a18dfEc4f2B66207b1Ad30aB5c7A0d62Ef4A40b" +settlement-contract-addr: "0xc1f93bE11D7472c9B9a4d87B41dD0a491F1fbc75" diff --git a/standard/bridge-v1/go.mod b/standard/bridge-v1/go.mod index dd27ed8..11eb021 100644 --- a/standard/bridge-v1/go.mod +++ b/standard/bridge-v1/go.mod @@ -6,13 +6,12 @@ require ( github.com/DataDog/datadog-api-client-go v1.16.0 github.com/ethereum/go-ethereum v1.13.5 github.com/primevprotocol/contracts-abi v0.0.0-20240204013900-514e33ba7098 - github.com/rs/zerolog v1.31.0 github.com/urfave/cli/v2 v2.27.1 - golang.org/x/crypto v0.14.0 - gopkg.in/yaml.v2 v2.4.0 + golang.org/x/crypto v0.21.0 ) require ( + github.com/BurntSushi/toml v1.3.2 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect @@ -35,7 +34,6 @@ require ( github.com/google/uuid v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/uint256 v1.2.3 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect @@ -51,13 +49,14 @@ require ( github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/time v0.4.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/standard/bridge-v1/go.sum b/standard/bridge-v1/go.sum index 2d52c79..587eef4 100644 --- a/standard/bridge-v1/go.sum +++ b/standard/bridge-v1/go.sum @@ -1,6 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DataDog/datadog-api-client-go v1.16.0 h1:5jOZv1m98criCvYTa3qpW8Hzv301nbZX3K9yJtwGyWY= @@ -53,7 +55,6 @@ github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5U github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -108,7 +109,6 @@ github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -222,7 +222,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= @@ -283,9 +282,6 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -356,8 +352,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= @@ -389,8 +385,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= @@ -425,14 +421,12 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -440,8 +434,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/standard/bridge-v1/pkg/relayer/listener.go b/standard/bridge-v1/pkg/relayer/listener.go index 590bf09..25472e9 100644 --- a/standard/bridge-v1/pkg/relayer/listener.go +++ b/standard/bridge-v1/pkg/relayer/listener.go @@ -3,15 +3,17 @@ package relayer import ( "context" "fmt" - "standard-bridge/pkg/shared" + "log/slog" "time" + "standard-bridge/pkg/shared" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/ethclient" - "github.com/rs/zerolog/log" ) type Listener struct { + logger *slog.Logger rawClient *ethclient.Client gatewayFilterer shared.GatewayFilterer sync bool @@ -21,44 +23,46 @@ type Listener struct { } func NewListener( + logger *slog.Logger, client *ethclient.Client, gatewayFilterer shared.GatewayFilterer, sync bool, ) *Listener { return &Listener{ + logger: logger, rawClient: client, gatewayFilterer: gatewayFilterer, sync: true, } } -func (listener *Listener) Start(ctx context.Context) ( +func (l *Listener) Start(ctx context.Context) ( <-chan struct{}, <-chan shared.TransferInitiatedEvent, error, ) { - chainID, err := listener.rawClient.ChainID(ctx) + chainID, err := l.rawClient.ChainID(ctx) if err != nil { return nil, nil, fmt.Errorf("failed to get chain id: %w", err) } switch chainID.String() { case "39999": - log.Info().Msg("Starting listener for local_l1") - listener.chain = shared.L1 + l.logger.Info("starting listener for local_l1") + l.chain = shared.L1 case "17000": - log.Info().Msg("Starting listener for Holesky L1") - listener.chain = shared.L1 + l.logger.Info("starting listener for Holesky L1") + l.chain = shared.L1 case "17864": - log.Info().Msg("Starting listener for mev-commit chain (settlement)") - listener.chain = shared.Settlement + l.logger.Info("starting listener for mev-commit chain (settlement)") + l.chain = shared.Settlement default: return nil, nil, fmt.Errorf("unsupported chain id: %s", chainID.String()) } - listener.DoneChan = make(chan struct{}) - listener.EventChan = make(chan shared.TransferInitiatedEvent, 10) // Buffer up to 10 events + l.DoneChan = make(chan struct{}) + l.EventChan = make(chan shared.TransferInitiatedEvent, 10) // Buffer up to 10 events go func() { - defer close(listener.DoneChan) - defer close(listener.EventChan) + defer close(l.DoneChan) + defer close(l.EventChan) ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() @@ -66,64 +70,75 @@ func (listener *Listener) Start(ctx context.Context) ( // Blocks up to this value have been handled blockNumHandled := uint64(0) - if listener.sync { - blockNumHandled, err = listener.obtainFinalizedBlockNum(ctx) + if l.sync { + blockNumHandled, err = l.obtainFinalizedBlockNum(ctx) if err != nil { - log.Fatal().Err(err).Msg("failed to obtain block number during sync") + l.logger.Error("failed to obtain block number during sync", "error", err) + return } // Most nodes limit query ranges so we fetch in 40k increments - events, err := listener.obtainTransferInitiatedEventsInBatches( + events, err := l.obtainTransferInitiatedEventsInBatches( ctx, 0, blockNumHandled) if err != nil { - log.Fatal().Err(err).Msg("failed to fetch transfer initiated events during sync") + l.logger.Error("failed to fetch transfer initiated events during sync", "error", err) + return } for _, event := range events { - log.Info().Msgf("Transfer initiated event seen by listener during sync: %+v", event) - listener.EventChan <- event + l.logger.Info("transfer initiated event seen by listener during sync", "event", event) + l.EventChan <- event } } for { select { case <-ctx.Done(): - log.Info().Msgf("Listener for %s shutting down", listener.chain) + l.logger.Info("listener shutting down", "chain", l.chain) return case <-ticker.C: } - currentBlockNum, err := listener.obtainFinalizedBlockNum(ctx) + currentBlockNum, err := l.obtainFinalizedBlockNum(ctx) if err != nil { // TODO: Secondary url if rpc fails. For now just start over... - log.Error().Err(err).Msg("failed to obtain block number") - log.Warn().Msg("Listener restarting from block 0...") + l.logger.Error("failed to obtain block number", "error", err) + l.logger.Warn("listener restarting from block 0...") blockNumHandled = 0 continue } if blockNumHandled < currentBlockNum { - events, err := listener.obtainTransferInitiatedEventsInBatches(ctx, blockNumHandled+1, currentBlockNum) + events, err := l.obtainTransferInitiatedEventsInBatches(ctx, blockNumHandled+1, currentBlockNum) if err != nil { // TODO: Secondary url if rpc fails. For now just start over... - log.Error().Err(err).Msgf("failed to query transfer initiated events from block %d to %d on %s", - blockNumHandled+1, currentBlockNum, listener.chain.String()) - log.Warn().Msg("Listener restarting from block 0...") + l.logger.Error( + "failed to query transfer initiated events", + "from_block", blockNumHandled+1, + "to_block", currentBlockNum, + "chain", l.chain, + ) + l.logger.Warn("listener restarting from block 0...") blockNumHandled = 0 continue } - log.Debug().Msgf("Fetched %d events from block %d to %d on %s", - len(events), blockNumHandled+1, currentBlockNum, listener.chain.String()) + l.logger.Debug( + "fetched events", + "event_count", len(events), + "from_block", blockNumHandled+1, + "to_block", currentBlockNum, + "chain", l.chain, + ) for _, event := range events { - log.Info().Msgf("Transfer initiated event seen by listener: %+v", event) - listener.EventChan <- event + l.logger.Info("transfer initiated event seen by listener", "event", event) + l.EventChan <- event } blockNumHandled = currentBlockNum } } }() - return listener.DoneChan, listener.EventChan, nil + return l.DoneChan, l.EventChan, nil } -func (listener *Listener) obtainFinalizedBlockNum(ctx context.Context) (uint64, error) { - blockNum, err := listener.rawClient.BlockNumber(ctx) +func (l *Listener) obtainFinalizedBlockNum(ctx context.Context) (uint64, error) { + blockNum, err := l.rawClient.BlockNumber(ctx) if err != nil { return 0, fmt.Errorf("failed to obtain block number: %w", err) } @@ -135,7 +150,7 @@ func (listener *Listener) obtainFinalizedBlockNum(ctx context.Context) (uint64, return blockNum - 2*epochBlocks, nil } -func (listener *Listener) obtainTransferInitiatedEventsInBatches( +func (l *Listener) obtainTransferInitiatedEventsInBatches( ctx context.Context, startBlock, endBlock uint64, @@ -148,7 +163,7 @@ func (listener *Listener) obtainTransferInitiatedEventsInBatches( end = endBlock } opts := &bind.FilterOpts{Start: start, End: &end, Context: ctx} - events, err := listener.gatewayFilterer.ObtainTransferInitiatedEvents(opts) + events, err := l.gatewayFilterer.ObtainTransferInitiatedEvents(opts) if err != nil { return nil, fmt.Errorf("failed to obtain transfer initiated events: %w", err) } diff --git a/standard/bridge-v1/pkg/relayer/relayer.go b/standard/bridge-v1/pkg/relayer/relayer.go index 90da6fd..c85312f 100644 --- a/standard/bridge-v1/pkg/relayer/relayer.go +++ b/standard/bridge-v1/pkg/relayer/relayer.go @@ -5,19 +5,23 @@ import ( "crypto/ecdsa" "database/sql" "errors" - "standard-bridge/pkg/shared" + "fmt" + "log/slog" "time" + "standard-bridge/pkg/shared" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" l1g "github.com/primevprotocol/contracts-abi/clients/L1Gateway" sg "github.com/primevprotocol/contracts-abi/clients/SettlementGateway" - "github.com/rs/zerolog/log" "golang.org/x/crypto/sha3" ) type Options struct { + Ctx context.Context + Logger *slog.Logger PrivateKey *ecdsa.PrivateKey SettlementRPCUrl string L1RPCUrl string @@ -26,72 +30,80 @@ type Options struct { } type Relayer struct { + logger *slog.Logger // Closes ctx's Done channel and waits for all goroutines to close. waitOnCloseRoutines func() db *sql.DB } -func NewRelayer(opts *Options) *Relayer { - r := &Relayer{} +func NewRelayer(opts *Options) (r *Relayer, err error) { + r = &Relayer{logger: opts.Logger} pubKey := &opts.PrivateKey.PublicKey pubKeyBytes := crypto.FromECDSAPub(pubKey) hash := sha3.NewLegacyKeccak256() hash.Write(pubKeyBytes[1:]) address := hash.Sum(nil)[12:] - valAddr := common.BytesToAddress(address) - log.Info().Msg("Relayer signing address: " + valAddr.Hex()) + r.logger.Info("relayer signing address", "address", common.BytesToAddress(address).Hex()) - l1Client, err := ethclient.Dial(opts.L1RPCUrl) + l1Client, err := ethclient.DialContext(opts.Ctx, opts.L1RPCUrl) if err != nil { - log.Fatal().Err(err).Msg("failed to dial l1 rpc") + return nil, fmt.Errorf("failed to dial l1 rpc: %w", err) } - l1ChainID, err := l1Client.ChainID(context.Background()) + l1ChainID, err := l1Client.ChainID(opts.Ctx) if err != nil { - log.Fatal().Err(err).Msg("failed to get l1 chain id") + return nil, fmt.Errorf("failed to get l1 chain id: %w", err) } - log.Info().Msg("L1 chain id: " + l1ChainID.String()) + r.logger.Info("L1 chain id", "chain_id", l1ChainID) settlementClient, err := ethclient.Dial(opts.SettlementRPCUrl) if err != nil { - log.Fatal().Err(err).Msg("failed to dial settlement rpc") + return nil, fmt.Errorf("failed to dial settlement rpc: %w", err) } - settlementChainID, err := settlementClient.ChainID(context.Background()) + settlementChainID, err := settlementClient.ChainID(opts.Ctx) if err != nil { - log.Fatal().Err(err).Msg("failed to dial settlement rpc") + return nil, fmt.Errorf("failed to dial settlement rpc: %w", err) } - log.Info().Msg("Settlement chain id: " + settlementChainID.String()) - - ctx, cancel := context.WithCancel(context.Background()) + r.logger.Info("settlement chain id", "chain_id", settlementChainID) sFilterer, err := shared.NewSettlementFilterer(opts.SettlementContractAddr, settlementClient) if err != nil { - log.Fatal().Err(err).Msg("failed to create settlement filterer") + return nil, fmt.Errorf("failed to create settlement filterer: %w", err) } - sListener := NewListener(settlementClient, sFilterer, false) + + ctx, cancel := context.WithCancel(opts.Ctx) + defer func() { + if err != nil { + cancel() + } + }() + + sListener := NewListener(r.logger.With("component", "settlement_listener"), settlementClient, sFilterer, false) sListenerClosed, settlementEventChan, err := sListener.Start(ctx) if err != nil { - log.Fatal().Err(err).Msg("failed to start settlement listener") + return nil, fmt.Errorf("failed to start settlement listener: %w", err) } l1Filterer, err := shared.NewL1Filterer(opts.L1ContractAddr, l1Client) if err != nil { - log.Fatal().Err(err).Msg("failed to create l1 filterer") + return nil, fmt.Errorf("failed to create l1 filterer: %w", err) } - l1Listener := NewListener(l1Client, l1Filterer, true) + + l1Listener := NewListener(r.logger.With("component", "l1_listener"), l1Client, l1Filterer, true) l1ListenerClosed, l1EventChan, err := l1Listener.Start(ctx) if err != nil { - log.Fatal().Err(err).Msg("failed to start l1 listener") + return nil, fmt.Errorf("failed to start l1 listener: %w", err) } st, err := sg.NewSettlementgatewayTransactor(opts.SettlementContractAddr, settlementClient) if err != nil { - log.Fatal().Err(err).Msg("failed to create settlement gateway transactor") + return nil, fmt.Errorf("failed to create settlement gateway transactor: %w", err) } settlementTransactor := NewTransactor( + r.logger.With("component", "settlement_transactor"), opts.PrivateKey, opts.SettlementContractAddr, settlementClient, @@ -99,13 +111,17 @@ func NewRelayer(opts *Options) *Relayer { sFilterer, l1EventChan, // L1 transfer initiations result in settlement finalizations ) - stClosed := settlementTransactor.Start(ctx) + stClosed, err := settlementTransactor.Start(ctx) + if err != nil { + return nil, err + } l1t, err := l1g.NewL1gatewayTransactor(opts.L1ContractAddr, l1Client) if err != nil { - log.Fatal().Err(err).Msg("failed to create l1 gateway transactor") + return nil, fmt.Errorf("failed to create l1 gateway transactor: %w", err) } l1Transactor := NewTransactor( + r.logger.With("component", "l1_transactor"), opts.PrivateKey, opts.L1ContractAddr, l1Client, @@ -113,7 +129,10 @@ func NewRelayer(opts *Options) *Relayer { l1Filterer, settlementEventChan, // Settlement transfer initiations result in L1 finalizations ) - l1tClosed := l1Transactor.Start(ctx) + l1tClosed, err := l1Transactor.Start(ctx) + if err != nil { + return nil, err + } r.waitOnCloseRoutines = func() { // Close ctx's Done channel @@ -129,12 +148,12 @@ func NewRelayer(opts *Options) *Relayer { }() <-allClosed } - return r + return r, nil } // TryCloseAll attempts to close all workers and the database connection. func (r *Relayer) TryCloseAll() (err error) { - log.Debug().Msg("closing all workers and db connection") + r.logger.Debug("closing all workers and db connection") defer func() { if r.db == nil { return @@ -152,11 +171,11 @@ func (r *Relayer) TryCloseAll() (err error) { select { case <-workersClosed: - log.Info().Msg("all workers closed") + r.logger.Info("all workers closed") return nil case <-time.After(10 * time.Second): msg := "failed to close all workers in 10 sec" - log.Error().Msg(msg) + r.logger.Error(msg) return errors.New(msg) } } diff --git a/standard/bridge-v1/pkg/relayer/transactor.go b/standard/bridge-v1/pkg/relayer/transactor.go index dab6604..f980c47 100644 --- a/standard/bridge-v1/pkg/relayer/transactor.go +++ b/standard/bridge-v1/pkg/relayer/transactor.go @@ -4,19 +4,21 @@ import ( "context" "crypto/ecdsa" "fmt" + "log/slog" "math/big" + "standard-bridge/pkg/shared" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" - "github.com/rs/zerolog/log" ) type Transactor struct { + logger *slog.Logger privateKey *ecdsa.PrivateKey - rawClient *ethclient.Client + rawClient *shared.ETHClient gatewayTransactor shared.GatewayTransactor gatewayFilterer shared.GatewayFilterer chainID *big.Int @@ -35,6 +37,7 @@ func (m mostRecentFinalized) String() string { } func NewTransactor( + logger *slog.Logger, pk *ecdsa.PrivateKey, gatewayAddr common.Address, ethClient *ethclient.Client, @@ -43,8 +46,12 @@ func NewTransactor( eventChan <-chan shared.TransferInitiatedEvent, ) *Transactor { return &Transactor{ - privateKey: pk, - rawClient: ethClient, + logger: logger, + privateKey: pk, + rawClient: shared.NewETHClient( + logger.With("component", "eth_client"), + ethClient, + ), gatewayTransactor: gatewayTransactor, gatewayFilterer: gatewayFilterer, eventChan: eventChan, @@ -55,27 +62,24 @@ func NewTransactor( } } -func (t *Transactor) Start( - ctx context.Context, -) <-chan struct{} { - +func (t *Transactor) Start(ctx context.Context) (<-chan struct{}, error) { var err error t.chainID, err = t.rawClient.ChainID(ctx) if err != nil { - log.Fatal().Err(err).Msg("failed to get chain id") + return nil, fmt.Errorf("failed to get chain id: %w", err) } switch t.chainID.String() { case "39999": - log.Info().Msg("Starting transactor for local_l1") + t.logger.Info("starting transactor for local_l1") t.chain = shared.L1 case "17000": - log.Info().Msg("Starting transactor for Holesky L1") + t.logger.Info("starting transactor for Holesky L1") t.chain = shared.L1 case "17864": - log.Info().Msg("Starting transactor for mev-commit chain (settlement)") + t.logger.Info("starting transactor for mev-commit chain (settlement)") t.chain = shared.Settlement default: - log.Fatal().Msgf("Unsupported chain id: %s", t.chainID.String()) + return nil, fmt.Errorf("unsupported chain id: %s", t.chainID) } doneChan := make(chan struct{}) @@ -83,22 +87,29 @@ func (t *Transactor) Start( go func() { defer close(doneChan) - shared.CancelPendingTxes(ctx, t.privateKey, t.rawClient) + if err := t.rawClient.CancelPendingTxes(ctx, t.privateKey); err != nil { + t.logger.Error("failed to cancel pending transactions", "error", err) + } for event := range t.eventChan { - log.Debug().Msgf("Received signal from listener to submit transfer finalization tx on dest chain: %s. "+ - "Where Src chain: %s, recipient: %s, amount: %d, srcTransferIdx: %d", - t.chain, event.Chain.String(), event.Recipient, event.Amount, event.TransferIdx) - opts, err := shared.CreateTransactOpts(ctx, t.privateKey, t.chainID, t.rawClient) + t.logger.Debug( + "received signal from listener to submit transfer finalization tx", + "dst_chain", t.chain, + "src_chain", event.Chain, + "recipient", event.Recipient, + "amount", event.Amount, + "src_transfer_idx", event.TransferIdx, + ) + opts, err := t.rawClient.CreateTransactOpts(ctx, t.privateKey, t.chainID) if err != nil { - log.Err(err).Msg("failed to create transact opts for transfer finalization tx.") - log.Warn().Msgf("skipping transfer finalization tx for src transfer idx: %d", event.TransferIdx) + t.logger.Error("failed to create transact opts for transfer finalization tx", "error", err) + t.logger.Warn("skipping transfer finalization tx", "src_transfer_idx", event.TransferIdx) continue } finalized, err := t.transferAlreadyFinalized(ctx, event.TransferIdx) if err != nil { - log.Err(err).Msg("failed to check if transfer already finalized.") - log.Warn().Msgf("skipping transfer finalization tx for src transfer idx: %d", event.TransferIdx) + t.logger.Error("failed to check if transfer already finalized") + t.logger.Warn("skipping transfer finalization tx", "src_transfer_idx", event.TransferIdx) continue } if finalized { @@ -106,8 +117,8 @@ func (t *Transactor) Start( } receipt, err := t.sendFinalizeTransfer(ctx, opts, event) if err != nil { - log.Err(err).Msg("failed to send transfer finalization tx.") - log.Warn().Msgf("skipping transfer finalization tx for src transfer idx: %d", event.TransferIdx) + t.logger.Error("failed to send transfer finalization tx") + t.logger.Warn("skipping transfer finalization tx", "src_transfer_idx", event.TransferIdx) continue } // Event should be obtainable to update cache @@ -115,17 +126,17 @@ func (t *Transactor) Start( filterOpts := &bind.FilterOpts{Start: eventBlock, End: &eventBlock, Context: ctx} _, found, err := t.obtainTransferFinalizedAndUpdateCache(ctx, filterOpts, event.TransferIdx) if err != nil { - log.Err(err).Msg("failed to obtain transfer finalized event after sending tx") + t.logger.Error("failed to obtain transfer finalized event after sending tx") continue } if !found { - log.Warn().Msg("transfer finalized event not found after sending tx") + t.logger.Warn("transfer finalized event not found after sending tx") continue } } - log.Info().Msgf("Chan to transactor was closed, transactor for chain %s is exiting", t.chain) + t.logger.Info("channel to transactor was closed, transactor is exiting", "chain", t.chain) }() - return doneChan + return doneChan, nil } func (t *Transactor) transferAlreadyFinalized( @@ -157,8 +168,13 @@ func (t *Transactor) transferAlreadyFinalized( return false, fmt.Errorf("failed to obtain transfer finalized event: %w", err) } if found { - log.Debug().Msgf("Transfer already finalized on dest chain: %s, recipient: %s, amount: %d, srcTransferIdx: %d", - t.chain.String(), event.Recipient, event.Amount, event.CounterpartyIdx) + t.logger.Debug( + "transfer already finalized", + "dst_chain", t.chain, + "recipient", event.Recipient, + "amount", event.Amount, + "src_transfer_idx", event.CounterpartyIdx, + ) return true, nil } } @@ -180,17 +196,22 @@ func (t *Transactor) sendFinalizeTransfer( if err != nil { return nil, fmt.Errorf("failed to send finalize transfer tx: %w", err) } - log.Debug().Msgf("Transfer finalization tx sent, hash: %s, destChain: %s, recipient: %s, amount: %d, srcTransferIdx: %d", - tx.Hash().Hex(), t.chain.String(), event.Recipient, event.Amount, event.TransferIdx) + t.logger.Debug("transfer finalization tx sent, hash: %s, destChain: %s, recipient: %s, amount: %d, srcTransferIdx: %d", + "hash", tx.Hash().Hex(), + "dest_chain", t.chain, + "recipient", event.Recipient, + "amount", event.Amount, + "src_transfer_idx", event.TransferIdx, + ) return tx, nil } - receipt, err := shared.WaitMinedWithRetry(ctx, t.rawClient, opts, submitFinalizeTransfer) + receipt, err := t.rawClient.WaitMinedWithRetry(ctx, opts, submitFinalizeTransfer) if err != nil { return nil, fmt.Errorf("failed to wait for finalize transfer tx to be mined: %w", err) } includedInBlock := receipt.BlockNumber.Uint64() - log.Info().Msgf("FinalizeTransfer tx included in block: %d on: %v", includedInBlock, t.chain.String()) + t.logger.Info("finalizeTransfer tx included in block", "block_number", includedInBlock, "chain", t.chain) return receipt, nil } @@ -206,7 +227,7 @@ func (t *Transactor) obtainTransferFinalizedAndUpdateCache( } if found { t.mostRecentFinalized = mostRecentFinalized{event, *opts} - log.Debug().Msgf("mostRecentFinalized cache updated: %+v", t.mostRecentFinalized) + t.logger.Debug("mostRecentFinalized cache updated", "new", fmt.Sprintf("%+v", t.mostRecentFinalized)) } return event, found, nil } diff --git a/standard/bridge-v1/pkg/shared/tx.go b/standard/bridge-v1/pkg/shared/client.go similarity index 65% rename from standard/bridge-v1/pkg/shared/tx.go rename to standard/bridge-v1/pkg/shared/client.go index c8cf7ff..840a285 100644 --- a/standard/bridge-v1/pkg/shared/tx.go +++ b/standard/bridge-v1/pkg/shared/client.go @@ -4,6 +4,7 @@ import ( "context" "crypto/ecdsa" "fmt" + "log/slog" "math/big" "strings" "time" @@ -12,14 +13,29 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" - "github.com/rs/zerolog/log" ) -func CreateTransactOpts( +type ETHClient struct { + logger *slog.Logger + client *ethclient.Client +} + +func NewETHClient(logger *slog.Logger, client *ethclient.Client) *ETHClient { + return ÐClient{logger: logger, client: client} +} + +func (c *ETHClient) ChainID(ctx context.Context) (*big.Int, error) { + return c.client.ChainID(ctx) +} + +func (c *ETHClient) BlockNumber(ctx context.Context) (uint64, error) { + return c.client.BlockNumber(ctx) +} + +func (c *ETHClient) CreateTransactOpts( ctx context.Context, privateKey *ecdsa.PrivateKey, srcChainID *big.Int, - srcClient *ethclient.Client, ) (*bind.TransactOpts, error) { auth, err := bind.NewKeyedTransactorWithChainID(privateKey, srcChainID) if err != nil { @@ -27,13 +43,13 @@ func CreateTransactOpts( } fromAddress := auth.From - nonce, err := srcClient.PendingNonceAt(ctx, fromAddress) + nonce, err := c.client.PendingNonceAt(ctx, fromAddress) if err != nil { return nil, fmt.Errorf("failed to get pending nonce: %w", err) } auth.Nonce = big.NewInt(int64(nonce)) - gasTip, gasPrice, err := SuggestGasTipCapAndPrice(ctx, srcClient) + gasTip, gasPrice, err := c.SuggestGasTipCapAndPrice(ctx) if err != nil { return nil, fmt.Errorf("failed to suggest gas tip cap and price: %w", err) } @@ -44,14 +60,14 @@ func CreateTransactOpts( return auth, nil } -func SuggestGasTipCapAndPrice(ctx context.Context, srcClient *ethclient.Client) (*big.Int, *big.Int, error) { +func (c *ETHClient) SuggestGasTipCapAndPrice(ctx context.Context) (*big.Int, *big.Int, error) { // Returns priority fee per gas - gasTip, err := srcClient.SuggestGasTipCap(ctx) + gasTip, err := c.client.SuggestGasTipCap(ctx) if err != nil { return nil, nil, fmt.Errorf("failed to get gas tip cap: %w", err) } // Returns priority fee per gas + base fee per gas - gasPrice, err := srcClient.SuggestGasPrice(ctx) + gasPrice, err := c.client.SuggestGasPrice(ctx) if err != nil { return nil, nil, fmt.Errorf("failed to get gas price: %w", err) } @@ -59,15 +75,18 @@ func SuggestGasTipCapAndPrice(ctx context.Context, srcClient *ethclient.Client) } // TODO: Unit tests -func BoostTipForTransactOpts( +func (c *ETHClient) BoostTipForTransactOpts( ctx context.Context, opts *bind.TransactOpts, - srcClient *ethclient.Client, ) error { - log.Debug().Msgf("Gas params for tx that was not included: Gas tip: %s wei, gas fee cap: %s wei, base fee: %s wei", - opts.GasTipCap.String(), opts.GasFeeCap.String(), new(big.Int).Sub(opts.GasFeeCap, opts.GasTipCap).String()) - - newGasTip, newFeeCap, err := SuggestGasTipCapAndPrice(ctx, srcClient) + c.logger.Debug( + "gas params for tx that were not included", + "gas_tip", opts.GasTipCap.String(), + "gas_fee_cap", opts.GasFeeCap.String(), + "base_fee", new(big.Int).Sub(opts.GasFeeCap, opts.GasTipCap).String(), + ) + + newGasTip, newFeeCap, err := c.SuggestGasTipCapAndPrice(ctx) if err != nil { return fmt.Errorf("failed to suggest gas tip cap and price: %w", err) } @@ -105,9 +124,13 @@ func BoostTipForTransactOpts( opts.GasTipCap = boostedTip opts.GasFeeCap = new(big.Int).Add(boostedBaseFee, boostedTip) - log.Debug().Msg("Tip and base fee will be boosted by 10%") - log.Debug().Msgf("Boosted gas tip cap to %s wei and gas fee cap to %s wei. Base fee: %s wei", - opts.GasTipCap.String(), opts.GasFeeCap.String(), boostedBaseFee.String()) + c.logger.Debug("tip and base fee will be boosted by 10%") + c.logger.Debug( + "boosted gas", + "get_tip_cap", opts.GasTipCap.String(), + "gas_fee_cap", opts.GasFeeCap.String(), + "base_fee", boostedBaseFee.String(), + ) return nil } @@ -121,9 +144,8 @@ type TxSubmitFunc func( ) // TODO: Unit tests -func WaitMinedWithRetry( +func (c *ETHClient) WaitMinedWithRetry( ctx context.Context, - rawClient *ethclient.Client, opts *bind.TransactOpts, submitTx TxSubmitFunc, ) (*types.Receipt, error) { @@ -134,8 +156,8 @@ func WaitMinedWithRetry( for attempt := 0; attempt < maxRetries; attempt++ { if attempt > 0 { - log.Info().Msgf("Transaction not included within 60 seconds, boosting gas tip by 10%% for attempt %d", attempt) - if err := BoostTipForTransactOpts(ctx, opts, rawClient); err != nil { + c.logger.Info("transaction not included within 60 seconds, boosting gas tip by 10%", "attempt", attempt) + if err := c.BoostTipForTransactOpts(ctx, opts); err != nil { return nil, fmt.Errorf("failed to boost gas tip for attempt %d: %w", attempt, err) } } @@ -143,12 +165,9 @@ func WaitMinedWithRetry( tx, err = submitTx(ctx, opts) if err != nil { if strings.Contains(err.Error(), "replacement transaction underpriced") || strings.Contains(err.Error(), "already known") { - log.Warn().Err(err).Msgf("Tx submission failed on attempt %d: %s", attempt, err) + c.logger.Error("tx submission failed", "attempt", attempt, "error", err) continue } - if strings.Contains(err.Error(), "nonce too low") { - log.Warn().Msg("Likely the previous tx attempt was included at the last moment. Relayer should continue as normal.") - } return nil, fmt.Errorf("tx submission failed on attempt %d: %w", attempt, err) } @@ -157,7 +176,7 @@ func WaitMinedWithRetry( errChan := make(chan error) go func() { - receipt, err := bind.WaitMined(timeoutCtx, rawClient, tx) + receipt, err := bind.WaitMined(timeoutCtx, c.client, tx) if err != nil { errChan <- err return @@ -183,20 +202,23 @@ func WaitMinedWithRetry( return nil, fmt.Errorf("unexpected error: control flow should not reach end of WaitMinedWithRetry") } -func CancelPendingTxes(ctx context.Context, privateKey *ecdsa.PrivateKey, rawClient *ethclient.Client) error { - cancelAllPendingTransactions(ctx, privateKey, rawClient) +func (c *ETHClient) CancelPendingTxes(ctx context.Context, privateKey *ecdsa.PrivateKey) error { + if err := c.cancelAllPendingTransactions(ctx, privateKey); err != nil { + return err + } + idx := 0 timeoutSec := 60 for { if idx >= timeoutSec { return fmt.Errorf("timeout: failed to cancel all pending transactions") } - exist, err := PendingTransactionsExist(ctx, privateKey, rawClient) + exist, err := c.PendingTransactionsExist(ctx, privateKey) if err != nil { return fmt.Errorf("failed to check pending transactions: %w", err) } if !exist { - log.Info().Msg("All pending transactions for signing account have been cancelled") + c.logger.Info("all pending transactions for signing account have been cancelled") return nil } time.Sleep(1 * time.Second) @@ -205,38 +227,37 @@ func CancelPendingTxes(ctx context.Context, privateKey *ecdsa.PrivateKey, rawCli } // TODO: Use WaitMinedWithRetry -func cancelAllPendingTransactions( +func (c *ETHClient) cancelAllPendingTransactions( ctx context.Context, privateKey *ecdsa.PrivateKey, - rawClient *ethclient.Client, ) error { - chainID, err := rawClient.ChainID(ctx) + chainID, err := c.ChainID(ctx) if err != nil { return fmt.Errorf("failed to get chain id: %w", err) } fromAddress := crypto.PubkeyToAddress(privateKey.PublicKey) - currentNonce, err := rawClient.PendingNonceAt(ctx, fromAddress) + currentNonce, err := c.client.PendingNonceAt(ctx, fromAddress) if err != nil { return fmt.Errorf("failed to get current pending nonce: %w", err) } - log.Debug().Msgf("Current pending nonce: %d", currentNonce) + c.logger.Debug("current pending nonce", "nonce", currentNonce) - latestNonce, err := rawClient.NonceAt(ctx, fromAddress, nil) + latestNonce, err := c.client.NonceAt(ctx, fromAddress, nil) if err != nil { return fmt.Errorf("failed to get latest nonce: %w", err) } - log.Debug().Msgf("Latest nonce: %d", latestNonce) + c.logger.Debug("latest nonce", "nonce", latestNonce) if currentNonce <= latestNonce { - log.Info().Msg("No pending transactions to cancel") + c.logger.Info("no pending transactions to cancel") return nil } - suggestedGasPrice, err := rawClient.SuggestGasPrice(ctx) + suggestedGasPrice, err := c.client.SuggestGasPrice(ctx) if err != nil { return fmt.Errorf("failed to get suggested gas price: %w", err) } - log.Debug().Msgf("Suggested gas price: %s wei", suggestedGasPrice.String()) + c.logger.Debug("suggested gas price", "gas_price", suggestedGasPrice.String()) for nonce := latestNonce; nonce < currentNonce; nonce++ { gasPrice := new(big.Int).Set(suggestedGasPrice) @@ -246,7 +267,7 @@ func cancelAllPendingTransactions( increase := new(big.Int).Div(gasPrice, big.NewInt(10)) gasPrice = gasPrice.Add(gasPrice, increase) gasPrice = gasPrice.Add(gasPrice, big.NewInt(1)) - log.Debug().Msgf("Increased gas price for retry %d: %s wei", retry, gasPrice.String()) + c.logger.Debug("increased gas price for retry", "retry", retry, "gas_price", gasPrice.String()) } tx := types.NewTransaction(nonce, fromAddress, big.NewInt(0), 21000, gasPrice, nil) @@ -255,33 +276,33 @@ func cancelAllPendingTransactions( return fmt.Errorf("failed to sign cancellation transaction for nonce %d: %w", nonce, err) } - err = rawClient.SendTransaction(ctx, signedTx) + err = c.client.SendTransaction(ctx, signedTx) if err != nil { if err.Error() == "replacement transaction underpriced" { - log.Warn().Err(err).Msgf("Retry %d: underpriced transaction for nonce %d, increasing gas price", retry+1, nonce) + c.logger.Warn("underpriced transaction, increasing gas price", "retry", retry+1, "nonce", nonce, "error", err) continue // Try again with a higher gas price } if err.Error() == "already known" { - log.Warn().Err(err).Msgf("Retry %d: already known transaction for nonce %d", retry+1, nonce) + c.logger.Warn("already known transaction", "retry", retry+1, "nonce", nonce, "error", err) continue // Try again with a higher gas price } return fmt.Errorf("failed to send cancellation transaction for nonce %d: %w", nonce, err) } - log.Info().Msgf("Sent cancel transaction for nonce %d with tx hash: %s, gas price: %s wei", nonce, signedTx.Hash().Hex(), gasPrice.String()) + c.logger.Info("sent cancel transaction", "nonce", nonce, "tx_hash", signedTx.Hash().Hex(), "gas_price", gasPrice.String()) break } } return nil } -func PendingTransactionsExist(ctx context.Context, privateKey *ecdsa.PrivateKey, rawClient *ethclient.Client) (bool, error) { +func (c *ETHClient) PendingTransactionsExist(ctx context.Context, privateKey *ecdsa.PrivateKey) (bool, error) { fromAddress := crypto.PubkeyToAddress(privateKey.PublicKey) - currentNonce, err := rawClient.PendingNonceAt(ctx, fromAddress) + currentNonce, err := c.client.PendingNonceAt(ctx, fromAddress) if err != nil { return false, fmt.Errorf("failed to get current pending nonce: %w", err) } - latestNonce, err := rawClient.NonceAt(ctx, fromAddress, nil) + latestNonce, err := c.client.NonceAt(ctx, fromAddress, nil) if err != nil { return false, fmt.Errorf("failed to get latest nonce: %w", err) } diff --git a/standard/bridge-v1/pkg/transfer/transfer.go b/standard/bridge-v1/pkg/transfer/transfer.go index d1ec0e4..3cd8e7e 100644 --- a/standard/bridge-v1/pkg/transfer/transfer.go +++ b/standard/bridge-v1/pkg/transfer/transfer.go @@ -4,39 +4,42 @@ import ( "context" "crypto/ecdsa" "fmt" + "log/slog" "math" "math/big" - shared "standard-bridge/pkg/shared" "time" - gethtypes "github.com/ethereum/go-ethereum/core/types" - l1g "github.com/primevprotocol/contracts-abi/clients/L1Gateway" - sg "github.com/primevprotocol/contracts-abi/clients/SettlementGateway" + "standard-bridge/pkg/shared" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" - "github.com/rs/zerolog/log" + l1g "github.com/primevprotocol/contracts-abi/clients/L1Gateway" + sg "github.com/primevprotocol/contracts-abi/clients/SettlementGateway" "golang.org/x/crypto/sha3" ) type Transfer struct { + logger *slog.Logger + amount *big.Int destAddress common.Address privateKey *ecdsa.PrivateKey - srcClient *ethclient.Client + srcClient *shared.ETHClient srcChainID *big.Int srcTransactor shared.GatewayTransactor srcFilterer shared.GatewayFilterer - destClient *ethclient.Client + destClient *shared.ETHClient destFilterer shared.GatewayFilterer destChainID *big.Int } func NewTransferToSettlement( + logger *slog.Logger, amount *big.Int, destAddress common.Address, privateKey *ecdsa.PrivateKey, @@ -45,8 +48,8 @@ func NewTransferToSettlement( l1ContractAddr common.Address, settlementContractAddr common.Address, ) (*Transfer, error) { + t := &Transfer{logger: logger} - t := &Transfer{} commonSetup, err := t.getCommonSetup(privateKey, settlementRPCUrl, l1RPCUrl) if err != nil { return nil, err @@ -66,20 +69,28 @@ func NewTransferToSettlement( } return &Transfer{ - amount: amount, - destAddress: destAddress, - privateKey: privateKey, - srcClient: commonSetup.l1Client, + logger: logger, + amount: amount, + destAddress: destAddress, + privateKey: privateKey, + srcClient: shared.NewETHClient( + logger.With("component", "l1_eth_client"), + commonSetup.l1Client, + ), srcChainID: commonSetup.l1ChainID, srcTransactor: l1t, srcFilterer: l1f, - destClient: commonSetup.settlementClient, - destFilterer: sf, - destChainID: commonSetup.settlementChainID, + destClient: shared.NewETHClient( + logger.With("component", "settlement_eth_client"), + commonSetup.settlementClient, + ), + destFilterer: sf, + destChainID: commonSetup.settlementChainID, }, nil } func NewTransferToL1( + logger *slog.Logger, amount *big.Int, destAddress common.Address, privateKey *ecdsa.PrivateKey, @@ -88,7 +99,7 @@ func NewTransferToL1( l1ContractAddr common.Address, settlementContractAddr common.Address, ) (*Transfer, error) { - t := &Transfer{} + t := &Transfer{logger: logger} commonSetup, err := t.getCommonSetup(privateKey, settlementRPCUrl, l1RPCUrl) if err != nil { return nil, err @@ -108,16 +119,23 @@ func NewTransferToL1( } return &Transfer{ - amount: amount, - destAddress: destAddress, - privateKey: privateKey, - srcClient: commonSetup.settlementClient, + logger: logger, + amount: amount, + destAddress: destAddress, + privateKey: privateKey, + srcClient: shared.NewETHClient( + logger.With("component", "settlement_eth_client"), + commonSetup.settlementClient, + ), srcChainID: commonSetup.settlementChainID, srcTransactor: st, srcFilterer: sf, - destClient: commonSetup.l1Client, - destFilterer: l1f, - destChainID: commonSetup.l1ChainID, + destClient: shared.NewETHClient( + logger.With("component", "l1_eth_client"), + commonSetup.l1Client, + ), + destFilterer: l1f, + destChainID: commonSetup.l1ChainID, }, nil } @@ -133,14 +151,13 @@ func (t *Transfer) getCommonSetup( settlementRPCUrl string, l1RPCUrl string, ) (*commonSetup, error) { - pubKey := &privateKey.PublicKey pubKeyBytes := crypto.FromECDSAPub(pubKey) hash := sha3.NewLegacyKeccak256() hash.Write(pubKeyBytes[1:]) address := hash.Sum(nil)[12:] valAddr := common.BytesToAddress(address) - log.Info().Msg("Signing address used for InitiateTransfer tx on source chain: " + valAddr.Hex()) + t.logger.Info("signing address used for InitiateTransfer tx on source chain", "address", valAddr.Hex()) l1Client, err := ethclient.Dial(l1RPCUrl) if err != nil { @@ -150,7 +167,7 @@ func (t *Transfer) getCommonSetup( if err != nil { return nil, fmt.Errorf("failed to get l1 chain id: %s", err) } - log.Debug().Msg("L1 chain id: " + l1ChainID.String()) + t.logger.Debug("L1 chain id", "chain_id", l1ChainID) settlementClient, err := ethclient.Dial(settlementRPCUrl) if err != nil { @@ -160,7 +177,7 @@ func (t *Transfer) getCommonSetup( if err != nil { return nil, fmt.Errorf("failed to get settlement chain id: %s", err) } - log.Debug().Msg("Settlement chain id: " + settlementChainID.String()) + t.logger.Debug("settlement chain id", "chain_id", settlementChainID) return &commonSetup{ l1Client: l1Client, @@ -172,7 +189,7 @@ func (t *Transfer) getCommonSetup( func (t *Transfer) Start(ctx context.Context) error { - opts, err := shared.CreateTransactOpts(ctx, t.privateKey, t.srcChainID, t.srcClient) + opts, err := t.srcClient.CreateTransactOpts(ctx, t.privateKey, t.srcChainID) if err != nil { return fmt.Errorf("failed to get transact opts: %s", err) } @@ -200,13 +217,17 @@ func (t *Transfer) Start(ctx context.Context) error { if err != nil { return nil, fmt.Errorf("failed to initiate transfer: %s", err) } - log.Debug().Msgf("Transfer initialization tx sent, hash: %s, srcChain: %s, recipient: %s, amount: %d", - tx.Hash().Hex(), t.srcChainID.String(), t.destAddress.Hex(), t.amount) + t.logger.Debug( + "transfer initialization tx sent", + "hash", tx.Hash().Hex(), + "src_chain", t.srcChainID, + "recipient", t.destAddress.Hex(), + "amount", t.amount, + ) return tx, nil } - receipt, err := shared.WaitMinedWithRetry( - ctx, t.srcClient, opts, submitInitiateTransfer) + receipt, err := t.srcClient.WaitMinedWithRetry(ctx, opts, submitInitiateTransfer) if err != nil { return fmt.Errorf("failed to wait for initiate transfer tx to be mined: %s", err) } @@ -215,7 +236,7 @@ func (t *Transfer) Start(ctx context.Context) error { if includedInBlock == math.MaxUint64 { return fmt.Errorf("transfer initiation tx not included in block") } - log.Info().Msgf("InitiateTransfer tx included in block: %d", includedInBlock) + t.logger.Info("initiateTransfer tx included in block", "block_number", includedInBlock) // Obtain event on src chain, transfer idx needed for dest chain event, err := t.srcFilterer.ObtainTransferInitiatedBySender(&bind.FilterOpts{ @@ -225,10 +246,15 @@ func (t *Transfer) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("error obtaining transfer initiated event: %s", err) } - log.Info().Msgf("InitiateTransfer event emitted on src chain: %s, recipient: %s, amount: %d, transferIdx: %d", - t.srcChainID.String(), event.Recipient, event.Amount, event.TransferIdx) + t.logger.Info( + "initiateTransfer event emitted", + "src_chain", t.srcChainID, + "recipient", event.Recipient, + "amount", event.Amount, + "transfer_idx", event.TransferIdx, + ) - log.Debug().Msgf("Waiting for transfer finalization tx from relayer") + t.logger.Debug("waiting for transfer finalization tx from relayer") timeoutSec := 60 * 30 // 30 minutes countSec := 0 for { @@ -244,8 +270,13 @@ func (t *Transfer) Start(ctx context.Context) error { return fmt.Errorf("error obtaining transfer finalized event: %s", err) } if found { - log.Info().Msgf("Transfer finalized on dest chain: %s, recipient: %s, amount: %d, srcTransferIdx: %d", - t.destChainID.String(), event.Recipient, event.Amount, event.CounterpartyIdx) + t.logger.Info( + "transfer finalized", + "dst_chain", t.destChainID, + "recipient", event.Recipient, + "amount", event.Amount, + "src_transfer_idx", event.CounterpartyIdx, + ) break } time.Sleep(time.Second) diff --git a/standard/bridge-v1/pkg/util/util.go b/standard/bridge-v1/pkg/util/util.go new file mode 100644 index 0000000..d4350ac --- /dev/null +++ b/standard/bridge-v1/pkg/util/util.go @@ -0,0 +1,55 @@ +package util + +import ( + "fmt" + "io" + "log/slog" + "strings" +) + +// NewLogger initializes a *slog.Logger with specified level, format, and sink. +// - lvl: string representation of slog.Level +// - logFmt: format of the log output: "text", "json", "none" defaults to "json" +// - tags: comma-separated list of pairs that will be inserted into each log line +// - sink: destination for log output (e.g., os.Stdout, file) +// +// Returns a configured *slog.Logger on success or nil on failure. +func NewLogger(lvl, logFmt, tags string, sink io.Writer) (*slog.Logger, error) { + level := new(slog.LevelVar) + if err := level.UnmarshalText([]byte(lvl)); err != nil { + return nil, fmt.Errorf("invalid log level: %w", err) + } + + var ( + handler slog.Handler + options = &slog.HandlerOptions{ + AddSource: true, + Level: level, + } + ) + switch logFmt { + case "text": + handler = slog.NewTextHandler(sink, options) + case "json", "none": + handler = slog.NewJSONHandler(sink, options) + default: + return nil, fmt.Errorf("invalid log format: %s", logFmt) + } + + logger := slog.New(handler) + + if tags == "" { + return logger, nil + } + + var args []any + for i, p := range strings.Split(tags, ",") { + kv := strings.Split(p, ":") + if len(kv) != 2 { + return nil, fmt.Errorf("invalid tag at index %d", i) + } + args = append(args, strings.ToValidUTF8(kv[0], "�"), strings.ToValidUTF8(kv[1], "�")) + } + + return logger.With(args...), nil +}