Skip to content

Commit

Permalink
Merge pull request #30 from primevprotocol/double-spend
Browse files Browse the repository at this point in the history
feat: handle double spending and use flags or config
  • Loading branch information
kant777 authored Feb 4, 2024
2 parents 060fa0e + 6dee5bb commit 166d166
Show file tree
Hide file tree
Showing 14 changed files with 1,341 additions and 241 deletions.
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o mev-commit-oracle ./cmd/main.go
FROM alpine:latest

COPY --from=builder /app/mev-commit-oracle /usr/local/bin/mev-commit-oracle
COPY --from=builder /app/key /key
COPY --from=builder /app/config.yaml /config.yaml
COPY --from=builder /app/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
Expand Down
14 changes: 0 additions & 14 deletions Dockerfile.release

This file was deleted.

293 changes: 205 additions & 88 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"crypto/ecdsa"
"fmt"
"os"
"path/filepath"
Expand All @@ -9,37 +10,163 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
contracts "github.com/primevprotocol/contracts-abi/config"
"github.com/primevprotocol/mev-oracle/pkg/node"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v2"
"github.com/urfave/cli/v2/altsrc"
"golang.org/x/crypto/sha3"
)

const (
defaultHTTPPort = 8080
defaultHTTPPort = 8080
defaultConfigDir = "~/.mev-commit-oracle"
defaultKeyFile = "key"
)

var (
optionConfig = &cli.StringFlag{
Name: "config",
Usage: "path to config file",
Required: true,
EnvVars: []string{"MEV_ORACLE_CONFIG"},
Name: "config",
Usage: "path to config file",
EnvVars: []string{"MEV_ORACLE_CONFIG"},
}

optionPrivKeyFile = altsrc.NewStringFlag(&cli.StringFlag{
Name: "priv-key-file",
Usage: "path to private key file",
EnvVars: []string{"MEV_ORACLE_PRIV_KEY_FILE"},
Value: filepath.Join(defaultConfigDir, defaultKeyFile),
})

optionHTTPPort = altsrc.NewIntFlag(&cli.IntFlag{
Name: "http-port",
Usage: "port to listen on for HTTP requests",
EnvVars: []string{"MEV_ORACLE_HTTP_PORT"},
Value: defaultHTTPPort,
Action: func(c *cli.Context, p int) error {
if p < 0 || p > 65535 {
return fmt.Errorf("invalid port number: %d", p)
}
return nil
},
})

optionLogLevel = altsrc.NewStringFlag(&cli.StringFlag{
Name: "log-level",
Usage: "log level",
EnvVars: []string{"MEV_ORACLE_LOG_LEVEL"},
Value: "info",
Action: func(c *cli.Context, l string) error {
_, err := zerolog.ParseLevel(l)
return err
},
})

optionL1RPCUrl = altsrc.NewStringFlag(&cli.StringFlag{
Name: "l1-rpc-url",
Usage: "URL for L1 RPC",
EnvVars: []string{"MEV_ORACLE_L1_RPC_URL"},
Required: true,
})

optionSettlementRPCUrl = altsrc.NewStringFlag(&cli.StringFlag{
Name: "settlement-rpc-url",
Usage: "URL for settlement RPC",
EnvVars: []string{"MEV_ORACLE_SETTLEMENT_RPC_URL"},
Value: "http://localhost:8545",
})

optionOracleContractAddr = altsrc.NewStringFlag(&cli.StringFlag{
Name: "oracle-contract-addr",
Usage: "address of the oracle contract",
EnvVars: []string{"MEV_ORACLE_ORACLE_CONTRACT_ADDR"},
Value: contracts.TestnetContracts.Oracle,
})

optionPreconfContractAddr = altsrc.NewStringFlag(&cli.StringFlag{
Name: "preconf-contract-addr",
Usage: "address of the preconf contract",
EnvVars: []string{"MEV_ORACLE_PRECONF_CONTRACT_ADDR"},
Value: contracts.TestnetContracts.PreconfCommitmentStore,
})

optionPgHost = altsrc.NewStringFlag(&cli.StringFlag{
Name: "pg-host",
Usage: "PostgreSQL host",
EnvVars: []string{"MEV_ORACLE_PG_HOST"},
Value: "localhost",
})

optionPgPort = altsrc.NewIntFlag(&cli.IntFlag{
Name: "pg-port",
Usage: "PostgreSQL port",
EnvVars: []string{"MEV_ORACLE_PG_PORT"},
Value: 5432,
})

optionPgUser = altsrc.NewStringFlag(&cli.StringFlag{
Name: "pg-user",
Usage: "PostgreSQL user",
EnvVars: []string{"MEV_ORACLE_PG_USER"},
Value: "postgres",
})

optionPgPassword = altsrc.NewStringFlag(&cli.StringFlag{
Name: "pg-password",
Usage: "PostgreSQL password",
EnvVars: []string{"MEV_ORACLE_PG_PASSWORD"},
Value: "postgres",
})

optionPgDbname = altsrc.NewStringFlag(&cli.StringFlag{
Name: "pg-dbname",
Usage: "PostgreSQL database name",
EnvVars: []string{"MEV_ORACLE_PG_DBNAME"},
Value: "mev_oracle",
})

optionLaggerdMode = altsrc.NewIntFlag(&cli.IntFlag{
Name: "laggerd-mode",
Usage: "No of blocks to lag behind for L1 chain",
EnvVars: []string{"MEV_ORACLE_LAGGERD_MODE"},
Value: 0,
})

optionOverrideWinners = altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
Name: "override-winners",
Usage: "Override winners for testing",
EnvVars: []string{"MEV_ORACLE_OVERRIDE_WINNERS"},
})
)

