diff --git a/go.mod b/go.mod index ea27fae..45fe945 100644 --- a/go.mod +++ b/go.mod @@ -4,23 +4,23 @@ go 1.23 require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 - github.com/ethereum/go-ethereum v1.14.11 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 - google.golang.org/grpc v1.64.0 - google.golang.org/protobuf v1.34.2 github.com/consensys/gnark-crypto v0.12.1 - github.com/crate-crypto/go-kzg-4844 v1.0.0 - github.com/holiman/uint256 v1.3.1 - github.com/stretchr/testify v1.9.0 github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect + github.com/crate-crypto/go-kzg-4844 v1.0.0 github.com/davecgh/go-spew v1.1.1 // indirect + github.com/ethereum/go-ethereum v1.14.11 github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 + github.com/holiman/uint256 v1.3.1 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -37,10 +37,7 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect - github.com/rs/zerolog v1.33.0 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/supranational/blst v0.3.13 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect diff --git a/go.sum b/go.sum index f7c640a..22704f8 100644 --- a/go.sum +++ b/go.sum @@ -36,7 +36,6 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I= @@ -66,7 +65,6 @@ github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -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/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -112,8 +110,6 @@ github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7 github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= @@ -147,9 +143,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.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 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= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= @@ -183,11 +176,8 @@ golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= diff --git a/internal/eth/bundle.go b/internal/eth/bundle.go index c40b6d1..b8d4ca4 100644 --- a/internal/eth/bundle.go +++ b/internal/eth/bundle.go @@ -1,3 +1,5 @@ +// Package eth provides functionalities related to Ethereum interactions, +// including sending transaction bundles via Flashbots. package eth import ( @@ -8,14 +10,17 @@ import ( "io" "net/http" + "log/slog" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/rs/zerolog/log" ) type JSONRPCResponse struct { - Result json.RawMessage `json:"result"` - RPCError + Result json.RawMessage `json:"result"` + RPCError RPCError `json:"error"` + ID int `json:"id,omitempty"` + Jsonrpc string `json:"jsonrpc,omitempty"` } type RPCError struct { @@ -30,16 +35,23 @@ type FlashbotsPayload struct { ID int `json:"id"` } + +// SendBundle sends a signed transaction bundle to the specified RPC URL. +// It returns the result as a string or an error if the operation fails. func SendBundle(rpcurl string, signedTx *types.Transaction, blkNum uint64) (string, error) { + // Marshal the signed transaction into binary format. binary, err := signedTx.MarshalBinary() if err != nil { - log.Error(). - Err(err). - Msg("Error marshaling transaction") + slog.Error("Error marshaling transaction", + "error", err, + ) return "", err } + // Encode the block number in hex. blockNum := hexutil.EncodeUint64(blkNum) + + // Construct the Flashbots payload. payload := FlashbotsPayload{ Jsonrpc: "2.0", Method: "eth_sendBundle", @@ -54,65 +66,73 @@ func SendBundle(rpcurl string, signedTx *types.Transaction, blkNum uint64) (stri ID: 1, } + // Marshal the payload into JSON. payloadBytes, err := json.Marshal(payload) if err != nil { - log.Error(). - Err(err). - Msg("Error marshaling payload") + slog.Error("Error marshaling payload", + "error", err, + ) return "", err } + // Create a context with a timeout. ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() + // Create a new HTTP POST request with the JSON payload. req, err := http.NewRequestWithContext(ctx, http.MethodPost, rpcurl, bytes.NewReader(payloadBytes)) if err != nil { - log.Error(). - Err(err). - Msg("An error occurred creating the request") + slog.Error("An error occurred creating the request", + "error", err, + ) return "", err } req.Header.Add("Content-Type", "application/json") + // Execute the HTTP request. resp, err := http.DefaultClient.Do(req) if err != nil { - log.Error(). - Err(err). - Msg("An error occurred during the request") + slog.Error("An error occurred during the request", + "error", err, + ) return "", err } defer resp.Body.Close() + // Read the response body. body, err := io.ReadAll(resp.Body) if err != nil { - log.Error(). - Err(err). - Msg("An error occurred reading the response body") + slog.Error("An error occurred reading the response body", + "error", err, + ) return "", err } + // Unmarshal the response into JSONRPCResponse struct. var rpcResp JSONRPCResponse err = json.Unmarshal(body, &rpcResp) if err != nil { - log.Error(). - Err(err). - Msg("Failed to unmarshal response") + slog.Error("Failed to unmarshal response", + "error", err, + ) return "", err } - if rpcResp.Code != 0 { - log.Error(). - Int("code", rpcResp.Code). - Str("message", rpcResp.Message). - Msg("Received error from RPC") - return "", fmt.Errorf("request failed %d: %s", rpcResp.Code, rpcResp.Message) + // Check for RPC errors. + if rpcResp.RPCError.Code != 0 { + slog.Error("Received error from RPC", + "code", rpcResp.RPCError.Code, + "message", rpcResp.RPCError.Message, + ) + return "", fmt.Errorf("request failed %d: %s", rpcResp.RPCError.Code, rpcResp.RPCError.Message) } + // Marshal the result to a string. resultStr, err := json.Marshal(rpcResp.Result) if err != nil { - log.Error(). - Err(err). - Msg("Failed to marshal result") + slog.Error("Failed to marshal result", + "error", err, + ) return "", err } diff --git a/internal/eth/sendtx.go b/internal/eth/sendtx.go index c4c44e1..144dc69 100644 --- a/internal/eth/sendtx.go +++ b/internal/eth/sendtx.go @@ -8,6 +8,7 @@ import ( "context" "crypto/ecdsa" "errors" + "log/slog" "math/big" "os" "strconv" @@ -23,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/holiman/uint256" bb "github.com/primev/preconf_blob_bidder/internal/mevcommit" - "github.com/rs/zerolog/log" "golang.org/x/exp/rand" ) @@ -36,21 +36,18 @@ func init() { if timeoutStr != "" { timeoutSeconds, err := strconv.Atoi(timeoutStr) if err != nil { - log.Warn(). - Str("DEFAULT_TIMEOUT", timeoutStr). - Msg("Invalid DEFAULT_TIMEOUT value. Using default of 15 seconds.") + slog.Default().Warn("Invalid DEFAULT_TIMEOUT value. Using default of 15 seconds.", + slog.String("DEFAULT_TIMEOUT", timeoutStr)) defaultTimeout = 15 * time.Second } else { defaultTimeout = time.Duration(timeoutSeconds) * time.Second - log.Info(). - Dur("defaultTimeout", defaultTimeout). - Msg("defaultTimeout loaded from environment") + slog.Default().Info("defaultTimeout loaded from environment", + slog.Duration("defaultTimeout", defaultTimeout)) } } else { defaultTimeout = 15 * time.Second - log.Info(). - Dur("defaultTimeout", defaultTimeout). - Msg("DEFAULT_TIMEOUT not set. Using default of 15 seconds.") + slog.Default().Info("DEFAULT_TIMEOUT not set. Using default of 15 seconds.", + slog.Duration("defaultTimeout", defaultTimeout)) } } @@ -63,30 +60,27 @@ func SelfETHTransfer(client *ethclient.Client, authAcct bb.AuthAcct, value *big. // Get the account's nonce nonce, err := client.PendingNonceAt(ctx, authAcct.Address) if err != nil { - log.Error(). - Err(err). - Str("function", "PendingNonceAt"). - Msg("Failed to get pending nonce") + slog.Default().Error("Failed to get pending nonce", + slog.String("function", "PendingNonceAt"), + slog.Any("error", err)) return nil, 0, err } // Get the current base fee per gas from the latest block header header, err := client.HeaderByNumber(ctx, nil) if err != nil { - log.Error(). - Err(err). - Str("function", "HeaderByNumber"). - Msg("Failed to get latest block header") + slog.Default().Error("Failed to get latest block header", + slog.String("function", "HeaderByNumber"), + slog.Any("error", err)) return nil, 0, err } // Get the chain ID chainID, err := client.NetworkID(ctx) if err != nil { - log.Error(). - Err(err). - Str("function", "NetworkID"). - Msg("Failed to get network ID") + slog.Default().Error("Failed to get network ID", + slog.String("function", "NetworkID"), + slog.Any("error", err)) return nil, 0, err } @@ -109,17 +103,15 @@ func SelfETHTransfer(client *ethclient.Client, authAcct bb.AuthAcct, value *big. signer := types.LatestSignerForChainID(chainID) signedTx, err := types.SignTx(tx, signer, authAcct.PrivateKey) if err != nil { - log.Error(). - Err(err). - Str("function", "SignTx"). - Msg("Failed to sign transaction") + slog.Default().Error("Failed to sign transaction", + slog.String("function", "SignTx"), + slog.Any("error", err)) return nil, 0, err } - log.Info(). - Str("tx_hash", signedTx.Hash().Hex()). - Uint64("block_number", blockNumber). - Msg("Self ETH transfer transaction created and signed") + slog.Default().Info("Self ETH transfer transaction created and signed", + slog.String("tx_hash", signedTx.Hash().Hex()), + slog.Uint64("block_number", blockNumber)) return signedTx, blockNumber + offset, nil } @@ -128,10 +120,10 @@ func SelfETHTransfer(client *ethclient.Client, authAcct bb.AuthAcct, value *big. func ExecuteBlobTransaction(client *ethclient.Client, authAcct bb.AuthAcct, numBlobs int, offset uint64) (*types.Transaction, uint64, error) { pubKey, ok := authAcct.PrivateKey.Public().(*ecdsa.PublicKey) - if !ok || pubKey == nil { - log.Error().Msg("failed to cast public key to ECDSA") + if !ok || pubKey == nil { + slog.Default().Error("Failed to cast public key to ECDSA") return nil, 0, errors.New("failed to cast public key to ECDSA") - } + } var ( gasLimit = uint64(500_000) @@ -147,27 +139,24 @@ func ExecuteBlobTransaction(client *ethclient.Client, authAcct bb.AuthAcct, numB publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { - log.Error(). - Msg("Failed to cast public key to ECDSA") + slog.Default().Error("Failed to cast public key to ECDSA") return nil, 0, errors.New("failed to cast public key to ECDSA") } fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) nonce, err := client.PendingNonceAt(ctx, authAcct.Address) if err != nil { - log.Error(). - Err(err). - Str("function", "PendingNonceAt"). - Msg("Failed to get pending nonce") + slog.Default().Error("Failed to get pending nonce", + slog.String("function", "PendingNonceAt"), + slog.Any("error", err)) return nil, 0, err } header, err := client.HeaderByNumber(ctx, nil) if err != nil { - log.Error(). - Err(err). - Str("function", "HeaderByNumber"). - Msg("Failed to get latest block header") + slog.Default().Error("Failed to get latest block header", + slog.String("function", "HeaderByNumber"), + slog.Any("error", err)) return nil, 0, err } @@ -175,10 +164,9 @@ func ExecuteBlobTransaction(client *ethclient.Client, authAcct bb.AuthAcct, numB chainID, err := client.NetworkID(ctx) if err != nil { - log.Error(). - Err(err). - Str("function", "NetworkID"). - Msg("Failed to get network ID") + slog.Default().Error("Failed to get network ID", + slog.String("function", "NetworkID"), + slog.Any("error", err)) return nil, 0, err } @@ -217,28 +205,25 @@ func ExecuteBlobTransaction(client *ethclient.Client, authAcct bb.AuthAcct, numB // Create the transaction options with the private key and chain ID auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) if err != nil { - log.Error(). - Err(err). - Str("function", "NewKeyedTransactorWithChainID"). - Msg("Failed to create keyed transactor") + slog.Default().Error("Failed to create keyed transactor", + slog.String("function", "NewKeyedTransactorWithChainID"), + slog.Any("error", err)) return nil, 0, err } // Sign the transaction signedTx, err := auth.Signer(auth.From, tx) if err != nil { - log.Error(). - Err(err). - Str("function", "Signer"). - Msg("Failed to sign blob transaction") + slog.Default().Error("Failed to sign blob transaction", + slog.String("function", "Signer"), + slog.Any("error", err)) return nil, 0, err } - log.Info(). - Str("tx_hash", signedTx.Hash().Hex()). - Uint64("block_number", blockNumber). - Int("num_blobs", numBlobs). - Msg("Blob transaction created and signed") + slog.Default().Info("Blob transaction created and signed", + slog.String("tx_hash", signedTx.Hash().Hex()), + slog.Uint64("block_number", blockNumber), + slog.Int("num_blobs", numBlobs)) return signedTx, blockNumber + offset, nil } @@ -290,9 +275,9 @@ func randFieldElement() [32]byte { bytes := make([]byte, 32) _, err := rand.Read(bytes) if err != nil { - log.Fatal(). - Err(err). - Msg("Failed to generate random field element") + slog.Default().Error("Failed to generate random field element", + slog.Any("error", err)) + os.Exit(1) } var r fr.Element r.SetBytes(bytes) diff --git a/internal/mevcommit/bidderapi.go b/internal/mevcommit/bidderapi.go index 0984f32..66f935c 100644 --- a/internal/mevcommit/bidderapi.go +++ b/internal/mevcommit/bidderapi.go @@ -11,11 +11,21 @@ import ( "strings" "time" + "log/slog" + "github.com/ethereum/go-ethereum/core/types" pb "github.com/primev/preconf_blob_bidder/internal/bidderpb" - "github.com/rs/zerolog/log" ) +// Initialize the logger with JSON format. +// It's recommended to configure the logger in the main package. +// If you need to initialize it here, uncomment the following init function. + +// func init() { +// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) +// slog.SetDefault(logger) +// } + // BidderInterface defines the methods that Bidder and MockBidderClient must implement. type BidderInterface interface { SendBid(input interface{}, amount string, blockNumber, decayStart, decayEnd int64) (pb.Bidder_SendBidClient, error) @@ -49,81 +59,81 @@ func SendPreconfBid(bidderClient BidderInterface, input interface{}, blockNumber case string: // Input is a string, process it as a transaction hash txHash := strings.TrimPrefix(v, "0x") - log.Info(). - Str("txHash", txHash). - Str("amount", amount). - Int64("blockNumber", blockNumber). - Int64("decayStart", decayStart). - Int64("decayEnd", decayEnd). - Msg("Sending bid with transaction hash") + slog.Info("Sending bid with transaction hash", + "txHash", txHash, + "amount", amount, + "blockNumber", blockNumber, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) // Send the bid with tx hash string responseClient, err = bidderClient.SendBid([]string{txHash}, amount, blockNumber, decayStart, decayEnd) case *types.Transaction: // Check for nil transaction if v == nil { - log.Warn().Msg("Transaction is nil, cannot send bid.") + slog.Warn("Transaction is nil, cannot send bid.") return } // Input is a transaction object, send the transaction object - log.Info(). - Str("txHash", v.Hash().String()). - Str("amount", amount). - Int64("blockNumber", blockNumber). - Int64("decayStart", decayStart). - Int64("decayEnd", decayEnd). - Msg("Sending bid with transaction payload") + slog.Info("Sending bid with transaction payload", + "txHash", v.Hash().String(), + "amount", amount, + "blockNumber", blockNumber, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) // Send the bid with the full transaction object responseClient, err = bidderClient.SendBid([]*types.Transaction{v}, amount, blockNumber, decayStart, decayEnd) default: - log.Warn(). - Msg("Unsupported input type, must be string or *types.Transaction") + slog.Warn("Unsupported input type, must be string or *types.Transaction", + "inputType", fmt.Sprintf("%T", input), + ) return } // Check if there was an error sending the bid if err != nil { - log.Warn(). - Err(err). - Str("txHash", fmt.Sprintf("%v", input)). - Str("amount", amount). - Int64("blockNumber", blockNumber). - Int64("decayStart", decayStart). - Int64("decayEnd", decayEnd). - Msg("Failed to send bid") + slog.Warn("Failed to send bid", + "err", err, + "txHash", fmt.Sprintf("%v", input), + "amount", amount, + "blockNumber", blockNumber, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) return } // Call Recv() to handle the response and complete the expectation in your tests _, recvErr := responseClient.Recv() if recvErr == io.EOF { - log.Info(). - Str("txHash", fmt.Sprintf("%v", input)). - Int64("blockNumber", blockNumber). - Float64("amount (ETH)", randomEthAmount). - Int64("decayStart", decayStart). - Int64("decayEnd", decayEnd). - Msg("Bid response received: EOF") + slog.Info("Bid response received: EOF", + "txHash", fmt.Sprintf("%v", input), + "blockNumber", blockNumber, + "amount_ETH", randomEthAmount, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) } else if recvErr != nil { - log.Warn(). - Err(recvErr). - Str("txHash", fmt.Sprintf("%v", input)). - Int64("blockNumber", blockNumber). - Int64("decayStart", decayStart). - Int64("decayEnd", decayEnd). - Msg("Error receiving bid response") + slog.Warn("Error receiving bid response", + "err", recvErr, + "txHash", fmt.Sprintf("%v", input), + "blockNumber", blockNumber, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) } else { - log.Info(). - Int64("block", blockNumber). - Float64("amount (ETH)", randomEthAmount). - Int64("decayStart", decayStart). - Int64("decayEnd", decayEnd). - Msg("Sent preconfirmation bid and received response") + slog.Info("Sent preconfirmation bid and received response", + "block", blockNumber, + "amount_ETH", randomEthAmount, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) } } - // SendBid handles sending a bid request after preparing the input data. func (b *Bidder) SendBid(input interface{}, amount string, blockNumber, decayStart, decayEnd int64) (pb.Bidder_SendBidClient, error) { txHashes, rawTransactions, err := b.parseInput(input) @@ -159,17 +169,17 @@ func (b *Bidder) parseInput(input interface{}) ([]string, []string, error) { for i, tx := range v { rlpEncodedTx, err := tx.MarshalBinary() if err != nil { - log.Error(). - Err(err). - Msg("Failed to marshal transaction to raw format") + slog.Error("Failed to marshal transaction to raw format", + "err", err, + ) return nil, nil, fmt.Errorf("failed to marshal transaction: %w", err) } rawTransactions[i] = hex.EncodeToString(rlpEncodedTx) } default: - log.Warn(). - Str("inputType", fmt.Sprintf("%T", input)). - Msg("Unsupported input type, must be []string or []*types.Transaction") + slog.Warn("Unsupported input type, must be []string or []*types.Transaction", + "inputType", fmt.Sprintf("%T", input), + ) return nil, nil, fmt.Errorf("unsupported input type: %T", input) } @@ -199,9 +209,9 @@ func (b *Bidder) sendBidRequest(bidRequest *pb.Bid) (pb.Bidder_SendBidClient, er ctx := context.Background() response, err := b.client.SendBid(ctx, bidRequest) if err != nil { - log.Error(). - Err(err). - Msg("Failed to send bid") + slog.Error("Failed to send bid", + "err", err, + ) return nil, fmt.Errorf("failed to send bid: %w", err) } @@ -217,19 +227,19 @@ func (b *Bidder) receiveBidResponses(response pb.Bidder_SendBidClient) { break } if err != nil { - log.Error(). - Err(err). - Msg("Failed to receive bid response") + slog.Error("Failed to receive bid response", + "err", err, + ) continue } - log.Info(). - Interface("commitmentDetails", msg). - Msg("Bid accepted") + slog.Info("Bid accepted", + "commitmentDetails", msg, + ) } startTimeBeforeSaveResponses := time.Now() - log.Info(). - Time("time", startTimeBeforeSaveResponses). - Msg("End Time") + slog.Info("End Time", + "time", startTimeBeforeSaveResponses, + ) } diff --git a/internal/mevcommit/client.go b/internal/mevcommit/client.go index c7b7051..b5e079e 100644 --- a/internal/mevcommit/client.go +++ b/internal/mevcommit/client.go @@ -7,6 +7,7 @@ import ( "context" "crypto/ecdsa" "fmt" + "log/slog" "math" "time" @@ -20,7 +21,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/rpc" - "github.com/rs/zerolog/log" "google.golang.org/grpc/credentials/insecure" ) @@ -60,10 +60,10 @@ func NewBidderClient(cfg BidderConfig) (*Bidder, error) { // Establish a gRPC connection to the bidder service conn, err := grpc.NewClient(cfg.ServerAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - log.Error(). - Err(err). - Str("server_address", cfg.ServerAddress). - Msg("Failed to connect to gRPC server") + slog.Error("Failed to connect to gRPC server", + "error", err, + "server_address", cfg.ServerAddress, + ) return nil, err } @@ -87,18 +87,18 @@ func NewGethClient(endpoint string) (*ethclient.Client, error) { // Use DialContext to establish a connection with the 15-second timeout client, err := rpc.DialContext(ctx, endpoint) if err != nil { - log.Error(). - Err(err). - Str("endpoint", MaskEndpoint(endpoint)). - Msg("Failed to dial Ethereum RPC endpoint") + slog.Error("Failed to dial Ethereum RPC endpoint", + "error", err, + "endpoint", MaskEndpoint(endpoint), + ) return nil, err } // Create a new ethclient.Client using the RPC client ec := ethclient.NewClient(client) - log.Info(). - Str("endpoint", MaskEndpoint(endpoint)). - Msg("Connected to Ethereum RPC endpoint") + slog.Info("Connected to Ethereum RPC endpoint", + "endpoint", MaskEndpoint(endpoint), + ) return ec, nil } @@ -113,16 +113,16 @@ func NewGethClient(endpoint string) (*ethclient.Client, error) { // - An AuthAcct struct, or an error if authentication fails. func AuthenticateAddress(privateKeyHex string, client *ethclient.Client) (AuthAcct, error) { if privateKeyHex == "" { - log.Warn().Msg("No private key provided; proceeding without authentication") + slog.Warn("No private key provided; proceeding without authentication") return AuthAcct{}, nil } // Convert the hex-encoded private key to an ECDSA private key privateKey, err := crypto.HexToECDSA(privateKeyHex) if err != nil { - log.Error(). - Err(err). - Msg("Failed to load private key") + slog.Error("Failed to load private key", + "error", err, + ) return AuthAcct{}, err } @@ -130,7 +130,7 @@ func AuthenticateAddress(privateKeyHex string, client *ethclient.Client) (AuthAc publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { - log.Error().Msg("Failed to assert public key type") + slog.Error("Failed to assert public key type") return AuthAcct{}, fmt.Errorf("failed to assert public key type") } @@ -143,25 +143,25 @@ func AuthenticateAddress(privateKeyHex string, client *ethclient.Client) (AuthAc chainID, err := client.ChainID(ctx) if err != nil { - log.Error(). - Err(err). - Msg("Failed to fetch chain ID") + slog.Error("Failed to fetch chain ID", + "error", err, + ) return AuthAcct{}, err } // Create the transaction options with the private key and chain ID auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) if err != nil { - log.Error(). - Err(err). - Msg("Failed to create authorized transactor") + slog.Error("Failed to create authorized transactor", + "error", err, + ) return AuthAcct{}, err } // Return the AuthAcct struct containing the private key, public key, address, and transaction options - log.Info(). - Str("address", address.Hex()). - Msg("Authenticated account") + slog.Info("Authenticated account", + "address", address.Hex(), + ) return AuthAcct{ PrivateKey: privateKey, @@ -190,26 +190,26 @@ func ConnectRPCClientWithRetries(rpcEndpoint string, maxRetries int, timeout tim rpcClient, err = ethclient.DialContext(ctx, rpcEndpoint) if err == nil { - log.Info(). - Str("rpc_endpoint", MaskEndpoint(rpcEndpoint)). - Int("attempt", i+1). - Msg("Successfully connected to RPC client") + slog.Info("Successfully connected to RPC client", + "rpc_endpoint", MaskEndpoint(rpcEndpoint), + "attempt", i+1, + ) return rpcClient } - log.Warn(). - Err(err). - Str("rpc_endpoint", MaskEndpoint(rpcEndpoint)). - Int("attempt", i+1). - Msg("Failed to connect to RPC client, retrying...") + slog.Warn("Failed to connect to RPC client, retrying...", + "error", err, + "rpc_endpoint", MaskEndpoint(rpcEndpoint), + "attempt", i+1, + ) time.Sleep(10 * time.Duration(math.Pow(2, float64(i))) * time.Second) // Exponential backoff } - log.Error(). - Err(err). - Str("rpc_endpoint", MaskEndpoint(rpcEndpoint)). - Int("max_retries", maxRetries). - Msg("Failed to connect to RPC client after maximum retries") + slog.Error("Failed to connect to RPC client after maximum retries", + "error", err, + "rpc_endpoint", MaskEndpoint(rpcEndpoint), + "max_retries", maxRetries, + ) return nil } @@ -226,10 +226,10 @@ func ConnectWSClient(wsEndpoint string) (*ethclient.Client, error) { if err == nil { return wsClient, nil } - log.Warn(). - Err(err). - Str("ws_endpoint", MaskEndpoint(wsEndpoint)). - Msg("Failed to connect to WebSocket client, retrying in 10 seconds...") + slog.Warn("Failed to connect to WebSocket client, retrying in 10 seconds...", + "error", err, + "ws_endpoint", MaskEndpoint(wsEndpoint), + ) time.Sleep(10 * time.Second) } } @@ -250,10 +250,10 @@ func ReconnectWSClient(wsEndpoint string, headers chan *types.Header) (*ethclien for i := 0; i < 10; i++ { // Retry logic for WebSocket connection wsClient, err = ConnectWSClient(wsEndpoint) if err == nil { - log.Info(). - Str("ws_endpoint", MaskEndpoint(wsEndpoint)). - Int("attempt", i+1). - Msg("WebSocket client reconnected") + slog.Info("WebSocket client reconnected", + "ws_endpoint", MaskEndpoint(wsEndpoint), + "attempt", i+1, + ) // Create a context with a 15-second timeout for the subscription ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) @@ -264,24 +264,24 @@ func ReconnectWSClient(wsEndpoint string, headers chan *types.Header) (*ethclien return wsClient, sub } - log.Warn(). - Err(err). - Msg("Failed to subscribe to new headers after reconnecting") + slog.Warn("Failed to subscribe to new headers after reconnecting", + "error", err, + ) } - log.Warn(). - Err(err). - Str("ws_endpoint", MaskEndpoint(wsEndpoint)). - Int("attempt", i+1). - Msg("Failed to reconnect WebSocket client, retrying in 5 seconds...") + slog.Warn("Failed to reconnect WebSocket client, retrying in 5 seconds...", + "error", err, + "ws_endpoint", MaskEndpoint(wsEndpoint), + "attempt", i+1, + ) time.Sleep(5 * time.Second) } - log.Error(). - Err(err). - Str("ws_endpoint", MaskEndpoint(wsEndpoint)). - Int("max_retries", 10). - Msg("Failed to reconnect WebSocket client after maximum retries") + slog.Error("Failed to reconnect WebSocket client after maximum retries", + "error", err, + "ws_endpoint", MaskEndpoint(wsEndpoint), + "max_retries", 10, + ) return nil, nil } diff --git a/internal/mevcommit/contracts.go b/internal/mevcommit/contracts.go index c063c82..04960a7 100644 --- a/internal/mevcommit/contracts.go +++ b/internal/mevcommit/contracts.go @@ -10,13 +10,14 @@ import ( "strings" "time" + "log/slog" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" - "github.com/rs/zerolog/log" ) // Global contract addresses @@ -35,10 +36,11 @@ func init() { if _, err := os.Stat(envFile); err == nil { if err := loadEnvFile(envFile); err != nil { - log.Fatal(). - Err(err). - Str("env_file", envFile). - Msg("Error loading .env file") + slog.Error("Error loading .env file", + "err", err, + "env_file", envFile, + ) + return } } @@ -62,21 +64,21 @@ func init() { PreconfManagerAddress = common.HexToAddress(preconfManager) // Log loaded contract addresses - log.Info(). - Str("BidderRegistry", BidderRegistryAddress.Hex()). - Str("BlockTracker", BlockTrackerAddress.Hex()). - Str("PreconfManager", PreconfManagerAddress.Hex()). - Msg("Loaded contract addresses") + slog.Info("Loaded contract addresses", + "BidderRegistry", BidderRegistryAddress.Hex(), + "BlockTracker", BlockTrackerAddress.Hex(), + "PreconfManager", PreconfManagerAddress.Hex(), + ) } // loadEnvFile loads environment variables from a specified file. func loadEnvFile(filePath string) error { data, err := os.ReadFile(filePath) if err != nil { - log.Error(). - Err(err). - Str("file_path", filePath). - Msg("Failed to read environment file") + slog.Error("Failed to read environment file", + "err", err, + "file_path", filePath, + ) return err } @@ -94,9 +96,9 @@ func loadEnvFile(filePath string) error { os.Setenv(key, value) } - log.Info(). - Str("file_path", filePath). - Msg("Environment variables loaded from file") + slog.Info("Environment variables loaded from file", + "file_path", filePath, + ) return nil } @@ -131,25 +133,25 @@ type CommitmentStoredEvent struct { func LoadABI(filePath string) (abi.ABI, error) { data, err := os.ReadFile(filePath) if err != nil { - log.Error(). - Err(err). - Str("file_path", filePath). - Msg("Failed to load ABI file") + slog.Error("Failed to load ABI file", + "err", err, + "file_path", filePath, + ) return abi.ABI{}, err } parsedABI, err := abi.JSON(strings.NewReader(string(data))) if err != nil { - log.Error(). - Err(err). - Str("file_path", filePath). - Msg("Failed to parse ABI file") + slog.Error("Failed to parse ABI file", + "err", err, + "file_path", filePath, + ) return abi.ABI{}, err } - log.Info(). - Str("file_path", filePath). - Msg("ABI file loaded and parsed successfully") + slog.Info("ABI file loaded and parsed successfully", + "file_path", filePath, + ) return parsedABI, nil } @@ -175,24 +177,23 @@ func WindowHeight(client *ethclient.Client) (*big.Int, error) { var currentWindowResult []interface{} err = blockTrackerContract.Call(nil, ¤tWindowResult, "getCurrentWindow") if err != nil { - log.Error(). - Err(err). - Str("function", "getCurrentWindow"). - Msg("Failed to get current window") + slog.Error("Failed to get current window", + "err", err, + "function", "getCurrentWindow", + ) return nil, fmt.Errorf("failed to get current window: %v", err) } // Extract the current window as *big.Int currentWindow, ok := currentWindowResult[0].(*big.Int) if !ok { - log.Error(). - Msg("Failed to convert current window to *big.Int") + slog.Error("Failed to convert current window to *big.Int") return nil, fmt.Errorf("conversion to *big.Int failed") } - log.Info(). - Str("current_window", currentWindow.String()). - Msg("Retrieved current bidding window height") + slog.Info("Retrieved current bidding window height", + "current_window", currentWindow.String(), + ) return currentWindow, nil } @@ -218,24 +219,23 @@ func GetMinDeposit(client *ethclient.Client) (*big.Int, error) { var minDepositResult []interface{} err = bidderRegistryContract.Call(nil, &minDepositResult, "minDeposit") if err != nil { - log.Error(). - Err(err). - Str("function", "minDeposit"). - Msg("Failed to call minDeposit function") + slog.Error("Failed to call minDeposit function", + "err", err, + "function", "minDeposit", + ) return nil, fmt.Errorf("failed to call minDeposit function: %v", err) } // Extract the minDeposit as *big.Int minDeposit, ok := minDepositResult[0].(*big.Int) if !ok { - log.Error(). - Msg("Failed to convert minDeposit to *big.Int") + slog.Error("Failed to convert minDeposit to *big.Int") return nil, fmt.Errorf("failed to convert minDeposit to *big.Int") } - log.Info(). - Str("min_deposit", minDeposit.String()). - Msg("Retrieved minimum deposit amount") + slog.Info("Retrieved minimum deposit amount", + "min_deposit", minDeposit.String(), + ) return minDeposit, nil } @@ -271,40 +271,40 @@ func DepositIntoWindow(client *ethclient.Client, depositWindow *big.Int, authAcc // Prepare and send the transaction to deposit into the specific window tx, err := bidderRegistryContract.Transact(authAcct.Auth, "depositForSpecificWindow", depositWindow) if err != nil { - log.Error(). - Err(err). - Str("function", "depositForSpecificWindow"). - Msg("Failed to create deposit transaction") + slog.Error("Failed to create deposit transaction", + "err", err, + "function", "depositForSpecificWindow", + ) return nil, fmt.Errorf("failed to create transaction: %v", err) } - log.Info(). - Str("tx_hash", tx.Hash().Hex()). - Str("window", depositWindow.String()). - Msg("Deposit transaction sent") + slog.Info("Deposit transaction sent", + "tx_hash", tx.Hash().Hex(), + "window", depositWindow.String(), + ) // Wait for the transaction to be mined (optional) ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() receipt, err := bind.WaitMined(ctx, client, tx) if err != nil { - log.Error(). - Err(err). - Str("tx_hash", tx.Hash().Hex()). - Msg("Transaction mining error") + slog.Error("Transaction mining error", + "err", err, + "tx_hash", tx.Hash().Hex(), + ) return nil, fmt.Errorf("transaction mining error: %v", err) } // Check the transaction status if receipt.Status == 1 { - log.Info(). - Str("tx_hash", tx.Hash().Hex()). - Msg("Deposit transaction successful") + slog.Info("Deposit transaction successful", + "tx_hash", tx.Hash().Hex(), + ) return tx, nil } else { - log.Error(). - Str("tx_hash", tx.Hash().Hex()). - Msg("Deposit transaction failed") + slog.Error("Deposit transaction failed", + "tx_hash", tx.Hash().Hex(), + ) return nil, fmt.Errorf("transaction failed") } } @@ -332,24 +332,23 @@ func GetDepositAmount(client *ethclient.Client, address common.Address, window b var depositResult []interface{} err = bidderRegistryContract.Call(nil, &depositResult, "getDeposit", address, window) if err != nil { - log.Error(). - Err(err). - Str("function", "getDeposit"). - Msg("Failed to call getDeposit function") + slog.Error("Failed to call getDeposit function", + "err", err, + "function", "getDeposit", + ) return nil, fmt.Errorf("failed to call getDeposit function: %v", err) } // Extract the deposit amount as *big.Int depositAmount, ok := depositResult[0].(*big.Int) if !ok { - log.Error(). - Msg("Failed to convert deposit amount to *big.Int") + slog.Error("Failed to convert deposit amount to *big.Int") return nil, fmt.Errorf("failed to convert deposit amount to *big.Int") } - log.Info(). - Str("deposit_amount", depositAmount.String()). - Msg("Retrieved deposit amount for address and window") + slog.Info("Retrieved deposit amount for address and window", + "deposit_amount", depositAmount.String(), + ) return depositAmount, nil } @@ -376,40 +375,40 @@ func WithdrawFromWindow(client *ethclient.Client, authAcct *AuthAcct, window *bi // Prepare the withdrawal transaction withdrawalTx, err := bidderRegistryContract.Transact(authAcct.Auth, "withdrawBidderAmountFromWindow", authAcct.Address, window) if err != nil { - log.Error(). - Err(err). - Str("function", "withdrawBidderAmountFromWindow"). - Msg("Failed to create withdrawal transaction") + slog.Error("Failed to create withdrawal transaction", + "err", err, + "function", "withdrawBidderAmountFromWindow", + ) return nil, fmt.Errorf("failed to create withdrawal transaction: %v", err) } - log.Info(). - Str("tx_hash", withdrawalTx.Hash().Hex()). - Str("window", window.String()). - Msg("Withdrawal transaction sent") + slog.Info("Withdrawal transaction sent", + "tx_hash", withdrawalTx.Hash().Hex(), + "window", window.String(), + ) // Wait for the withdrawal transaction to be mined ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) defer cancel() withdrawalReceipt, err := bind.WaitMined(ctx, client, withdrawalTx) if err != nil { - log.Error(). - Err(err). - Str("tx_hash", withdrawalTx.Hash().Hex()). - Msg("Withdrawal transaction mining error") + slog.Error("Withdrawal transaction mining error", + "err", err, + "tx_hash", withdrawalTx.Hash().Hex(), + ) return nil, fmt.Errorf("withdrawal transaction mining error: %v", err) } // Check the withdrawal transaction status if withdrawalReceipt.Status == 1 { - log.Info(). - Str("tx_hash", withdrawalTx.Hash().Hex()). - Msg("Withdrawal transaction successful") + slog.Info("Withdrawal transaction successful", + "tx_hash", withdrawalTx.Hash().Hex(), + ) return withdrawalTx, nil } else { - log.Error(). - Str("tx_hash", withdrawalTx.Hash().Hex()). - Msg("Withdrawal transaction failed") + slog.Error("Withdrawal transaction failed", + "tx_hash", withdrawalTx.Hash().Hex(), + ) return nil, fmt.Errorf("withdrawal failed") } } @@ -422,108 +421,109 @@ func WithdrawFromWindow(client *ethclient.Client, authAcct *AuthAcct, window *bi // // Note: The event listener uses a timeout of 15 seconds for subscription. func ListenForCommitmentStoredEvent(client *ethclient.Client) { - // Load the PreConfCommitmentStore contract ABI - contractAbi, err := LoadABI("abi/PreConfCommitmentStore.abi") - if err != nil { - log.Fatal(). - Err(err). - Str("contract", "PreConfCommitmentStore"). - Msg("Failed to load contract ABI") - } - - // Create a parent context that can be canceled to stop all operations - parentCtx, parentCancel := context.WithCancel(context.Background()) - defer parentCancel() - - // Subscribe to new block headers - headers := make(chan *types.Header) - sub, err := client.SubscribeNewHead(parentCtx, headers) - if err != nil { - log.Fatal(). - Err(err). - Msg("Failed to subscribe to new block headers") - } - - log.Info(). - Msg("Subscribed to new block headers for CommitmentStored events") - - // Listen for new block headers and filter logs for the CommitmentStored event - for { - select { - case err := <-sub.Err(): - log.Error(). - Err(err). - Msg("Error with header subscription") - // Cancel the parent context to terminate all ongoing log subscriptions - parentCancel() - return - - case header := <-headers: - query := ethereum.FilterQuery{ - Addresses: []common.Address{PreconfManagerAddress}, - FromBlock: header.Number, - ToBlock: header.Number, - } - - logs := make(chan types.Log) - ctxLogs, cancelLogs := context.WithTimeout(parentCtx, defaultTimeout) - - // Subscribe to filter logs with the derived context - subLogs, err := client.SubscribeFilterLogs(ctxLogs, query, logs) - if err != nil { - log.Error(). - Err(err). - Msg("Failed to subscribe to logs") - // Ensure cancelLogs is called to release resources - cancelLogs() - continue - } - - // Process incoming logs in a separate goroutine - go func() { - // Ensure cancelLogs is called when the goroutine exits - defer cancelLogs() - - for { - select { - case err := <-subLogs.Err(): - log.Error(). - Err(err). - Msg("Error with log subscription") - return - - case vLog := <-logs: - var event CommitmentStoredEvent - - // Unpack the log data into the CommitmentStoredEvent struct - err := contractAbi.UnpackIntoInterface(&event, "CommitmentStored", vLog.Data) - if err != nil { - log.Error(). - Err(err). - Msg("Failed to unpack log data") - continue - } - - // Log event details - log.Info(). - Str("commitment_index", fmt.Sprintf("%x", event.CommitmentIndex)). - Str("bidder", event.Bidder.Hex()). - Str("commiter", event.Commiter.Hex()). - Uint64("bid", event.Bid). - Uint64("block_number", event.BlockNumber). - Str("bid_hash", fmt.Sprintf("%x", event.BidHash)). - Uint64("decay_start_timestamp", event.DecayStartTimeStamp). - Uint64("decay_end_timestamp", event.DecayEndTimeStamp). - Str("txn_hash", event.TxnHash). - Str("commitment_hash", fmt.Sprintf("%x", event.CommitmentHash)). - Str("bid_signature", fmt.Sprintf("%x", event.BidSignature)). - Str("commitment_signature", fmt.Sprintf("%x", event.CommitmentSignature)). - Uint64("dispatch_timestamp", event.DispatchTimestamp). - Str("shared_secret_key", fmt.Sprintf("%x", event.SharedSecretKey)). - Msg("CommitmentStored Event Detected") - } - } - }() - } - } + // Load the PreConfCommitmentStore contract ABI + contractAbi, err := LoadABI("abi/PreConfCommitmentStore.abi") + if err != nil { + slog.Error("Failed to load contract ABI", + "contract", "PreConfCommitmentStore", + "err", err, + ) + return + } + + // Create a parent context that can be canceled to stop all operations + parentCtx, parentCancel := context.WithCancel(context.Background()) + defer parentCancel() + + // Subscribe to new block headers + headers := make(chan *types.Header) + sub, err := client.SubscribeNewHead(parentCtx, headers) + if err != nil { + slog.Error("Failed to subscribe to new block headers", + "err", err, + ) + return + } + + slog.Info("Subscribed to new block headers for CommitmentStored events") + + // Listen for new block headers and filter logs for the CommitmentStored event + for { + select { + case err := <-sub.Err(): + slog.Error("Error with header subscription", + "err", err, + ) + // Cancel the parent context to terminate all ongoing log subscriptions + parentCancel() + return + + case header := <-headers: + query := ethereum.FilterQuery{ + Addresses: []common.Address{PreconfManagerAddress}, + FromBlock: header.Number, + ToBlock: header.Number, + } + + logs := make(chan types.Log) + ctxLogs, cancelLogs := context.WithTimeout(parentCtx, defaultTimeout) + + // Subscribe to filter logs with the derived context + subLogs, err := client.SubscribeFilterLogs(ctxLogs, query, logs) + if err != nil { + slog.Error("Failed to subscribe to logs", + "err", err, + ) + // Ensure cancelLogs is called to release resources + cancelLogs() + continue + } + + // Process incoming logs in a separate goroutine + go func() { + // Ensure cancelLogs is called when the goroutine exits + defer cancelLogs() + + for { + select { + case err := <-subLogs.Err(): + slog.Error("Error with log subscription", + "err", err, + ) + return + + case vLog := <-logs: + var event CommitmentStoredEvent + + // Unpack the log data into the CommitmentStoredEvent struct + err := contractAbi.UnpackIntoInterface(&event, "CommitmentStored", vLog.Data) + if err != nil { + slog.Error("Failed to unpack log data", + "err", err, + ) + continue + } + + // Log event details + slog.Info("CommitmentStored Event Detected", + "commitment_index", fmt.Sprintf("%x", event.CommitmentIndex), + "bidder", event.Bidder.Hex(), + "commiter", event.Commiter.Hex(), + "bid", event.Bid, + "block_number", event.BlockNumber, + "bid_hash", fmt.Sprintf("%x", event.BidHash), + "decay_start_timestamp", event.DecayStartTimeStamp, + "decay_end_timestamp", event.DecayEndTimeStamp, + "txn_hash", event.TxnHash, + "commitment_hash", fmt.Sprintf("%x", event.CommitmentHash), + "bid_signature", fmt.Sprintf("%x", event.BidSignature), + "commitment_signature", fmt.Sprintf("%x", event.CommitmentSignature), + "dispatch_timestamp", event.DispatchTimestamp, + "shared_secret_key", fmt.Sprintf("%x", event.SharedSecretKey), + ) + } + } + }() + } + } } diff --git a/main.go b/main.go index d1e7217..2aefdb6 100644 --- a/main.go +++ b/main.go @@ -3,72 +3,77 @@ package main import ( "context" "fmt" + "log/slog" "math/big" "math/rand" "os" - "strings" "time" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ee "github.com/primev/preconf_blob_bidder/internal/eth" bb "github.com/primev/preconf_blob_bidder/internal/mevcommit" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" ) -func main() { - // Load the .env file before setting up the app - envFile := os.Getenv("ENV_FILE") - if envFile == "" { - envFile = ".env" - } - if _, err := os.Stat(envFile); err == nil { - if err := loadEnvFile(envFile); err != nil { - fmt.Fprintf(os.Stderr, "Error loading .env file: %v\n", err) - os.Exit(1) - } - } +// Define flag names as constants +const ( + FlagEnv = "env" + FlagBidderAddress = "bidder-address" + FlagUsePayload = "use-payload" + FlagRpcEndpoint = "rpc-endpoint" + FlagWsEndpoint = "ws-endpoint" + FlagPrivateKey = "private-key" + FlagOffset = "offset" + FlagBidAmount = "bid-amount" + FlagBidAmountStdDevPercentage = "bid-amount-std-dev-percentage" + FlagNumBlob = "num-blob" + FlagDefaultTimeout = "default-timeout" +) - zerolog.SetGlobalLevel(zerolog.InfoLevel) - log.Logger = log.Output(os.Stderr).With().Timestamp().Logger() +func main() { + // Initialize the slog logger with JSON handler and set log level to Info + logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + Level: slog.LevelInfo, + AddSource: true, + })) + slog.SetDefault(logger) app := &cli.App{ Name: "Preconf Bidder", Usage: "A tool for bidding in mev-commit preconfirmation auctions for blobs and transactions", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "env", + Name: FlagEnv, Usage: "Path to .env file", EnvVars: []string{"ENV_FILE"}, }, &cli.StringFlag{ - Name: "bidder-address", + Name: FlagBidderAddress, Usage: "Address of the bidder", EnvVars: []string{"BIDDER_ADDRESS"}, Value: "mev-commit-bidder:13524", }, &cli.BoolFlag{ - Name: "use-payload", + Name: FlagUsePayload, Usage: "Use payload for transactions", EnvVars: []string{"USE_PAYLOAD"}, Value: true, }, &cli.StringFlag{ - Name: "rpc-endpoint", + Name: FlagRpcEndpoint, Usage: "RPC endpoint when use-payload is false", EnvVars: []string{"RPC_ENDPOINT"}, Required: false, }, &cli.StringFlag{ - Name: "ws-endpoint", + Name: FlagWsEndpoint, Usage: "WebSocket endpoint for transactions", EnvVars: []string{"WS_ENDPOINT"}, Required: true, }, &cli.StringFlag{ - Name: "private-key", + Name: FlagPrivateKey, Usage: "Private key for signing transactions", EnvVars: []string{"PRIVATE_KEY"}, Required: true, @@ -76,63 +81,63 @@ func main() { TakesFile: false, }, &cli.Uint64Flag{ - Name: "offset", + Name: FlagOffset, Usage: "Offset value for transactions", EnvVars: []string{"OFFSET"}, Value: 1, }, &cli.Float64Flag{ - Name: "bid-amount", + Name: FlagBidAmount, Usage: "Amount to bid", EnvVars: []string{"BID_AMOUNT"}, Value: 0.001, }, &cli.Float64Flag{ - Name: "bid-amount-std-dev-percentage", + Name: FlagBidAmountStdDevPercentage, Usage: "Standard deviation percentage for bid amount", EnvVars: []string{"BID_AMOUNT_STD_DEV_PERCENTAGE"}, Value: 100.0, }, &cli.UintFlag{ - Name: "num-blob", + Name: FlagNumBlob, Usage: "Number of blobs to send (0 for ETH transfer)", EnvVars: []string{"NUM_BLOB"}, Value: 0, }, &cli.UintFlag{ - Name: "default-timeout", + Name: FlagDefaultTimeout, Usage: "Default timeout in seconds", EnvVars: []string{"DEFAULT_TIMEOUT"}, Value: 15, // Default to 15 seconds }, }, Action: func(c *cli.Context) error { - // Retrieve flag values - bidderAddress := c.String("bidder-address") - usePayload := c.Bool("use-payload") - rpcEndpoint := c.String("rpc-endpoint") - wsEndpoint := c.String("ws-endpoint") - privateKeyHex := c.String("private-key") - offset := c.Uint64("offset") - bidAmount := c.Float64("bid-amount") - stdDevPercentage := c.Float64("bid-amount-std-dev-percentage") - numBlob := c.Uint64("num-blob") - defaultTimeoutSeconds := c.Uint("default-timeout") // New variable + // Retrieve flag values using constants + bidderAddress := c.String(FlagBidderAddress) + usePayload := c.Bool(FlagUsePayload) + rpcEndpoint := c.String(FlagRpcEndpoint) + wsEndpoint := c.String(FlagWsEndpoint) + privateKeyHex := c.String(FlagPrivateKey) + offset := c.Uint64(FlagOffset) + bidAmount := c.Float64(FlagBidAmount) + stdDevPercentage := c.Float64(FlagBidAmountStdDevPercentage) + numBlob := c.Uint(FlagNumBlob) + defaultTimeoutSeconds := c.Uint(FlagDefaultTimeout) defaultTimeout := time.Duration(defaultTimeoutSeconds) * time.Second // Log the defaultTimeout value - log.Info(). - Str("bidderAddress", bidderAddress). - Str("rpcEndpoint", bb.MaskEndpoint(rpcEndpoint)). - Str("wsEndpoint", bb.MaskEndpoint(wsEndpoint)). - Uint64("offset", offset). - Bool("usePayload", usePayload). - Float64("bidAmount", bidAmount). - Float64("stdDevPercentage", stdDevPercentage). - Uint64("numBlob", numBlob). - Bool("privateKeyProvided", privateKeyHex != ""). - Uint("defaultTimeoutSeconds", defaultTimeoutSeconds). - Msg("Configuration values") + slog.Info("Configuration values", + "bidderAddress", bidderAddress, + "rpcEndpoint", bb.MaskEndpoint(rpcEndpoint), + "wsEndpoint", bb.MaskEndpoint(wsEndpoint), + "offset", offset, + "usePayload", usePayload, + "bidAmount", bidAmount, + "stdDevPercentage", stdDevPercentage, + "numBlob", numBlob, + "privateKeyProvided", privateKeyHex != "", + "defaultTimeoutSeconds", defaultTimeoutSeconds, + ) cfg := bb.BidderConfig{ ServerAddress: bidderAddress, @@ -142,10 +147,11 @@ func main() { bidderClient, err := bb.NewBidderClient(cfg) if err != nil { + slog.Error("Failed to connect to mev-commit bidder API", "error", err) return fmt.Errorf("failed to connect to mev-commit bidder API: %w", err) } - log.Info().Msg("Connected to mev-commit client") + slog.Info("Connected to mev-commit client") timeout := defaultTimeout // Use the defaultTimeout here @@ -154,43 +160,42 @@ func main() { if !usePayload { rpcClient = bb.ConnectRPCClientWithRetries(rpcEndpoint, 5, timeout) if rpcClient == nil { - log.Error(). - Str("rpcEndpoint", bb.MaskEndpoint(rpcEndpoint)). - Msg("Failed to connect to RPC client") + slog.Error("Failed to connect to RPC client", "rpcEndpoint", bb.MaskEndpoint(rpcEndpoint)) } else { - log.Info(). - Str("endpoint", bb.MaskEndpoint(rpcEndpoint)). - Msg("(rpc) Geth client connected") + slog.Info("Geth client connected (rpc)", + "endpoint", bb.MaskEndpoint(rpcEndpoint), + ) } } // Connect to WS client wsClient, err := bb.ConnectWSClient(wsEndpoint) if err != nil { + slog.Error("Failed to connect to WebSocket client", "error", err) return fmt.Errorf("failed to connect to WebSocket client: %w", err) } - log.Info(). - Str("endpoint", bb.MaskEndpoint(wsEndpoint)). - Msg("(ws) Geth client connected") + slog.Info("Geth client connected (ws)", + "endpoint", bb.MaskEndpoint(wsEndpoint), + ) headers := make(chan *types.Header) sub, err := wsClient.SubscribeNewHead(context.Background(), headers) if err != nil { + slog.Error("Failed to subscribe to new blocks", "error", err) return fmt.Errorf("failed to subscribe to new blocks: %w", err) } // Authenticate with private key authAcct, err := bb.AuthenticateAddress(privateKeyHex, wsClient) if err != nil { + slog.Error("Failed to authenticate private key", "error", err) return fmt.Errorf("failed to authenticate private key: %w", err) } for { select { case err := <-sub.Err(): - log.Warn(). - Err(err). - Msg("Subscription error") + slog.Warn("Subscription error", "error", err) wsClient, sub = bb.ReconnectWSClient(wsEndpoint, headers) continue case header := <-headers: @@ -206,23 +211,21 @@ func main() { } if signedTx == nil { - log.Error().Msg("Transaction was not signed or created.") + slog.Error("Transaction was not signed or created.") } else { - log.Info().Msg("Transaction sent successfully") + slog.Info("Transaction sent successfully") } // Check for errors before using signedTx if err != nil { - log.Error(). - Err(err). - Msg("Failed to execute transaction") + slog.Error("Failed to execute transaction", "error", err) } - log.Info(). - Uint64("blockNumber", header.Number.Uint64()). - Uint64("timestamp", header.Time). - Str("hash", header.Hash().String()). - Msg("New block received") + slog.Info("New block received", + "blockNumber", header.Number.Uint64(), + "timestamp", header.Time, + "hash", header.Hash().String(), + ) // Compute standard deviation in ETH stdDev := bidAmount * stdDevPercentage / 100.0 @@ -246,19 +249,17 @@ func main() { // Send as a flashbots bundle and send the preconf bid with the transaction hash _, err = ee.SendBundle(rpcEndpoint, signedTx, blockNumber) if err != nil { - log.Error(). - Str("rpcEndpoint", bb.MaskEndpoint(rpcEndpoint)). - Err(err). - Msg("Failed to send transaction") + slog.Error("Failed to send transaction", + "rpcEndpoint", bb.MaskEndpoint(rpcEndpoint), + "error", err, + ) } bb.SendPreconfBid(bidderClient, signedTx.Hash().String(), int64(blockNumber), randomEthAmount) } // Handle ExecuteBlob error if err != nil { - log.Error(). - Err(err). - Msg("Failed to execute transaction") + slog.Error("Failed to execute transaction", "error", err) continue // Skip to the next iteration } } @@ -268,33 +269,7 @@ func main() { // Run the app if err := app.Run(os.Args); err != nil { - log.Fatal(). - Err(err). - Msg("Application error") - } -} - -// loadEnvFile loads the specified .env file into the environment variables -func loadEnvFile(filename string) error { - data, err := os.ReadFile(filename) - if err != nil { - return err - } - lines := strings.Split(string(data), "\n") - for _, line := range lines { - // Ignore comments and empty lines - trimmed := strings.TrimSpace(line) - if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") { - continue - } - // Split key and value - parts := strings.SplitN(trimmed, "=", 2) - if len(parts) != 2 { - continue - } - key := strings.TrimSpace(parts[0]) - value := strings.TrimSpace(parts[1]) - os.Setenv(key, value) + slog.Error("Application error", "error", err) + os.Exit(1) } - return nil }