diff --git a/.env.replay.example b/.env.replay.example new file mode 100644 index 0000000..53f22b2 --- /dev/null +++ b/.env.replay.example @@ -0,0 +1,50 @@ +# The RPC endpoint of the source EVM chain, i.e. the chain where the proofs will be gotten from. +EVM_SOURCE_RPC= + +# The BlobstreamX contract address in the source EVM chain. This contract will be used to +# read the proofs from. +EVM_SOURCE_CONTRACT_ADDRESS= + +# The RPC endpoint of the target EVM chain, i.e. the chain where the proofs will be submitted to. +EVM_TARGET_RPC= + +# The BlobstreamX contract address in the target EVM chain. This contract will receive the proofs +# from the source contract. +EVM_TARGET_CONTRACT_ADDRESS= + +# The Succinct Gateway contract address in the target EVM chain. This contract will be responsible +# for verifying the proofs before they're committed to in the target BlobstreamX contract. +EVM_TARGET_GATEWAY= + +# The private key of your EVM account in hex format. The corresponding account should be funded +# because it will be used to submit the transactions containing the proofs. +EVM_PRIVATE_KEY= + +# Is the range of the filter to use when querying for events in the source EVM chain. +# If you run the replay mechanism and the RPC provider complains that the filter range is +# too wide, please set a lower value depending on your RPC provider. +EVM_FILTER_RANGE= + +# The function ID of the header range circuit verifier. It is the digest returned from +# the Succinct Gateway when you register the verifier of the header range circuit. +CIRCUITS_HEADER_RANGE_FUNCTIONID= + + +# The function ID of the next header circuit verifier. It is the digest returned from +# the Succinct Gateway when you register the verifier of the next header circuit. +CIRCUITS_NEXT_HEADER_FUNCTIONID= + +# Set it to true to validate the data root tuple roots before submitting their corresponding +# proofs to the target chain. If set to true, it requires the CORE_RPC variable to be set +# to a Celestia consensus network RPC endpoint. +VERIFY=false + +# The endpoint of the Celestia consensus network RPC endpoint. Should be set if the VERIFY +# is set to true. +CORE_RPC= + +# The logging level. Accepted values: trace|debug|info|warn|error|fatal|panic. +LOG_LEVEL= + +# The logging format. Accepted values: json|plain. +LOG_FORMAT= diff --git a/.env.verify.example b/.env.verify.example new file mode 100644 index 0000000..e164ef6 --- /dev/null +++ b/.env.verify.example @@ -0,0 +1,15 @@ +# The RPC endpoint of the EVM chain where the BlobstreamX contract is deployed. +EVM_RPC= + +# The BlobstreamX contract address in the EVM chain. This contract will be used to +# read the data root tuple root from and verify their validity. +EVM_CONTRACT_ADDRESS= + +# The endpoint of the Celestia consensus network RPC endpoint. +CORE_RPC= + +# The logging level. Accepted values: trace|debug|info|warn|error|fatal|panic. +LOG_LEVEL= + +# The logging format. Accepted values: json|plain. +LOG_FORMAT= diff --git a/cmd/blobstream-ops/common/utils.go b/cmd/blobstream-ops/common/utils.go index 0613007..998b799 100644 --- a/cmd/blobstream-ops/common/utils.go +++ b/cmd/blobstream-ops/common/utils.go @@ -9,6 +9,9 @@ import ( "strings" "syscall" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/server" "github.com/rs/zerolog" tmconfig "github.com/tendermint/tendermint/config" @@ -42,3 +45,26 @@ func TrapSignal(logger tmlog.Logger, cancel context.CancelFunc) { logger.Info("caught signal; shutting down...", "signal", sig.String()) cancel() } + +// ToEnvVariableFormat takes a flag and returns its corresponding environment +// variable. +// Example: if the flag is: `--flag1.flag2-flag3`, then, the environment variable that is looked for +// is `FLAG1_FLAG2_FLAG3`. +func ToEnvVariableFormat(flag string) string { + return strings.ReplaceAll( + strings.ToUpper(strings.ReplaceAll(strings.TrimPrefix(flag, "--"), "-", "_")), + ".", + "_", + ) +} + +func BindFlagAndEnvVar(cmd *cobra.Command, flag string) { + if err := viper.BindPFlag(flag, cmd.Flags().Lookup(flag)); err != nil { + fmt.Println(err) + os.Exit(1) + } + if err := viper.BindEnv(flag, ToEnvVariableFormat(flag)); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/blobstream-ops/replay/cmd.go b/cmd/blobstream-ops/replay/cmd.go index 3825032..ef00c76 100644 --- a/cmd/blobstream-ops/replay/cmd.go +++ b/cmd/blobstream-ops/replay/cmd.go @@ -22,7 +22,7 @@ func Command() *cobra.Command { Long: "verifies that a BlobstreamX contract is committing to valid data", SilenceUsage: true, RunE: func(cmd *cobra.Command, _ []string) error { - config, err := parseFlags(cmd) + config, err := parseFlags() if err != nil { return err } diff --git a/cmd/blobstream-ops/replay/config.go b/cmd/blobstream-ops/replay/config.go index d200aab..e098520 100644 --- a/cmd/blobstream-ops/replay/config.go +++ b/cmd/blobstream-ops/replay/config.go @@ -5,8 +5,11 @@ import ( "encoding/hex" "errors" "fmt" + "strings" + "github.com/celestiaorg/blobstream-ops/cmd/blobstream-ops/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/spf13/viper" ethcmn "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" @@ -33,31 +36,99 @@ const ( ) func addFlags(cmd *cobra.Command) *cobra.Command { - cmd.Flags().String(FlagSourceEVMRPC, "http://localhost:8545", "Specify the Ethereum rpc address of the source EVM chain") - cmd.Flags().String(FlagTargetEVMRPC, "http://localhost:8545", "Specify the Ethereum rpc address of the target EVM chain") - cmd.Flags().String(FlagSourceEVMContractAddress, "", "Specify the source contract at which the source BlobstreamX contract is deployed") - cmd.Flags().String(FlagTargetEVMContractAddress, "", "Specify the target contract at which the target BlobstreamX contract is deployed") - cmd.Flags().String(FlagTargetChainGateway, "", "Specify the target chain succinct gateway contract address") + viper.AutomaticEnv() + + cmd.Flags().String( + FlagSourceEVMRPC, + "http://localhost:8545", + fmt.Sprintf("Specify the Ethereum rpc address of the source EVM chain. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagSourceEVMRPC)), + ) + common.BindFlagAndEnvVar(cmd, FlagSourceEVMRPC) + + cmd.Flags().String( + FlagTargetEVMRPC, + "http://localhost:8545", + fmt.Sprintf("Specify the Ethereum rpc address of the target EVM chain. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagTargetEVMRPC)), + ) + common.BindFlagAndEnvVar(cmd, FlagTargetEVMRPC) + + cmd.Flags().String( + FlagSourceEVMContractAddress, + "", + fmt.Sprintf("Specify the source contract at which the source BlobstreamX contract is deployed. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagSourceEVMContractAddress)), + ) + common.BindFlagAndEnvVar(cmd, FlagSourceEVMContractAddress) + + cmd.Flags().String( + FlagTargetEVMContractAddress, + "", + fmt.Sprintf("Specify the target contract at which the target BlobstreamX contract is deployed. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagTargetEVMContractAddress)), + ) + common.BindFlagAndEnvVar(cmd, FlagTargetEVMContractAddress) + + cmd.Flags().String( + FlagTargetChainGateway, + "", + fmt.Sprintf("Specify the target chain succinct gateway contract address. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagTargetChainGateway)), + ) + common.BindFlagAndEnvVar(cmd, FlagTargetChainGateway) + cmd.Flags().String( FlagLogLevel, "info", - "The logging level (trace|debug|info|warn|error|fatal|panic)", + fmt.Sprintf("The logging level (trace|debug|info|warn|error|fatal|panic). Corresponding environment variable %s", common.ToEnvVariableFormat(FlagLogLevel)), ) + common.BindFlagAndEnvVar(cmd, FlagLogLevel) + cmd.Flags().String( FlagLogFormat, "plain", - "The logging format (json|plain)", + fmt.Sprintf("The logging format (json|plain). Corresponding environment variable %s", common.ToEnvVariableFormat(FlagLogFormat)), ) + common.BindFlagAndEnvVar(cmd, FlagLogFormat) + cmd.Flags().String( FlagCoreRPC, "tcp://localhost:26657", - "The celestia app rpc address", + fmt.Sprintf("The celestia app rpc address. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagCoreRPC)), ) - cmd.Flags().Bool(FlagVerify, false, "Set to verify the commitments before replaying their proofs. Require the core rpc flag to be set") - cmd.Flags().String(FlagEVMPrivateKey, "", "Specify the EVM private key, in hex format without the leading 0x, to use for replaying transaction in the target chain. Corresponding account should be funded") - cmd.Flags().String(FlagHeaderRangeFunctionID, "", "Specify the function ID of the header range circuit in the target BlobstreamX contract, in hex format without the leading 0x") - cmd.Flags().String(FlagNextHeaderFunctionID, "", "Specify the function ID of the next header circuit in the target BlobstreamX contract, in hex format without the leading 0x") - cmd.Flags().Int64(FlagEVMFilterRange, 5000, "Specify the eth_getLogs filter range") + common.BindFlagAndEnvVar(cmd, FlagCoreRPC) + + cmd.Flags().Bool( + FlagVerify, + false, + fmt.Sprintf("Set to verify the commitments before replaying their proofs. Require the core rpc flag to be set. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagVerify)), + ) + common.BindFlagAndEnvVar(cmd, FlagVerify) + + cmd.Flags().String( + FlagEVMPrivateKey, + "", + fmt.Sprintf("Specify the EVM private key, in hex format, to use for replaying transaction in the target chain. Corresponding account should be funded. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagEVMPrivateKey)), + ) + common.BindFlagAndEnvVar(cmd, FlagEVMPrivateKey) + + cmd.Flags().String( + FlagHeaderRangeFunctionID, + "", + fmt.Sprintf("Specify the function ID of the header range circuit in the target BlobstreamX contract, in hex format. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagHeaderRangeFunctionID)), + ) + common.BindFlagAndEnvVar(cmd, FlagHeaderRangeFunctionID) + + cmd.Flags().String( + FlagNextHeaderFunctionID, + "", + fmt.Sprintf("Specify the function ID of the next header circuit in the target BlobstreamX contract, in hex format. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagNextHeaderFunctionID)), + ) + common.BindFlagAndEnvVar(cmd, FlagNextHeaderFunctionID) + + cmd.Flags().Int64( + FlagEVMFilterRange, + 5000, + fmt.Sprintf("Specify the eth_getLogs filter range. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagEVMFilterRange)), + ) + common.BindFlagAndEnvVar(cmd, FlagEVMFilterRange) + return cmd } @@ -79,16 +150,21 @@ type Config struct { func (cfg Config) ValidateBasics() error { if err := ValidateEVMAddress(cfg.SourceContractAddress); err != nil { - return fmt.Errorf("%s: flag --%s", err.Error(), FlagSourceEVMContractAddress) + return fmt.Errorf( + "%s: flag --%s or environment variable %s", + err.Error(), + FlagSourceEVMContractAddress, + common.ToEnvVariableFormat(FlagSourceEVMContractAddress), + ) } if err := ValidateEVMAddress(cfg.TargetContractAddress); err != nil { - return fmt.Errorf("%s: flag --%s", err.Error(), FlagTargetEVMContractAddress) + return fmt.Errorf("%s: flag --%s or environment variable %s", err.Error(), FlagTargetEVMContractAddress, common.ToEnvVariableFormat(FlagTargetEVMContractAddress)) } if err := ValidateEVMAddress(cfg.TargetChainGateway); err != nil { - return fmt.Errorf("%s: flag --%s", err.Error(), FlagTargetChainGateway) + return fmt.Errorf("%s: flag --%s or environment variable %s", err.Error(), FlagTargetChainGateway, common.ToEnvVariableFormat(FlagTargetChainGateway)) } if cfg.Verify && cfg.CoreRPC == "" { - return fmt.Errorf("flag --%s is set but the core RPC flag --%s is not set", FlagVerify, FlagCoreRPC) + return fmt.Errorf("flag --%s is set but the core RPC flag --%s is not set. Please set --%s or environment variable %s", FlagVerify, FlagCoreRPC, FlagCoreRPC, common.ToEnvVariableFormat(FlagCoreRPC)) } return nil } @@ -103,67 +179,38 @@ func ValidateEVMAddress(addr string) error { return nil } -func parseFlags(cmd *cobra.Command) (Config, error) { - // TODO add support for env variables - sourceContractAddress, err := cmd.Flags().GetString(FlagSourceEVMContractAddress) - if err != nil { - return Config{}, err - } +func parseFlags() (Config, error) { + sourceContractAddress := viper.GetString(FlagSourceEVMContractAddress) - targetContractAddress, err := cmd.Flags().GetString(FlagTargetEVMContractAddress) - if err != nil { - return Config{}, err - } + targetContractAddress := viper.GetString(FlagTargetEVMContractAddress) - targetChainGateway, err := cmd.Flags().GetString(FlagTargetChainGateway) - if err != nil { - return Config{}, err - } + targetChainGateway := viper.GetString(FlagTargetChainGateway) - sourceEVMRPC, err := cmd.Flags().GetString(FlagSourceEVMRPC) - if err != nil { - return Config{}, err - } + sourceEVMRPC := viper.GetString(FlagSourceEVMRPC) - targetEVMRPC, err := cmd.Flags().GetString(FlagTargetEVMRPC) - if err != nil { - return Config{}, err - } + targetEVMRPC := viper.GetString(FlagTargetEVMRPC) - coreRPC, err := cmd.Flags().GetString(FlagCoreRPC) - if err != nil { - return Config{}, err - } + coreRPC := viper.GetString(FlagCoreRPC) - logLevel, err := cmd.Flags().GetString(FlagLogLevel) - if err != nil { - return Config{}, err - } + logLevel := viper.GetString(FlagLogLevel) - logFormat, err := cmd.Flags().GetString(FlagLogFormat) - if err != nil { - return Config{}, err - } + logFormat := viper.GetString(FlagLogFormat) - rawPrivateKey, err := cmd.Flags().GetString(FlagEVMPrivateKey) - if err != nil { - return Config{}, err - } + rawPrivateKey := viper.GetString(FlagEVMPrivateKey) if rawPrivateKey == "" { - return Config{}, fmt.Errorf("please set the private key --%s", FlagEVMPrivateKey) + return Config{}, fmt.Errorf("please set the private key --%s or %s", FlagEVMPrivateKey, common.ToEnvVariableFormat(FlagEVMPrivateKey)) } + rawPrivateKey = strings.TrimPrefix(rawPrivateKey, "0x") privateKey, err := crypto.HexToECDSA(rawPrivateKey) if err != nil { return Config{}, fmt.Errorf("failed to hex-decode Ethereum ECDSA Private Key: %w", err) } - strHeaderRange, err := cmd.Flags().GetString(FlagHeaderRangeFunctionID) - if err != nil { - return Config{}, err - } + strHeaderRange := viper.GetString(FlagHeaderRangeFunctionID) if strHeaderRange == "" { - return Config{}, fmt.Errorf("please set the header range function ID --%s", FlagHeaderRangeFunctionID) + return Config{}, fmt.Errorf("please set the header range function ID --%s or %s", FlagHeaderRangeFunctionID, common.ToEnvVariableFormat(FlagHeaderRangeFunctionID)) } + strHeaderRange = strings.TrimPrefix(strHeaderRange, "0x") decodedHeaderRange, err := hex.DecodeString(strHeaderRange) if err != nil { return Config{}, err @@ -171,13 +218,11 @@ func parseFlags(cmd *cobra.Command) (Config, error) { var bzHeaderRange [32]byte copy(bzHeaderRange[:], decodedHeaderRange) - strNextHeader, err := cmd.Flags().GetString(FlagNextHeaderFunctionID) - if err != nil { - return Config{}, err - } + strNextHeader := viper.GetString(FlagNextHeaderFunctionID) if strNextHeader == "" { - return Config{}, fmt.Errorf("please set the header range function ID --%s", FlagHeaderRangeFunctionID) + return Config{}, fmt.Errorf("please set the next header function ID --%s or %s", FlagNextHeaderFunctionID, common.ToEnvVariableFormat(FlagNextHeaderFunctionID)) } + strNextHeader = strings.TrimPrefix(strNextHeader, "0x") decodedNextHeader, err := hex.DecodeString(strNextHeader) if err != nil { return Config{}, err @@ -185,15 +230,9 @@ func parseFlags(cmd *cobra.Command) (Config, error) { var bzNextHeader [32]byte copy(bzNextHeader[:], decodedNextHeader) - filterRange, err := cmd.Flags().GetInt64(FlagEVMFilterRange) - if err != nil { - return Config{}, err - } + filterRange := viper.GetInt64(FlagEVMFilterRange) - verify, err := cmd.Flags().GetBool(FlagVerify) - if err != nil { - return Config{}, err - } + verify := viper.GetBool(FlagVerify) // TODO add rate limiting flag // TODO add gas price multiplier flag diff --git a/cmd/blobstream-ops/verify/cmd.go b/cmd/blobstream-ops/verify/cmd.go index 5fe2c80..d71a2e2 100644 --- a/cmd/blobstream-ops/verify/cmd.go +++ b/cmd/blobstream-ops/verify/cmd.go @@ -42,7 +42,7 @@ func VerifyContractCommand() *cobra.Command { Short: "Starts the BlobstreamX contract verifier", Long: "verifies that a BlobstreamX contract is committing to valid data", RunE: func(cmd *cobra.Command, _ []string) error { - config, err := parseStartFlags(cmd) + config, err := parseStartFlags() if err != nil { return err } diff --git a/cmd/blobstream-ops/verify/config.go b/cmd/blobstream-ops/verify/config.go index 7653fe0..6ca58a7 100644 --- a/cmd/blobstream-ops/verify/config.go +++ b/cmd/blobstream-ops/verify/config.go @@ -4,8 +4,10 @@ import ( "errors" "fmt" + "github.com/celestiaorg/blobstream-ops/cmd/blobstream-ops/common" ethcmn "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" + "github.com/spf13/viper" ) const ( @@ -19,23 +21,43 @@ const ( ) func addStartFlags(cmd *cobra.Command) *cobra.Command { - cmd.Flags().String(FlagEVMRPC, "http://localhost:8545", "Specify the ethereum rpc address") - cmd.Flags().String(FlagEVMContractAddress, "", "Specify the contract at which the BlobstreamX contract is deployed") + viper.AutomaticEnv() + + cmd.Flags().String( + FlagEVMRPC, + "http://localhost:8545", + fmt.Sprintf("Specify the ethereum rpc address. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagEVMRPC)), + ) + common.BindFlagAndEnvVar(cmd, FlagEVMRPC) + + cmd.Flags().String( + FlagEVMContractAddress, + "", + fmt.Sprintf("Specify the contract at which the BlobstreamX contract is deployed. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagEVMContractAddress)), + ) + common.BindFlagAndEnvVar(cmd, FlagEVMContractAddress) + cmd.Flags().String( FlagLogLevel, "info", - "The logging level (trace|debug|info|warn|error|fatal|panic)", + fmt.Sprintf("The logging level (trace|debug|info|warn|error|fatal|panic). Corresponding environment variable %s", common.ToEnvVariableFormat(FlagLogLevel)), ) + common.BindFlagAndEnvVar(cmd, FlagLogLevel) + cmd.Flags().String( FlagLogFormat, "plain", - "The logging format (json|plain)", + fmt.Sprintf("The logging format (json|plain). Corresponding environment variable %s", common.ToEnvVariableFormat(FlagLogFormat)), ) + common.BindFlagAndEnvVar(cmd, FlagLogFormat) + cmd.Flags().String( FlagCoreRPC, "tcp://localhost:26657", - "The celestia app rpc address", + fmt.Sprintf("The celestia app rpc address. Corresponding environment variable %s", common.ToEnvVariableFormat(FlagCoreRPC)), ) + common.BindFlagAndEnvVar(cmd, FlagCoreRPC) + return cmd } @@ -64,31 +86,12 @@ func ValidateEVMAddress(addr string) error { return nil } -func parseStartFlags(cmd *cobra.Command) (StartConfig, error) { - contractAddress, err := cmd.Flags().GetString(FlagEVMContractAddress) - if err != nil { - return StartConfig{}, err - } - - evmRPC, err := cmd.Flags().GetString(FlagEVMRPC) - if err != nil { - return StartConfig{}, err - } - - coreRPC, err := cmd.Flags().GetString(FlagCoreRPC) - if err != nil { - return StartConfig{}, err - } - - logLevel, err := cmd.Flags().GetString(FlagLogLevel) - if err != nil { - return StartConfig{}, err - } - - logFormat, err := cmd.Flags().GetString(FlagLogFormat) - if err != nil { - return StartConfig{}, err - } +func parseStartFlags() (StartConfig, error) { + contractAddress := viper.GetString(FlagEVMContractAddress) + evmRPC := viper.GetString(FlagEVMRPC) + coreRPC := viper.GetString(FlagCoreRPC) + logLevel := viper.GetString(FlagLogLevel) + logFormat := viper.GetString(FlagLogFormat) return StartConfig{ EVMRPC: evmRPC, diff --git a/go.mod b/go.mod index e83c675..98d65c9 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/cosmos/cosmos-sdk v0.50.3 github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.16.0 github.com/succinctlabs/blobstreamx v0.0.0-20240115194141-5649c689a7fe github.com/succinctlabs/succinctx v1.1.0 github.com/tendermint/tendermint v0.35.9 @@ -125,7 +126,6 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.16.0 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.11 // indirect