diff --git a/cmd/main.go b/cmd/main.go index ac9e30c7..6f773440 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,8 +2,6 @@ package main import ( "fmt" - "io" - "log/slog" "os" "path/filepath" "slices" @@ -14,6 +12,7 @@ import ( mevcommit "github.com/primevprotocol/mev-commit" ks "github.com/primevprotocol/mev-commit/pkg/keysigner" "github.com/primevprotocol/mev-commit/pkg/node" + "github.com/primevprotocol/mev-commit/pkg/util" "github.com/urfave/cli/v2" "github.com/urfave/cli/v2/altsrc" ) @@ -158,6 +157,20 @@ var ( Action: stringInCheck("log-level", []string{"debug", "info", "warn", "error"}), }) + 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{"MEV_COMMIT_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 + }, + }) + optionBidderRegistryAddr = altsrc.NewStringFlag(&cli.StringFlag{ Name: "bidder-registry-contract", Usage: "address of the bidder registry contract", @@ -229,6 +242,7 @@ func main() { optionSecret, optionLogFmt, optionLogLevel, + optionLogTags, optionBidderRegistryAddr, optionProviderRegistryAddr, optionPreconfStoreAddr, @@ -274,20 +288,22 @@ func verifyKeystorePasswordPresence(c *cli.Context) error { // launchNodeWithConfig configures and starts the p2p node based on the CLI context. func launchNodeWithConfig(c *cli.Context) error { - keysigner, err := newKeySigner(c) - if err != nil { - return fmt.Errorf("failed to create keysigner: %w", err) - } - - logger, err := newLogger( + 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) } + keysigner, err := newKeySigner(c) + if err != nil { + return fmt.Errorf("failed to create keysigner: %w", err) + } + logger.Info("key signer account", "address", keysigner.GetAddress().Hex(), "url", keysigner.String()) + httpAddr := fmt.Sprintf("%s:%d", c.String(optionHTTPAddr.Name), c.Int(optionHTTPPort.Name)) rpcAddr := fmt.Sprintf("%s:%d", c.String(optionRPCAddr.Name), c.Int(optionRPCPort.Name)) natAddr := "" @@ -324,7 +340,7 @@ func launchNodeWithConfig(c *cli.Context) error { } <-c.Done() - fmt.Fprintln(c.App.Writer, "shutting down...") + logger.Info("shutting down...") closed := make(chan struct{}) go func() { @@ -345,34 +361,9 @@ func launchNodeWithConfig(c *cli.Context) error { return nil } -func newLogger(lvl, logFmt 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) - } - - return slog.New(handler), nil -} - func newKeySigner(c *cli.Context) (ks.KeySigner, error) { if c.IsSet(optionKeystorePath.Name) { - return ks.NewKeystoreSigner(c.App.Writer, c.String(optionKeystorePath.Name), c.String(optionKeystorePassword.Name)) + return ks.NewKeystoreSigner(c.String(optionKeystorePath.Name), c.String(optionKeystorePassword.Name)) } - return ks.NewPrivateKeySigner(c.App.Writer, c.String(optionPrivKeyFile.Name)) + return ks.NewPrivateKeySigner(c.String(optionPrivKeyFile.Name)) } diff --git a/examples/biddercli/main.go b/examples/biddercli/main.go index 43a60b6e..712470ea 100644 --- a/examples/biddercli/main.go +++ b/examples/biddercli/main.go @@ -55,7 +55,6 @@ func main() { app.Before = func(c *cli.Context) error { configFile := c.String(optionConfig.Name) - fmt.Printf("using configuration file: %s\n", configFile) buf, err := os.ReadFile(configFile) if err != nil { diff --git a/integrationtest/bidder/main.go b/integrationtest/bidder/main.go index c683b71e..96931da8 100644 --- a/integrationtest/bidder/main.go +++ b/integrationtest/bidder/main.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" pb "github.com/primevprotocol/mev-commit/gen/go/rpc/bidderapi/v1" + "github.com/primevprotocol/mev-commit/pkg/util" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "google.golang.org/grpc" @@ -44,6 +45,16 @@ var ( "debug", "Verbosity level (debug|info|warn|error)", ) + logFmt = flag.String( + "log-fmt", + "text", + "Format of the log output: 'text', 'json'", + ) + logTags = flag.String( + "log-tags", + "", + "Comma-separated list of pairs that will be inserted into each log line", + ) httpPort = flag.Int( "http-port", 8080, @@ -85,22 +96,18 @@ var ( func main() { flag.Parse() - if *serverAddr == "" { - fmt.Println("Please provide a valid server address with the -serverAddr flag") + + logger, err := util.NewLogger(*logLevel, *logFmt, *logTags, os.Stdout) + if err != nil { + fmt.Printf("failed to create logger: %v", err) return } - level := new(slog.LevelVar) - if err := level.UnmarshalText([]byte(*logLevel)); err != nil { - level.Set(slog.LevelDebug) - fmt.Printf("Invalid log level: %s; using %q", err, level) + if *serverAddr == "" { + fmt.Println("Please provide a valid server address with the -serverAddr flag") + return } - logger := slog.New(slog.NewTextHandler( - os.Stdout, - &slog.HandlerOptions{Level: level}, - )) - registry := prometheus.NewRegistry() registry.MustRegister( receivedPreconfs, diff --git a/integrationtest/provider/main.go b/integrationtest/provider/main.go index 3cc4beca..751b14d4 100644 --- a/integrationtest/provider/main.go +++ b/integrationtest/provider/main.go @@ -6,12 +6,12 @@ import ( "errors" "flag" "fmt" - "log/slog" "math/rand" "net/http" "os" providerapiv1 "github.com/primevprotocol/mev-commit/gen/go/rpc/providerapi/v1" + "github.com/primevprotocol/mev-commit/pkg/util" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -21,6 +21,8 @@ import ( const ( serverAddrFlagName = "server-addr" logLevelFlagName = "log-level" + logFmtFlagName = "log-fmt" + logTagsFlagName = "log-tags" httpPortFlagName = "http-port" errorProbabilityFlagName = "error-probability" ) @@ -36,6 +38,16 @@ var ( "debug", "Verbosity level (debug|info|warn|error)", ) + logFmt = flag.String( + logFmtFlagName, + "text", + "Format of the log output: 'text', 'json'", + ) + logTags = flag.String( + logTagsFlagName, + "", + "Comma-separated list of pairs that will be inserted into each log line", + ) httpPort = flag.Int( httpPortFlagName, 8080, @@ -71,22 +83,18 @@ var ( func main() { flag.Parse() - if *serverAddr == "" { - fmt.Printf("please provide a valid server address with the -%s flag\n", serverAddrFlagName) + + logger, err := util.NewLogger(*logLevel, *logFmt, *logTags, os.Stdout) + if err != nil { + fmt.Printf("failed to create logger: %v", err) return } - level := new(slog.LevelVar) - if err := level.UnmarshalText([]byte(*logLevel)); err != nil { - level.Set(slog.LevelDebug) - fmt.Printf("invalid log level: %s; using %q", err, level) + if *serverAddr == "" { + fmt.Printf("please provide a valid server address with the -%s flag\n", serverAddrFlagName) + return } - logger := slog.New(slog.NewTextHandler( - os.Stdout, - &slog.HandlerOptions{Level: level}, - )) - registry := prometheus.NewRegistry() registry.MustRegister(receivedBids, sentBids) diff --git a/pkg/keysigner/keysigner.go b/pkg/keysigner/keysigner.go index 52dd3b12..d01aec2d 100644 --- a/pkg/keysigner/keysigner.go +++ b/pkg/keysigner/keysigner.go @@ -2,6 +2,7 @@ package keysigner import ( "crypto/ecdsa" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -9,6 +10,8 @@ import ( ) type KeySigner interface { + fmt.Stringer + SignHash(data []byte) ([]byte, error) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) GetAddress() common.Address diff --git a/pkg/keysigner/keystoresigner.go b/pkg/keysigner/keystoresigner.go index 1459fe4c..bbb83b9f 100644 --- a/pkg/keysigner/keystoresigner.go +++ b/pkg/keysigner/keystoresigner.go @@ -3,7 +3,6 @@ package keysigner import ( "crypto/ecdsa" "fmt" - "io" "math/big" "runtime" @@ -13,13 +12,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -type keystoreSigner struct { +type KeystoreSigner struct { keystore *keystore.KeyStore password string account accounts.Account } -func NewKeystoreSigner(wr io.Writer, path, password string) (*keystoreSigner, error) { +func NewKeystoreSigner(path, password string) (*KeystoreSigner, error) { // lightscripts are using 4MB memory and taking approximately 100ms CPU time on a modern processor to decrypt keystore := keystore.NewKeyStore(path, keystore.LightScryptN, keystore.LightScryptP) ksAccounts := keystore.Accounts() @@ -35,33 +34,30 @@ func NewKeystoreSigner(wr io.Writer, path, password string) (*keystoreSigner, er account = ksAccounts[0] } - fmt.Fprintf(wr, "Public address of the key: %s\n", account.Address.Hex()) - fmt.Fprintf(wr, "Path of the secret key file: %s\n", account.URL.Path) - - return &keystoreSigner{ + return &KeystoreSigner{ keystore: keystore, password: password, account: account, }, nil } -func (kss *keystoreSigner) SignHash(hash []byte) ([]byte, error) { +func (kss *KeystoreSigner) SignHash(hash []byte) ([]byte, error) { return kss.keystore.SignHashWithPassphrase(kss.account, kss.password, hash) } -func (kss *keystoreSigner) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { +func (kss *KeystoreSigner) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { return kss.keystore.SignTxWithPassphrase(kss.account, kss.password, tx, chainID) } -func (kss *keystoreSigner) GetAddress() common.Address { +func (kss *KeystoreSigner) GetAddress() common.Address { return kss.account.Address } -func (kss *keystoreSigner) GetPrivateKey() (*ecdsa.PrivateKey, error) { +func (kss *KeystoreSigner) GetPrivateKey() (*ecdsa.PrivateKey, error) { return extractPrivateKey(kss.account.URL.Path, kss.password) } -func (kss *keystoreSigner) ZeroPrivateKey(key *ecdsa.PrivateKey) { +func (kss *KeystoreSigner) ZeroPrivateKey(key *ecdsa.PrivateKey) { b := key.D.Bits() for i := range b { b[i] = 0 @@ -69,3 +65,7 @@ func (kss *keystoreSigner) ZeroPrivateKey(key *ecdsa.PrivateKey) { // Force garbage collection to remove the key from memory runtime.GC() } + +func (kss *KeystoreSigner) String() string { + return kss.account.URL.String() +} diff --git a/pkg/keysigner/mock/mock.go b/pkg/keysigner/mock/mock.go index 2927dbbd..fe400841 100644 --- a/pkg/keysigner/mock/mock.go +++ b/pkg/keysigner/mock/mock.go @@ -9,29 +9,33 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -type mockKeySigner struct { +type MockKeySigner struct { privKey *ecdsa.PrivateKey address common.Address } -func NewMockKeySigner(privKey *ecdsa.PrivateKey, address common.Address) *mockKeySigner { - return &mockKeySigner{privKey: privKey, address: address} +func NewMockKeySigner(privKey *ecdsa.PrivateKey, address common.Address) *MockKeySigner { + return &MockKeySigner{privKey: privKey, address: address} } -func (m *mockKeySigner) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { +func (m *MockKeySigner) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { return tx, nil } -func (m *mockKeySigner) SignHash(hash []byte) ([]byte, error) { +func (m *MockKeySigner) SignHash(hash []byte) ([]byte, error) { return crypto.Sign(hash, m.privKey) } -func (m *mockKeySigner) GetAddress() common.Address { +func (m *MockKeySigner) GetAddress() common.Address { return m.address } -func (m *mockKeySigner) GetPrivateKey() (*ecdsa.PrivateKey, error) { +func (m *MockKeySigner) GetPrivateKey() (*ecdsa.PrivateKey, error) { return m.privKey, nil } -func (m *mockKeySigner) ZeroPrivateKey(key *ecdsa.PrivateKey) {} +func (m *MockKeySigner) ZeroPrivateKey(key *ecdsa.PrivateKey) {} + +func (m *MockKeySigner) String() string { + return "mock" +} diff --git a/pkg/keysigner/privatekeysigner.go b/pkg/keysigner/privatekeysigner.go index e9a38b78..2653b171 100644 --- a/pkg/keysigner/privatekeysigner.go +++ b/pkg/keysigner/privatekeysigner.go @@ -3,7 +3,6 @@ package keysigner import ( "crypto/ecdsa" "fmt" - "io" "math/big" "os" "path/filepath" @@ -15,17 +14,18 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -type privateKeySigner struct { +type PrivateKeySigner struct { + path string privKey *ecdsa.PrivateKey } -func NewPrivateKeySigner(wr io.Writer, path string) (*privateKeySigner, error) { +func NewPrivateKeySigner(path string) (*PrivateKeySigner, error) { privKeyFile, err := resolveFilePath(path) if err != nil { return nil, fmt.Errorf("failed to get private key file path: %w", err) } - if err := createKeyIfNotExists(wr, privKeyFile); err != nil { + if err := createKeyIfNotExists(privKeyFile); err != nil { return nil, fmt.Errorf("failed to create private key: %w", err) } @@ -34,30 +34,35 @@ func NewPrivateKeySigner(wr io.Writer, path string) (*privateKeySigner, error) { return nil, fmt.Errorf("failed to load private key from file '%s': %w", privKeyFile, err) } - return &privateKeySigner{ + return &PrivateKeySigner{ + path: privKeyFile, privKey: privKey, }, nil } -func (pks *privateKeySigner) SignHash(hash []byte) ([]byte, error) { +func (pks *PrivateKeySigner) SignHash(hash []byte) ([]byte, error) { return crypto.Sign(hash, pks.privKey) } -func (pks *privateKeySigner) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { +func (pks *PrivateKeySigner) SignTx(tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { return types.SignTx(tx, types.NewLondonSigner(chainID), pks.privKey) } -func (pks *privateKeySigner) GetAddress() common.Address { +func (pks *PrivateKeySigner) GetAddress() common.Address { return crypto.PubkeyToAddress(pks.privKey.PublicKey) } -func (pks *privateKeySigner) GetPrivateKey() (*ecdsa.PrivateKey, error) { +func (pks *PrivateKeySigner) GetPrivateKey() (*ecdsa.PrivateKey, error) { return pks.privKey, nil } // ZeroPrivateKey does nothing because the private key for PKS persists in memory // and should not be deleted. -func (pks *privateKeySigner) ZeroPrivateKey(key *ecdsa.PrivateKey) {} +func (pks *PrivateKeySigner) ZeroPrivateKey(key *ecdsa.PrivateKey) {} + +func (pks *PrivateKeySigner) String() string { + return pks.path +} func extractPrivateKey(keystoreFile, passphrase string) (*ecdsa.PrivateKey, error) { keyjson, err := os.ReadFile(keystoreFile) @@ -79,13 +84,11 @@ func extractPrivateKey(keystoreFile, passphrase string) (*ecdsa.PrivateKey, erro return key.PrivateKey, nil } -func createKeyIfNotExists(wr io.Writer, path string) error { +func createKeyIfNotExists(path string) error { if _, err := os.Stat(path); err == nil { - fmt.Fprintln(wr, "using existing private key:", path) return nil } - fmt.Fprintln(wr, "creating new private key:", path) if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { return err } @@ -95,16 +98,7 @@ func createKeyIfNotExists(wr io.Writer, path string) error { return err } - if err := crypto.SaveECDSA(path, key); err != nil { - return err - } - - addr := crypto.PubkeyToAddress(key.PublicKey) - - fmt.Fprintln(wr, "private key saved to file:", path) - fmt.Fprintln(wr, "wallet address:", addr.Hex()) - - return nil + return crypto.SaveECDSA(path, key) } func resolveFilePath(path string) (string, error) { diff --git a/pkg/p2p/libp2p/export_test.go b/pkg/p2p/libp2p/export_test.go index 078c6b7a..c72c7dae 100644 --- a/pkg/p2p/libp2p/export_test.go +++ b/pkg/p2p/libp2p/export_test.go @@ -1,8 +1,6 @@ package libp2p import ( - "fmt" - "github.com/libp2p/go-libp2p/core/peer" "github.com/primevprotocol/mev-commit/pkg/p2p" ) @@ -24,7 +22,6 @@ func (s *Service) HostID() peer.ID { } func (s *Service) AddrString() string { - fmt.Println(s.host.Addrs()) return s.host.Addrs()[0].String() + "/p2p/" + s.host.ID().String() } diff --git a/pkg/topology/topology_test.go b/pkg/topology/topology_test.go index bf6db975..f93bccbe 100644 --- a/pkg/topology/topology_test.go +++ b/pkg/topology/topology_test.go @@ -3,7 +3,6 @@ package topology_test import ( "context" "errors" - "fmt" "io" "log/slog" "os" @@ -30,7 +29,6 @@ func (a *announcer) BroadcastPeers(_ context.Context, p p2p.Peer, peers []p2p.Pe a.mu.Lock() defer a.mu.Unlock() - fmt.Println("broadcasting peers", p) a.broadcasts = append(a.broadcasts, p) if len(peers) != 1 { diff --git a/pkg/util/util.go b/pkg/util/util.go index e29ae945..55f92a65 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,9 +1,11 @@ package util import ( + "fmt" "io" "log/slog" "math/big" + "strings" ) func PadKeyTo32Bytes(key *big.Int) []byte { @@ -21,3 +23,50 @@ func NewTestLogger(w io.Writer) *slog.Logger { }) return slog.New(testLogger) } + +// 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 +}