func main() {
flags := []cli.Flag{
optionConfig,
optionPrivKeyFile,
optionHTTPPort,
optionLogLevel,
optionL1RPCUrl,
optionSettlementRPCUrl,
optionOracleContractAddr,
optionPreconfContractAddr,
optionPgHost,
optionPgPort,
optionPgUser,
optionPgPassword,
optionPgDbname,
optionLaggerdMode,
optionOverrideWinners,
}
app := &cli.App{
Name: "mev-oracle",
Usage: "Entry point for mev-oracle",
Commands: []*cli.Command{
{
Name: "start",
Usage: "Start the mev-oracle node",
Flags: []cli.Flag{
optionConfig,
},
Name: "start",
Usage: "Start the mev-oracle node",
Flags: flags,
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc(optionConfig.Name)),
Action: func(c *cli.Context) error {
return start(c)
},
Expand All @@ -51,117 +178,107 @@ func main() {
}
}

type config struct {
PrivKeyFile string `yaml:"priv_key_file" json:"priv_key_file"`
HTTPPort int `yaml:"http_port" json:"http_port"`
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"`
OracleContractAddr string `yaml:"oracle_contract_addr" json:"oracle_contract_addr"`
PreconfContractAddr string `yaml:"preconf_contract_addr" json:"preconf_contract_addr"`
PgHost string `yaml:"pg_host" json:"pg_host"`
PgPort int `yaml:"pg_port" json:"pg_port"`
PgUser string `yaml:"pg_user" json:"pg_user"`
PgPassword string `yaml:"pg_password" json:"pg_password"`
PgDbname string `yaml:"pg_dbname" json:"pg_dbname"`
LaggerdMode int `yaml:"laggerd_mode" json:"laggerd_mode"`
OverrideWinners []string `yaml:"override_winners" json:"override_winners"`
}

func checkConfig(cfg *config) error {
if cfg.PrivKeyFile == "" {
return fmt.Errorf("priv_key_file is required")
func createKeyIfNotExists(c *cli.Context, path string) error {
// check if key already exists
if _, err := os.Stat(path); err == nil {
fmt.Fprintf(c.App.Writer, "Using existing private key: %s\n", path)
return nil
}

if cfg.HTTPPort == 0 {
cfg.HTTPPort = defaultHTTPPort
}
fmt.Fprintf(c.App.Writer, "Creating new private key: %s\n", path)

if cfg.LogLevel == "" {
cfg.LogLevel = "info"
// check if parent directory exists
if _, err := os.Stat(filepath.Dir(path)); os.IsNotExist(err) {
// create parent directory
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return err
}
}

if cfg.L1RPCUrl == "" {
return fmt.Errorf("l1_rpc_url is required")
privKey, err := crypto.GenerateKey()
if err != nil {
return err
}

if cfg.SettlementRPCUrl == "" {
return fmt.Errorf("settlement_rpc_url is required")
f, err := os.Create(path)
if err != nil {
return err
}

if cfg.OracleContractAddr == "" {
return fmt.Errorf("oracle_contract_addr is required")
}
defer f.Close()

if cfg.PreconfContractAddr == "" {
return fmt.Errorf("preconf_contract_addr is required")
if err := crypto.SaveECDSA(path, privKey); err != nil {
return err
}

if cfg.PgHost == "" || cfg.PgPort == 0 || cfg.PgUser == "" || cfg.PgPassword == "" || cfg.PgDbname == "" {
return fmt.Errorf("pg_host, pg_port, pg_user, pg_password, pg_dbname are required")
}
wallet := getEthAddressFromPubKey(&privKey.PublicKey)

fmt.Fprintf(c.App.Writer, "Private key saved to file: %s\n", path)
fmt.Fprintf(c.App.Writer, "Wallet address: %s\n", wallet.Hex())
return nil
}

func start(c *cli.Context) error {
configFile := c.String(optionConfig.Name)
fmt.Fprintf(c.App.Writer, "starting mev-oracle with config file: %s\n", configFile)

var cfg config
buf, err := os.ReadFile(configFile)
if err != nil {
return fmt.Errorf("failed to read config file at '%s': %w", configFile, err)
func resolveFilePath(path string) (string, error) {
if path == "" {
return "", fmt.Errorf("path is empty")
}

if err := yaml.Unmarshal(buf, &cfg); err != nil {
return fmt.Errorf("failed to unmarshal config file at '%s': %w", configFile, err)
}
if strings.HasPrefix(path, "~") {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}

if err := checkConfig(&cfg); err != nil {
return fmt.Errorf("invalid config file at '%s': %w", configFile, err)
return filepath.Join(home, path[1:]), nil
}

lvl, err := zerolog.ParseLevel(cfg.LogLevel)
return path, nil
}

func getEthAddressFromPubKey(key *ecdsa.PublicKey) common.Address {
pbBytes := crypto.FromECDSAPub(key)
hash := sha3.NewLegacyKeccak256()
hash.Write(pbBytes[1:])
address := hash.Sum(nil)[12:]

return common.BytesToAddress(address)
}

func start(c *cli.Context) error {
privKeyFile, err := resolveFilePath(c.String(optionPrivKeyFile.Name))
if err != nil {
return fmt.Errorf("failed to parse log level '%s': %w", cfg.LogLevel, err)
return fmt.Errorf("failed to get private key file path: %w", err)
}

if err := createKeyIfNotExists(c, privKeyFile); err != nil {
return fmt.Errorf("failed to create private key: %w", err)
}

lvl, _ := zerolog.ParseLevel(c.String(optionLogLevel.Name))

zerolog.SetGlobalLevel(lvl)
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = log.Output(os.Stdout).With().Caller().Logger()

privKeyFile := cfg.PrivKeyFile
if strings.HasPrefix(privKeyFile, "~/") {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get user home directory: %w", err)
}

privKeyFile = filepath.Join(homeDir, privKeyFile[2:])
}

privKey, err := crypto.LoadECDSA(privKeyFile)
if err != nil {
return fmt.Errorf("failed to load private key from file '%s': %w", cfg.PrivKeyFile, err)
return fmt.Errorf("failed to load private key from file '%s': %w", privKeyFile, err)
}

common.HexToAddress(cfg.OracleContractAddr)

nd, err := node.NewNode(&node.Options{
PrivateKey: privKey,
HTTPPort: cfg.HTTPPort,
L1RPCUrl: cfg.L1RPCUrl,
SettlementRPCUrl: cfg.SettlementRPCUrl,
OracleContractAddr: common.HexToAddress(cfg.OracleContractAddr),
PreconfContractAddr: common.HexToAddress(cfg.PreconfContractAddr),
PgHost: cfg.PgHost,
PgPort: cfg.PgPort,
PgUser: cfg.PgUser,
PgPassword: cfg.PgPassword,
PgDbname: cfg.PgDbname,
LaggerdMode: cfg.LaggerdMode,
OverrideWinners: cfg.OverrideWinners,
HTTPPort: c.Int(optionHTTPPort.Name),
L1RPCUrl: c.String(optionL1RPCUrl.Name),
SettlementRPCUrl: c.String(optionSettlementRPCUrl.Name),
OracleContractAddr: common.HexToAddress(c.String(optionOracleContractAddr.Name)),
PreconfContractAddr: common.HexToAddress(c.String(optionPreconfContractAddr.Name)),
PgHost: c.String(optionPgHost.Name),
PgPort: c.Int(optionPgPort.Name),
PgUser: c.String(optionPgUser.Name),
PgPassword: c.String(optionPgPassword.Name),
PgDbname: c.String(optionPgDbname.Name),
LaggerdMode: c.Int(optionLaggerdMode.Name),
OverrideWinners: c.StringSlice(optionOverrideWinners.Name),
})
if err != nil {
return fmt.Errorf("failed starting node: %w", err)
Expand Down
Loading

0 comments on commit 166d166

Please sign in to comment.