diff --git a/Dockerfile b/Dockerfile index f60e817..c07b8e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 +FROM golang:1.23 WORKDIR /app @@ -7,10 +7,7 @@ RUN go mod download COPY . . +# Build the binary as preconf_bot from main.go at the top level +RUN go build -o preconf_bot . -RUN go build -o getPreconf ./cmd - - -ENTRYPOINT ["./getPreconf"] - -CMD ["--ethtransfer"] +ENTRYPOINT ["./preconf_bot"] diff --git a/README.md b/README.md index 2d5a065..dd7e0e6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ## About -This repository provides an example workflow that gets preconfirmation bids from mev-commit for eth transfers. Transactions are sent directly to the builder as transaction payloads. +This repository provides an example workflow that gets preconfirmation bids from mev-commit for eth transfers. Transactions are sent directly to the builder as transaction payloads. Currently a fixed priority fee is used alongside a preconf bid amount. ## Requirements @@ -24,16 +24,19 @@ PRIVATE_KEY=private_key # L1 private key USE_PAYLOAD=true BIDDER_ADDRESS="127.0.0.1:13524" OFFSET=1 # of blocks in the future to ask for the preconf bid -ETH_TRANSFER="false" # choose eth transfer or blob -BLOB="true" +NUM_BLOB=0 # blob count of 0 will just send eth transfers BID_AMOUNT=0.0025 # preconf bid amount BID_AMOUNT_STD_DEV_PERCENTAGE=200 # amount of variation in the preconf bid amount (in %) +DEFAULT_TIMEOUT=0 ``` ## How to run Ensure that the mev-commit bidder node is running in the background. A quickstart can be found [here](https://docs.primev.xyz/get-started/quickstart), which will get the latest mev-commit version and start running it with an auto generated private key. ## Docker -Build the docker with `sudo docker-compose build` and then `sudo docker-compose up`. Best run with the [dockerized bidder node example](https://github.com/primev/bidder_node_docker) +Build the docker with `sudo docker-compose up --build`. Best run with the unofficial [dockerized bidder node example](https://github.com/primev/bidder_node_docker) ## Linting -Run linting with `golangci-lint run ./...` inside the repository folder \ No newline at end of file +Run linting with `golangci-lint run ./...` inside the repository folder + +## Testing +Run `go test -v ./...` in the main folder directory to run all the tests. \ No newline at end of file diff --git a/cmd/getPreconf.go b/cmd/getPreconf.go deleted file mode 100644 index ac39fbc..0000000 --- a/cmd/getPreconf.go +++ /dev/null @@ -1,371 +0,0 @@ -package main - -import ( - "context" - "fmt" - "math" - "math/big" - "math/rand" - "os" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/joho/godotenv" - ee "github.com/primev/preconf_blob_bidder/core/eth" - bb "github.com/primev/preconf_blob_bidder/core/mevcommit" -) - -var NUM_BLOBS = 6 - -func main() { - // Load the .env file - err := godotenv.Load() - if err != nil { - log.Crit("Error loading .env file", "err", err) - } - - // Set up logging - glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, true)) - glogger.Verbosity(log.LevelInfo) - log.SetDefault(log.NewLogger(glogger)) - - // Read configuration from environment variables - bidderAddress := os.Getenv("BIDDER_ADDRESS") - if bidderAddress == "" { - bidderAddress = "mev-commit-bidder:13524" - } - - usePayloadEnv := os.Getenv("USE_PAYLOAD") - usePayload := true // Default value - if usePayloadEnv != "" { - // Convert usePayloadEnv to bool - var err error - usePayload, err = parseBoolEnvVar("USE_PAYLOAD", usePayloadEnv) - if err != nil { - log.Crit("Invalid USE_PAYLOAD value", "err", err) - } - } - - // Now, load rpcEndpoint conditionally - var rpcEndpoint string - if !usePayload { - rpcEndpoint = os.Getenv("RPC_ENDPOINT") - if rpcEndpoint == "" { - log.Crit("RPC_ENDPOINT environment variable is required when USE_PAYLOAD is false") - } - } - - wsEndpoint := os.Getenv("WS_ENDPOINT") - if wsEndpoint == "" { - log.Crit("WS_ENDPOINT environment variable is required") - } - - privateKeyHex := os.Getenv("PRIVATE_KEY") - if privateKeyHex == "" { - log.Crit("PRIVATE_KEY environment variable is required") - } - - offsetEnv := os.Getenv("OFFSET") - var offset uint64 = 1 // Default offset - if offsetEnv != "" { - // Convert offsetEnv to uint64 - var err error - offset, err = parseUintEnvVar("OFFSET", offsetEnv) - if err != nil { - log.Crit("Invalid OFFSET value", "err", err) - } - } - - // Read bidAmount from environment variable - bidAmountEnv := os.Getenv("BID_AMOUNT") - var bidAmount float64 = 0.001 // Default bid amount - if bidAmountEnv != "" { - var err error - bidAmount, err = parseFloatEnvVar("BID_AMOUNT", bidAmountEnv) - if err != nil { - log.Crit("Invalid BID_AMOUNT value", "err", err) - } - } - - // Read stdDevPercentage from environment variable - stdDevPercentageEnv := os.Getenv("BID_AMOUNT_STD_DEV_PERCENTAGE") - var stdDevPercentage float64 = 100.0 // Default std percent is 100% - if stdDevPercentageEnv != "" { - var err error - stdDevPercentage, err = parseFloatEnvVar("BID_AMOUNT_STD_DEV_PERCENTAGE", stdDevPercentageEnv) - if err != nil { - log.Crit("Invalid BID_AMOUNT_STD_DEV_PERCENTAGE value", "err", err) - } - } - - // these variables are not required - ethTransfer := os.Getenv("ETH_TRANSFER") - blob := os.Getenv("BLOB") - - // Validate that only one of the flags is set - if ethTransfer == "true" && blob == "true" { - log.Crit("Only one of --ethtransfer or --blob can be set at a time") - } - - // Log configuration values (excluding sensitive data) - log.Info("Configuration values", - "bidderAddress", bidderAddress, - "rpcEndpoint", maskEndpoint(rpcEndpoint), - "wsEndpoint", maskEndpoint(wsEndpoint), - "offset", offset, - "usePayload", usePayload, - "bidAmount", bidAmount, - "stdDevPercentage", stdDevPercentage, - ) - - authAcct, err := bb.AuthenticateAddress(privateKeyHex) - if err != nil { - log.Crit("Failed to authenticate private key:", "err", err) - } - - cfg := bb.BidderConfig{ - ServerAddress: bidderAddress, - LogFmt: "json", - LogLevel: "info", - } - - bidderClient, err := bb.NewBidderClient(cfg) - if err != nil { - log.Crit("failed to connect to mev-commit bidder API", "err", err) - } - - log.Info("connected to mev-commit client") - - timeout := 30 * time.Second - - // Only connect to the RPC client if usePayload is false - if !usePayload { - // Connect to RPC client - client := connectRPCClientWithRetries(rpcEndpoint, 5, timeout) - if client == nil { - log.Error("failed to connect to RPC client", rpcEndpoint) - } - log.Info("(rpc) geth client connected", "endpoint", rpcEndpoint) - } - - // Connect to WS client - wsClient, err := connectWSClient(wsEndpoint) - if err != nil { - log.Crit("failed to connect to geth client", "err", err) - } - log.Info("(ws) geth client connected") - - headers := make(chan *types.Header) - sub, err := wsClient.SubscribeNewHead(context.Background(), headers) - if err != nil { - log.Crit("failed to subscribe to new blocks", "err", err) - } - - timer := time.NewTimer(24 * 14 * time.Hour) - - - for { - select { - case <-timer.C: - log.Info("Stopping the loop.") - return - case err := <-sub.Err(): - log.Warn("subscription error", "err", err) - wsClient, sub = reconnectWSClient(wsEndpoint, headers) - continue - case header := <-headers: - var signedTx *types.Transaction - var blockNumber uint64 - if ethTransfer == "true" { - amount := new(big.Int).SetInt64(1e15) - signedTx, blockNumber, err = ee.SelfETHTransfer(wsClient, authAcct, amount, offset) - } else if blob == "true" { - // Execute Blob Transaction - signedTx, blockNumber, err = ee.ExecuteBlobTransaction(wsClient, authAcct, NUM_BLOBS, offset) - } - - if signedTx == nil { - log.Error("Transaction was not signed or created.") - } else { - log.Info("Transaction sent successfully") - } - - // Check for errors before using signedTx - if err != nil { - log.Error("failed to execute transaction", "err", err) - } - - log.Info("new block received", - "blockNumber", header.Number, - "timestamp", header.Time, - "hash", header.Hash().String(), - ) - - // Compute standard deviation in ETH - stdDev := bidAmount * stdDevPercentage / 100.0 - - // Generate random amount with normal distribution - randomEthAmount := rand.NormFloat64()*stdDev + bidAmount - - // Ensure the randomEthAmount is positive - if randomEthAmount <= 0 { - randomEthAmount = bidAmount // fallback to bidAmount - } - - if usePayload { - // If use-payload is true, send the transaction payload to mev-commit. Don't send bundle - sendPreconfBid(bidderClient, signedTx, int64(blockNumber), randomEthAmount) - } else { - // 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("Failed to send transaction", "rpcEndpoint", rpcEndpoint, "error", err) - } - sendPreconfBid(bidderClient, signedTx.Hash().String(), int64(blockNumber), randomEthAmount) - } - - // handle ExecuteBlob error - if err != nil { - log.Error("failed to execute blob tx", "err", err) - continue // Skip to the next endpoint - } - } - } -} - -func maskEndpoint(rpcEndpoint string) string { - if len(rpcEndpoint) > 5 { - return rpcEndpoint[:5] + "*****" - } - return "*****" -} - -func connectRPCClientWithRetries(rpcEndpoint string, maxRetries int, timeout time.Duration) *ethclient.Client { - var rpcClient *ethclient.Client - var err error - - for i := 0; i < maxRetries; i++ { - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - rpcClient, err = ethclient.DialContext(ctx, rpcEndpoint) - if err == nil { - return rpcClient - } - - log.Warn("failed to connect to RPC client, retrying...", "attempt", i+1, "err", err) - time.Sleep(10 * time.Duration(math.Pow(2, float64(i)))) // Exponential backoff - } - - log.Error("failed to connect to RPC client after retries", "err", err) - return nil -} - -func connectWSClient(wsEndpoint string) (*ethclient.Client, error) { - for { - wsClient, err := bb.NewGethClient(wsEndpoint) - if err == nil { - return wsClient, nil - } - log.Warn("failed to connect to websocket client", "err", err) - time.Sleep(10 * time.Second) - } -} - -func reconnectWSClient(wsEndpoint string, headers chan *types.Header) (*ethclient.Client, ethereum.Subscription) { - var wsClient *ethclient.Client - var sub ethereum.Subscription - var err error - - for i := 0; i < 10; i++ { // Retry logic for WebSocket connection - wsClient, err = connectWSClient(wsEndpoint) - if err == nil { - log.Info("(ws) geth client reconnected") - sub, err = wsClient.SubscribeNewHead(context.Background(), headers) - if err == nil { - return wsClient, sub - } - } - log.Warn("failed to reconnect WebSocket client, retrying...", "attempt", i+1, "err", err) - time.Sleep(5 * time.Second) - } - log.Crit("failed to reconnect WebSocket client after retries", "err", err) - return nil, nil -} - -func sendPreconfBid(bidderClient *bb.Bidder, input interface{}, blockNumber int64, randomEthAmount float64) { - // Get current time in milliseconds - currentTime := time.Now().UnixMilli() - - // Define bid decay start and end - decayStart := currentTime - decayEnd := currentTime + int64(time.Duration(36*time.Second).Milliseconds()) // bid decay is 36 seconds (2 blocks) - - // Convert the random ETH amount to wei (1 ETH = 10^18 wei) - bigEthAmount := big.NewFloat(randomEthAmount) - weiPerEth := big.NewFloat(1e18) - bigWeiAmount := new(big.Float).Mul(bigEthAmount, weiPerEth) - - // Convert big.Float to big.Int - randomWeiAmount := new(big.Int) - bigWeiAmount.Int(randomWeiAmount) - - // Convert the amount to a string for the bidder - amount := randomWeiAmount.String() - - // Determine how to handle the input - var err error - switch v := input.(type) { - case string: - // Input is a string, process it as a transaction hash - txHash := strings.TrimPrefix(v, "0x") - log.Info("sending bid with transaction hash", "tx", input) - // Send the bid with tx hash string - _, err = bidderClient.SendBid([]string{txHash}, amount, blockNumber, decayStart, decayEnd) - - case *types.Transaction: - // Input is a transaction object, send the transaction object - log.Info("sending bid with tx payload", "tx", v.Hash().String()) - // Send the bid with the full transaction object - _, err = bidderClient.SendBid([]*types.Transaction{v}, amount, blockNumber, decayStart, decayEnd) - - default: - log.Warn("unsupported input type, must be string or *types.Transaction") - return - } - - if err != nil { - log.Warn("failed to send bid", "err", err) - } else { - log.Info("sent preconfirmation bid", "block", blockNumber, "amount (ETH)", randomEthAmount) - } -} - -func parseBoolEnvVar(name, value string) (bool, error) { - parsedValue, err := strconv.ParseBool(value) - if err != nil { - return false, fmt.Errorf("environment variable %s must be true or false, got '%s'", name, value) - } - return parsedValue, nil -} - -func parseUintEnvVar(name, value string) (uint64, error) { - parsedValue, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return 0, fmt.Errorf("environment variable %s must be a positive integer, got '%s'", name, value) - } - return parsedValue, nil -} - -func parseFloatEnvVar(name, value string) (float64, error) { - parsedValue, err := strconv.ParseFloat(value, 64) - if err != nil { - return 0, fmt.Errorf("environment variable %s must be a float, got '%s'", name, value) - } - return parsedValue, nil -} diff --git a/core/eth/bundle.go b/core/eth/bundle.go deleted file mode 100644 index b870a59..0000000 --- a/core/eth/bundle.go +++ /dev/null @@ -1,80 +0,0 @@ -package eth - -import ( - "bytes" - "encoding/json" - "io" - "net/http" - "time" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" -) - -type FlashbotsPayload struct { - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params []map[string]interface{} `json:"params"` - ID int `json:"id"` -} - -var httpClient = &http.Client{ - Timeout: 12 * time.Second, - Transport: &http.Transport{ - DisableKeepAlives: false, - MaxIdleConnsPerHost: 1, - IdleConnTimeout: 12 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - }, -} - -func SendBundle(RPCURL string, signedTx *types.Transaction, blkNum uint64) (string, error) { - binary, err := signedTx.MarshalBinary() - if err != nil { - log.Error("Error marshal transaction", "err", err) - return "", err - } - - blockNum := hexutil.EncodeUint64(blkNum) - - payload := FlashbotsPayload{ - Jsonrpc: "2.0", - Method: "eth_sendBundle", - Params: []map[string]interface{}{ - { - "txs": []string{ - hexutil.Encode(binary), - }, - "blockNumber": blockNum, - }, - }, - ID: 1, - } - - payloadBytes, err := json.Marshal(payload) - if err != nil { - return "", err - } - - req, err := http.NewRequest("POST", RPCURL, bytes.NewBuffer(payloadBytes)) - if err != nil { - log.Error("an error occurred creating request", "err", err) - } - req.Header.Add("Content-Type", "application/json") - - resp, err := httpClient.Do(req) - if err != nil { - log.Error("an error occurred", "err", err) - return "", err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Error("an error occurred", "err", err) - return "", err - } - - return string(body), nil -} diff --git a/core/mevcommit/bidderapi.go b/core/mevcommit/bidderapi.go deleted file mode 100644 index 391cc42..0000000 --- a/core/mevcommit/bidderapi.go +++ /dev/null @@ -1,94 +0,0 @@ -// Package mevcommit provides functionality for interacting with the mev-commit protocol, -// including sending bids for blob transactions and saving bid requests and responses. -package mevcommit - -import ( - "context" - "encoding/hex" - "fmt" - "io" - "strings" - "time" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - pb "github.com/primev/preconf_blob_bidder/core/bidderpb" -) - -func (b *Bidder) SendBid(input interface{}, amount string, blockNumber, decayStart, decayEnd int64) (pb.Bidder_SendBidClient, error) { - // Prepare variables to hold transaction hashes or raw transactions - var txHashes []string - var rawTransactions []string - - // Determine the input type and process accordingly - switch v := input.(type) { - case []string: - // If input is a slice of transaction hashes - txHashes = make([]string, len(v)) - for i, hash := range v { - txHashes[i] = strings.TrimPrefix(hash, "0x") - } - case []*types.Transaction: - // If input is a slice of *types.Transaction, convert to raw transactions - rawTransactions = make([]string, len(v)) - for i, tx := range v { - rlpEncodedTx, err := tx.MarshalBinary() - if err != nil { - log.Error("Failed to marshal transaction to raw format", "error", err) - return nil, fmt.Errorf("failed to marshal transaction: %w", err) - } - rawTransactions[i] = hex.EncodeToString(rlpEncodedTx) - } - default: - log.Warn("Unsupported input type, must be []string or []*types.Transaction") - return nil, fmt.Errorf("unsupported input type: %T", input) - } - - // Create a new bid request with the appropriate transaction data - bidRequest := &pb.Bid{ - Amount: amount, - BlockNumber: blockNumber, - DecayStartTimestamp: decayStart, - DecayEndTimestamp: decayEnd, - } - - if len(txHashes) > 0 { - bidRequest.TxHashes = txHashes - } else if len(rawTransactions) > 0 { - // Convert rawTransactions to []string - rawTxStrings := make([]string, len(rawTransactions)) - for i, rawTx := range rawTransactions { - rawTxStrings[i] = string(rawTx) - } - bidRequest.RawTransactions = rawTxStrings - } - - ctx := context.Background() - - // Send the bid request to the mev-commit client - response, err := b.client.SendBid(ctx, bidRequest) - if err != nil { - log.Error("Failed to send bid", "error", err) - return nil, fmt.Errorf("failed to send bid: %w", err) - } - - // Continuously receive bid responses - for { - msg, err := response.Recv() - if err == io.EOF { - // End of stream - break - } - if err != nil { - log.Error("Failed to receive bid response", "error", err) - } - - log.Info("Bid accepted", "commitment details", msg) - } - - // Timer before saving bid responses - startTimeBeforeSaveResponses := time.Now() - log.Info("End Time", "time", startTimeBeforeSaveResponses) - - return response, nil -} \ No newline at end of file diff --git a/core/mevcommit/client.go b/core/mevcommit/client.go deleted file mode 100644 index 5e74b0e..0000000 --- a/core/mevcommit/client.go +++ /dev/null @@ -1,135 +0,0 @@ -// Package mevcommit provides functionality for interacting with the mev-commit protocol, -// including setting up a bidder client, connecting to an Ethereum node, and handling -// account authentication. -package mevcommit - -import ( - "crypto/ecdsa" - "math/big" - - pb "github.com/primev/preconf_blob_bidder/core/bidderpb" - "google.golang.org/grpc" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "google.golang.org/grpc/credentials/insecure" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" -) - -const HOLESKY_CHAIN_ID = 1700 - -// BidderConfig holds the configuration settings for the mev-commit bidder node. -type BidderConfig struct { - ServerAddress string `json:"server_address" yaml:"server_address"` // The address of the gRPC server for the bidder node. - LogFmt string `json:"log_fmt" yaml:"log_fmt"` // The format for logging output. - LogLevel string `json:"log_level" yaml:"log_level"` // The level of logging detail. -} - -// Bidder utilizes the mev-commit bidder client to interact with the mev-commit chain. -type Bidder struct { - client pb.BidderClient // gRPC client for interacting with the mev-commit bidder service. -} - -// GethConfig holds configuration settings for a Geth node to connect to the mev-commit chain. -type GethConfig struct { - Endpoint string `json:"endpoint" yaml:"endpoint"` // The RPC endpoint for connecting to the Ethereum node. -} - -// AuthAcct holds the private key, public key, address, and transaction authorization information for an account. -type AuthAcct struct { - PrivateKey *ecdsa.PrivateKey // The private key for the account. - PublicKey *ecdsa.PublicKey // The public key derived from the private key. - Address common.Address // The Ethereum address derived from the public key. - Auth *bind.TransactOpts // The transaction options for signing transactions. -} - -// NewBidderClient creates a new gRPC client connection to the bidder service and returns a Bidder instance. -// -// Parameters: -// - cfg: The BidderConfig struct containing the server address and logging settings. -// -// Returns: -// - A pointer to a Bidder struct, or an error if the connection fails. -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.Crit("Failed to connect to gRPC server", "err", err) - return nil, err - } - - // Create a new bidder client using the gRPC connection - client := pb.NewBidderClient(conn) - return &Bidder{client: client}, nil -} - -// NewGethClient connects to an Ethereum-compatible chain using the provided RPC endpoint. -// -// Parameters: -// - endpoint: The RPC endpoint of the Ethereum node. -// -// Returns: -// - A pointer to an ethclient.Client for interacting with the Ethereum node, or an error if the connection fails. -func NewGethClient(endpoint string) (*ethclient.Client, error) { - // Dial the Ethereum RPC endpoint - client, err := rpc.Dial(endpoint) - if err != nil { - return nil, err - } - - // Create a new ethclient.Client using the RPC client - ec := ethclient.NewClient(client) - return ec, nil -} - -// AuthenticateAddress converts a hex-encoded private key string to an AuthAcct struct, -// which contains the account's private key, public key, address, and transaction authorization. -// -// Parameters: -// - privateKeyHex: The hex-encoded private key string. -// -// Returns: -// - A pointer to an AuthAcct struct, or an error if authentication fails. -func AuthenticateAddress(privateKeyHex string) (AuthAcct, error) { - if privateKeyHex == "" { - return AuthAcct{}, nil - } - - // Convert the hex-encoded private key to an ECDSA private key - privateKey, err := crypto.HexToECDSA(privateKeyHex) - if err != nil { - log.Crit("Failed to load private key", "err", err) - return AuthAcct{}, err - } - - // Extract the public key from the private key - publicKey := privateKey.Public() - publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) - if !ok { - log.Crit("Failed to assert public key type") - } - - // Generate the Ethereum address from the public key - address := crypto.PubkeyToAddress(*publicKeyECDSA) - - // Set the chain ID (currently hardcoded for Holesky testnet) - chainID := big.NewInt(HOLESKY_CHAIN_ID) // Holesky - - // Create the transaction options with the private key and chain ID - auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) - if err != nil { - log.Crit("Failed to create authorized transactor", "err", err) - } - - // Return the AuthAcct struct containing the private key, public key, address, and transaction options - return AuthAcct{ - PrivateKey: privateKey, - PublicKey: publicKeyECDSA, - Address: address, - Auth: auth, - }, nil -} diff --git a/core/mevcommit/contracts.go b/core/mevcommit/contracts.go deleted file mode 100644 index 43abde6..0000000 --- a/core/mevcommit/contracts.go +++ /dev/null @@ -1,339 +0,0 @@ -// Package mevcommit provides functionality for interacting with the mev-commit protocol, -// including managing bids, deposits, withdrawals, and event listeners on the Ethereum blockchain. -package mevcommit - -import ( - "context" - "fmt" - "log" - "math/big" - "os" - "strings" - - "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" -) - -// Contract addresses used within the mev-commit protocol. -const ( - // latest contracts as of v0.6.1 - bidderRegistryAddress = "0x401B3287364f95694c43ACA3252831cAc02e5C41" - blockTrackerAddress = "0x7538F3AaA07dA1990486De21A0B438F55e9639e4" - PreconfManagerAddress = "0x9433bCD9e89F923ce587f7FA7E39e120E93eb84D" -) - -// CommitmentStoredEvent represents the data structure for the CommitmentStored event. -type CommitmentStoredEvent struct { - CommitmentIndex [32]byte - Bidder common.Address - Commiter common.Address - Bid uint64 - BlockNumber uint64 - BidHash [32]byte - DecayStartTimeStamp uint64 - DecayEndTimeStamp uint64 - TxnHash string - CommitmentHash [32]byte - BidSignature []byte - CommitmentSignature []byte - DispatchTimestamp uint64 - SharedSecretKey []byte -} - -// LoadABI loads the ABI from the specified file path and parses it. -// -// Parameters: -// - filePath: The path to the ABI file to be loaded. -// -// Returns: -// - The parsed ABI object, or an error if loading fails. -func LoadABI(filePath string) (abi.ABI, error) { - data, err := os.ReadFile(filePath) - if err != nil { - log.Println("Failed to load ABI file:", err) - return abi.ABI{}, err - } - - parsedABI, err := abi.JSON(strings.NewReader(string(data))) - if err != nil { - log.Println("Failed to parse ABI file:", err) - return abi.ABI{}, err - } - - return parsedABI, nil -} - -// WindowHeight retrieves the current bidding window height from the BlockTracker contract. -// -// Parameters: -// - client: The Ethereum client instance. -// -// Returns: -// - The current window height as a big.Int, or an error if the call fails. -func WindowHeight(client *ethclient.Client) (*big.Int, error) { - // Load the BlockTracker contract ABI - blockTrackerABI, err := LoadABI("abi/BlockTracker.abi") - if err != nil { - log.Println("Failed to load ABI file:", err) - return nil, err - } - - // Bind the contract to the client - blockTrackerContract := bind.NewBoundContract(common.HexToAddress(blockTrackerAddress), blockTrackerABI, client, client, client) - - // Call the getCurrentWindow function to retrieve the current window height - var currentWindowResult []interface{} - err = blockTrackerContract.Call(nil, ¤tWindowResult, "getCurrentWindow") - if err != nil { - log.Println("Failed to get current window:", err) - return nil, err - } - - // Extract the current window as *big.Int - currentWindow, ok := currentWindowResult[0].(*big.Int) - if !ok { - log.Println("Failed to convert current window to *big.Int") - return nil, fmt.Errorf("conversion to *big.Int failed") - } - - return currentWindow, nil -} - -// GetMinDeposit retrieves the minimum deposit required for participating in the bidding window. -// -// Parameters: -// - client: The Ethereum client instance. -// -// Returns: -// - The minimum deposit as a big.Int, or an error if the call fails. -func GetMinDeposit(client *ethclient.Client) (*big.Int, error) { - // Load the BidderRegistry contract ABI - bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") - if err != nil { - return nil, fmt.Errorf("failed to load ABI file: %v", err) - } - - // Bind the contract to the client - bidderRegistryContract := bind.NewBoundContract(common.HexToAddress(bidderRegistryAddress), bidderRegistryABI, client, client, client) - - // Call the minDeposit function to get the minimum deposit amount - var minDepositResult []interface{} - err = bidderRegistryContract.Call(nil, &minDepositResult, "minDeposit") - if err != nil { - 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 { - return nil, fmt.Errorf("failed to convert minDeposit to *big.Int") - } - - return minDeposit, nil -} - -// DepositIntoWindow deposits the minimum bid amount into the specified bidding window. -// -// Parameters: -// - client: The Ethereum client instance. -// - depositWindow: The window into which the deposit should be made. -// - authAcct: The authenticated account struct containing transaction authorization. -// -// Returns: -// - The transaction object if successful, or an error if the transaction fails. -func DepositIntoWindow(client *ethclient.Client, depositWindow *big.Int, authAcct *AuthAcct) (*types.Transaction, error) { - // Load the BidderRegistry contract ABI - bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") - if err != nil { - return nil, fmt.Errorf("failed to load ABI file: %v", err) - } - - // Bind the contract to the client - bidderRegistryContract := bind.NewBoundContract(common.HexToAddress(bidderRegistryAddress), bidderRegistryABI, client, client, client) - - // Retrieve the minimum deposit amount - minDeposit, err := GetMinDeposit(client) - if err != nil { - return nil, fmt.Errorf("failed to get minDeposit: %v", err) - } - - // Set the value for the transaction to the minimum deposit amount - authAcct.Auth.Value = minDeposit - - // Prepare and send the transaction to deposit into the specific window - tx, err := bidderRegistryContract.Transact(authAcct.Auth, "depositForSpecificWindow", depositWindow) - if err != nil { - return nil, fmt.Errorf("failed to create transaction: %v", err) - } - - // Wait for the transaction to be mined (optional) - receipt, err := bind.WaitMined(context.Background(), client, tx) - if err != nil { - return nil, fmt.Errorf("transaction mining error: %v", err) - } - - // Check the transaction status - if receipt.Status == 1 { - fmt.Println("Transaction successful") - return tx, nil - } else { - return nil, fmt.Errorf("transaction failed") - } -} - -// GetDepositAmount retrieves the deposit amount for a given address and window. -// -// Parameters: -// - client: The Ethereum client instance. -// - address: The Ethereum address to query the deposit for. -// - window: The bidding window to query the deposit for. -// -// Returns: -// - The deposit amount as a big.Int, or an error if the call fails. -func GetDepositAmount(client *ethclient.Client, address common.Address, window big.Int) (*big.Int, error) { - // Load the BidderRegistry contract ABI - bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") - if err != nil { - return nil, fmt.Errorf("failed to load ABI file: %v", err) - } - - // Bind the contract to the client - bidderRegistryContract := bind.NewBoundContract(common.HexToAddress(bidderRegistryAddress), bidderRegistryABI, client, client, client) - - // Call the getDeposit function to retrieve the deposit amount - var depositResult []interface{} - err = bidderRegistryContract.Call(nil, &depositResult, "getDeposit", address, window) - if err != nil { - 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 { - return nil, fmt.Errorf("failed to convert deposit amount to *big.Int") - } - - return depositAmount, nil -} - -// WithdrawFromWindow withdraws all funds from the specified bidding window. -// -// Parameters: -// - client: The Ethereum client instance. -// - authAcct: The authenticated account struct containing transaction authorization. -// - window: The window from which to withdraw funds. -// -// Returns: -// - The transaction object if successful, or an error if the transaction fails. -func WithdrawFromWindow(client *ethclient.Client, authAcct *AuthAcct, window *big.Int) (*types.Transaction, error) { - // Load the BidderRegistry contract ABI - bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") - if err != nil { - return nil, fmt.Errorf("failed to load ABI file: %v", err) - } - - // Bind the contract to the client - bidderRegistryContract := bind.NewBoundContract(common.HexToAddress(bidderRegistryAddress), bidderRegistryABI, client, client, client) - - // Prepare the withdrawal transaction - withdrawalTx, err := bidderRegistryContract.Transact(authAcct.Auth, "withdrawBidderAmountFromWindow", authAcct.Address, window) - if err != nil { - return nil, fmt.Errorf("failed to create withdrawal transaction: %v", err) - } - - // Wait for the withdrawal transaction to be mined - withdrawalReceipt, err := bind.WaitMined(context.Background(), client, withdrawalTx) - if err != nil { - return nil, fmt.Errorf("withdrawal transaction mining error: %v", err) - } - - // Check the withdrawal transaction status - if withdrawalReceipt.Status == 1 { - fmt.Println("Withdrawal successful") - return withdrawalTx, nil - } else { - return nil, fmt.Errorf("withdrawal failed") - } -} - -// ListenForCommitmentStoredEvent listens for the CommitmentStored event on the Ethereum blockchain. -// This function will print event details when the CommitmentStored event is detected. -// -// Parameters: -// - client: The Ethereum client instance. -// -// Note: The event listener is not currently functioning correctly (as per the TODO comment). -func ListenForCommitmentStoredEvent(client *ethclient.Client) { - // Load the PreConfCommitmentStore contract ABI - contractAbi, err := LoadABI("abi/PreConfCommitmentStore.abi") - if err != nil { - log.Fatalf("Failed to load contract ABI: %v", err) - } - - // Subscribe to new block headers - headers := make(chan *types.Header) - sub, err := client.SubscribeNewHead(context.Background(), headers) - if err != nil { - log.Fatalf("Failed to subscribe to new head: %v", err) - } - - // Listen for new block headers and filter logs for the CommitmentStored event - for { - select { - case err := <-sub.Err(): - log.Fatalf("Error with header subscription: %v", err) - case header := <-headers: - query := ethereum.FilterQuery{ - Addresses: []common.Address{common.HexToAddress(PreconfManagerAddress)}, - FromBlock: header.Number, - ToBlock: header.Number, - } - - logs := make(chan types.Log) - subLogs, err := client.SubscribeFilterLogs(context.Background(), query, logs) - if err != nil { - log.Printf("Failed to subscribe to logs: %v", err) - continue - } - - // Process incoming logs - for { - select { - case err := <-subLogs.Err(): - log.Printf("Error with log subscription: %v", err) - break - 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.Printf("Failed to unpack log data: %v", err) - continue - } - - // Print event details - fmt.Printf("CommitmentStored Event: \n") - fmt.Printf("CommitmentIndex: %x\n", event.CommitmentIndex) - fmt.Printf("Bidder: %s\n", event.Bidder.Hex()) - fmt.Printf("Commiter: %s\n", event.Commiter.Hex()) - fmt.Printf("Bid: %d\n", event.Bid) - fmt.Printf("BlockNumber: %d\n", event.BlockNumber) - fmt.Printf("BidHash: %x\n", event.BidHash) - fmt.Printf("DecayStartTimeStamp: %d\n", event.DecayStartTimeStamp) - fmt.Printf("DecayEndTimeStamp: %d\n", event.DecayEndTimeStamp) - fmt.Printf("TxnHash: %s\n", event.TxnHash) - fmt.Printf("CommitmentHash: %x\n", event.CommitmentHash) - fmt.Printf("BidSignature: %x\n", event.BidSignature) - fmt.Printf("CommitmentSignature: %x\n", event.CommitmentSignature) - fmt.Printf("DispatchTimestamp: %d\n", event.DispatchTimestamp) - fmt.Printf("SharedSecretKey: %x\n", event.SharedSecretKey) - } - } - } - } -} diff --git a/docker-compose.yml b/docker-compose.yml index 458cc5c..7e1a64b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,9 +10,8 @@ services: - WS_ENDPOINT=${WS_ENDPOINT} - USE_PAYLOAD=${USE_PAYLOAD} - BIDDER_ADDRESS=${BIDDER_ADDRESS} - - ETH_TRANSFER=${ETH_TRANSFER} - - BLOB=${BLOB} - SERVICE_ADDRESS=mev-commit-bidder:13524 + - NUM_BLOB=${NUM_BLOB} networks: app-network: external: true diff --git a/env.example b/env.example index 23fde03..20f2997 100644 --- a/env.example +++ b/env.example @@ -2,9 +2,14 @@ RPC_ENDPOINT=rpc_endpoint #optional WS_ENDPOINT=ws_endpoint PRIVATE_KEY=private_key USE_PAYLOAD=true -BIDDER_ADDRESS="mev-commit-bidder:13524" +BIDDER_ADDRESS="localhost:13524" OFFSET=1 -ETH_TRANSFER="false" -BLOB="true" +# 0 blobs means eth transfer. Otehrwise a nonzero blob count will send blobs +NUM_BLOB=0 BID_AMOUNT=0.0025 -BID_AMOUNT_STD_DEV_PERCENTAGE=200 \ No newline at end of file +BID_AMOUNT_STD_DEV_PERCENTAGE=200 +# mev-commit addresess as of v0.6.0 +BIDDER_REGISTRY_ADDRESS=0x401B3287364f95694c43ACA3252831cAc02e5C41 +BLOCK_TRACKER_ADDRESS=0x7538F3AaA07dA1990486De21A0B438F55e9639e4 +PRECONF_MANAGER_ADDRESS=0x9433bCD9e89F923ce587f7FA7E39e120E93eb84D +DEFAULT_TIMEOUT=15 \ No newline at end of file diff --git a/go.mod b/go.mod index 74291a9..45fe945 100644 --- a/go.mod +++ b/go.mod @@ -1,50 +1,50 @@ module github.com/primev/preconf_blob_bidder -go 1.21 +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.7 + github.com/consensys/gnark-crypto v0.12.1 + 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/rs/zerolog v1.33.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 -) - -require ( - github.com/consensys/gnark-crypto v0.12.1 - github.com/crate-crypto/go-kzg-4844 v1.0.0 - github.com/holiman/uint256 v1.3.0 + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect - github.com/bits-and-blooms/bitset v1.10.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/bits-and-blooms/bitset v1.13.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/huin/goupnp v1.3.0 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/joho/godotenv v1.5.1 // 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/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/supranational/blst v0.3.11 // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/supranational/blst v0.3.13 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect + github.com/urfave/cli/v2 v2.27.5 golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index 52a5fca..22704f8 100644 --- a/go.sum +++ b/go.sum @@ -10,10 +10,10 @@ github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjC github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= -github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= +github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= @@ -26,8 +26,8 @@ github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/e github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw= -github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= +github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= @@ -36,9 +36,8 @@ 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.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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= github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI= @@ -53,14 +52,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.14.7 h1:EHpv3dE8evQmpVEQ/Ne2ahB06n2mQptdwqaMNhAT29g= -github.com/ethereum/go-ethereum v1.14.7/go.mod h1:Mq0biU2jbdmKSZoqOj29017ygFrMnB5/Rifwp980W4o= -github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= -github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= -github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= -github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ethereum/go-ethereum v1.14.11 h1:8nFDCUUE67rPc6AKxFj7JKaOa2W/W1Rse3oS6LvvxEY= +github.com/ethereum/go-ethereum v1.14.11/go.mod h1:+l/fr42Mma+xBnhefL/+z11/hcmJ2egl+ScIVPjhc7E= +github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9 h1:8NfxH2iXvJ60YRB8ChToFTUzl8awsc3cJ8CbLjGIl/A= +github.com/ethereum/go-verkle v0.1.1-0.20240829091221-dffa7562dbe9/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= @@ -70,28 +65,16 @@ 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= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -109,15 +92,12 @@ github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6w github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.3.0 h1:4wdcm/tnd0xXdu7iS3ruNvxkWwrb4aeBQv19ayYn8F4= -github.com/holiman/uint256 v1.3.0/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= +github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -130,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= @@ -145,17 +123,8 @@ github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -174,19 +143,18 @@ 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= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= -github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= +github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= @@ -195,76 +163,40 @@ github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= -github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e h1:Elxv5MwEkCI9f5SkoL6afed6NTdxaGoAo39eANBwHL8= google.golang.org/genproto/googleapis/rpc v0.0.0-20240521202816-d264139d666e/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/abi/BidderRegistry.abi b/internal/abi/BidderRegistry.abi similarity index 100% rename from abi/BidderRegistry.abi rename to internal/abi/BidderRegistry.abi diff --git a/abi/BlockTracker.abi b/internal/abi/BlockTracker.abi similarity index 100% rename from abi/BlockTracker.abi rename to internal/abi/BlockTracker.abi diff --git a/abi/L1Gateway.abi b/internal/abi/L1Gateway.abi similarity index 100% rename from abi/L1Gateway.abi rename to internal/abi/L1Gateway.abi diff --git a/abi/Oracle.abi b/internal/abi/Oracle.abi similarity index 100% rename from abi/Oracle.abi rename to internal/abi/Oracle.abi diff --git a/abi/PreConfCommitmentStore.abi b/internal/abi/PreConfCommitmentStore.abi similarity index 100% rename from abi/PreConfCommitmentStore.abi rename to internal/abi/PreConfCommitmentStore.abi diff --git a/abi/ProviderRegistry.abi b/internal/abi/ProviderRegistry.abi similarity index 100% rename from abi/ProviderRegistry.abi rename to internal/abi/ProviderRegistry.abi diff --git a/abi/SettlementGateway.abi b/internal/abi/SettlementGateway.abi similarity index 100% rename from abi/SettlementGateway.abi rename to internal/abi/SettlementGateway.abi diff --git a/abi/ValidatorRegistry.abi b/internal/abi/ValidatorRegistry.abi similarity index 100% rename from abi/ValidatorRegistry.abi rename to internal/abi/ValidatorRegistry.abi diff --git a/core/bidderpb/bidderapi.pb.go b/internal/bidderpb/bidderapi.pb.go similarity index 100% rename from core/bidderpb/bidderapi.pb.go rename to internal/bidderpb/bidderapi.pb.go diff --git a/core/bidderpb/bidderapi.pb.gw.go b/internal/bidderpb/bidderapi.pb.gw.go similarity index 100% rename from core/bidderpb/bidderapi.pb.gw.go rename to internal/bidderpb/bidderapi.pb.gw.go diff --git a/core/bidderpb/bidderapi_grpc.pb.go b/internal/bidderpb/bidderapi_grpc.pb.go similarity index 100% rename from core/bidderpb/bidderapi_grpc.pb.go rename to internal/bidderpb/bidderapi_grpc.pb.go diff --git a/internal/eth/bundle.go b/internal/eth/bundle.go new file mode 100644 index 0000000..65e3ea8 --- /dev/null +++ b/internal/eth/bundle.go @@ -0,0 +1,118 @@ +package eth + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +type JSONRPCResponse struct { + Result json.RawMessage `json:"result"` + RPCError RPCError `json:"error"` + ID int `json:"id,omitempty"` + Jsonrpc string `json:"jsonrpc,omitempty"` +} + +type RPCError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type FlashbotsPayload struct { + Jsonrpc string `json:"jsonrpc"` + Method string `json:"method"` + Params []map[string]interface{} `json:"params"` + 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 (s *Service) SendBundle(signedTx *types.Transaction, blkNum uint64) (string, error) { + // Marshal the signed transaction into binary format. + binary, err := signedTx.MarshalBinary() + if err != nil { + s.Logger.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", + Params: []map[string]interface{}{ + { + "txs": []string{ + hexutil.Encode(binary), + }, + "blockNumber": blockNum, + }, + }, + ID: 1, + } + + // Marshal the payload into JSON. + payloadBytes, err := json.Marshal(payload) + if err != nil { + s.Logger.Error("Error marshaling payload", "error", err) + return "", err + } + + // Create a context with a timeout. + ctx, cancel := context.WithTimeout(context.Background(), s.DefaultTimeout) + defer cancel() + + // Create a new HTTP POST request with the JSON payload. + req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.RPCURL, bytes.NewReader(payloadBytes)) + if err != nil { + s.Logger.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 { + s.Logger.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 { + s.Logger.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 { + s.Logger.Error("Failed to unmarshal response", "error", err) + return "", err + } + + // Check for RPC errors. + if rpcResp.RPCError.Code != 0 { + s.Logger.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 { + s.Logger.Error("Failed to marshal result", "error", err) + return "", err + } + + return string(resultStr), nil +} diff --git a/core/eth/sendtx.go b/internal/eth/sendtx.go similarity index 53% rename from core/eth/sendtx.go rename to internal/eth/sendtx.go index d8d27ef..28ebfb4 100644 --- a/core/eth/sendtx.go +++ b/internal/eth/sendtx.go @@ -8,7 +8,9 @@ import ( "context" "crypto/ecdsa" "errors" + "log/slog" "math/big" + "os" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" gokzg4844 "github.com/crate-crypto/go-kzg-4844" @@ -17,89 +19,122 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" - bb "github.com/primev/preconf_blob_bidder/core/mevcommit" "golang.org/x/exp/rand" ) +func (s *Service) SelfETHTransfer(value *big.Int, offset uint64) (*types.Transaction, uint64, error) { + ctx, cancel := context.WithTimeout(context.Background(), s.DefaultTimeout) + defer cancel() -func SelfETHTransfer(client *ethclient.Client, authAcct bb.AuthAcct, value *big.Int, offset uint64) (*types.Transaction, uint64, error) { - // Get the account's nonce - nonce, err := client.PendingNonceAt(context.Background(), authAcct.Address) + // Use s.Client, s.AuthAcct, s.Logger + nonce, err := s.Client.PendingNonceAt(ctx, s.AuthAcct.Address) if err != nil { + s.Logger.Error("Failed to get pending nonce", "error", err) return nil, 0, err } // Get the current base fee per gas from the latest block header - header, err := client.HeaderByNumber(context.Background(), nil) + header, err := s.Client.HeaderByNumber(ctx, nil) if err != nil { + slog.Default().Error("Failed to get latest block header", + slog.String("function", "HeaderByNumber"), + slog.Any("error", err)) return nil, 0, err } - baseFee := header.BaseFee - - - blockNumber := header.Number.Uint64() - // Get the chain ID (this does not work with the Titan RPC) - chainID, err := client.NetworkID(context.Background()) + // Get the chain ID + chainID, err := s.Client.NetworkID(ctx) if err != nil { + slog.Default().Error("Failed to get network ID", + slog.String("function", "NetworkID"), + slog.Any("error", err)) return nil, 0, err } - // Create a new EIP-1559 transaction + baseFee := header.BaseFee + blockNumber := header.Number.Uint64() + + // Create a transaction with a priority fee. + priorityFee := big.NewInt(2_000_000_000) // 2 gwei in wei + maxFee := new(big.Int).Add(baseFee, priorityFee) tx := types.NewTx(&types.DynamicFeeTx{ Nonce: nonce, - To: &authAcct.Address, + To: &s.AuthAcct.Address, Value: value, Gas: 500_000, - GasFeeCap: baseFee, - GasTipCap: big.NewInt((0)), + GasFeeCap: maxFee, + GasTipCap: priorityFee, }) // Sign the transaction with the authenticated account's private key signer := types.LatestSignerForChainID(chainID) - signedTx, err := types.SignTx(tx, signer, authAcct.PrivateKey) + signedTx, err := types.SignTx(tx, signer, s.AuthAcct.PrivateKey) if err != nil { - log.Error("Failed to sign transaction", "error", err) + slog.Default().Error("Failed to sign transaction", + slog.String("function", "SignTx"), + slog.Any("error", err)) return nil, 0, err } - return signedTx, blockNumber + offset, nil + 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 } +// ExecuteBlobTransaction executes a blob transaction with preconfirmation bids. +func (s *Service) ExecuteBlobTransaction(numBlobs int, offset uint64) (*types.Transaction, uint64, error) { + + pubKey, ok := s.AuthAcct.PrivateKey.Public().(*ecdsa.PublicKey) + 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") + } -func ExecuteBlobTransaction(client *ethclient.Client, authAcct bb.AuthAcct, numBlobs int, offset uint64) (*types.Transaction, uint64, error) { var ( gasLimit = uint64(500_000) blockNumber uint64 nonce uint64 ) - privateKey := authAcct.PrivateKey + // Set a timeout context + ctx, cancel := context.WithTimeout(context.Background(), s.DefaultTimeout) + defer cancel() + + privateKey := s.AuthAcct.PrivateKey publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { + 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(context.Background(), authAcct.Address) + nonce, err := s.Client.PendingNonceAt(ctx, s.AuthAcct.Address) if err != nil { + slog.Default().Error("Failed to get pending nonce", + slog.String("function", "PendingNonceAt"), + slog.Any("error", err)) return nil, 0, err } - header, err := client.HeaderByNumber(context.Background(), nil) + header, err := s.Client.HeaderByNumber(ctx, nil) if err != nil { + slog.Default().Error("Failed to get latest block header", + slog.String("function", "HeaderByNumber"), + slog.Any("error", err)) return nil, 0, err } - + blockNumber = header.Number.Uint64() - chainID, err := client.NetworkID(context.Background()) + chainID, err := s.Client.NetworkID(ctx) if err != nil { + slog.Default().Error("Failed to get network ID", + slog.String("function", "NetworkID"), + slog.Any("error", err)) return nil, 0, err } @@ -119,13 +154,15 @@ func ExecuteBlobTransaction(client *ethclient.Client, authAcct bb.AuthAcct, numB baseFee := header.BaseFee maxFeePerGas := baseFee - + // Use for nonzero priority fee + priorityFee := big.NewInt(5_000_000_000) // 5 gwei in wei + maxFeePriority := new(big.Int).Add(maxFeePerGas, priorityFee) // Create a new BlobTx transaction tx := types.NewTx(&types.BlobTx{ ChainID: uint256.MustFromBig(chainID), Nonce: nonce, - GasTipCap: uint256.NewInt(0), - GasFeeCap: uint256.MustFromBig(maxFeePerGas), + GasTipCap: uint256.MustFromBig(priorityFee), + GasFeeCap: uint256.MustFromBig(maxFeePriority), Gas: gasLimit, To: fromAddress, BlobFeeCap: uint256.MustFromBig(blobFeeCap), @@ -133,22 +170,33 @@ func ExecuteBlobTransaction(client *ethclient.Client, authAcct bb.AuthAcct, numB Sidecar: sideCar, }) - // Sign the transaction with the authenticated account's private key + // Create the transaction options with the private key and chain ID auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) if err != nil { - log.Error("Failed to create keyed transactor", "error", err) + 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("Failed to sign transaction", "error", err) + slog.Default().Error("Failed to sign blob transaction", + slog.String("function", "Signer"), + slog.Any("error", err)) return nil, 0, err } + + 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 } - +// makeSidecar creates a sidecar for the given blobs by generating commitments and proofs. func makeSidecar(blobs []kzg4844.Blob) *types.BlobTxSidecar { var ( commitments []kzg4844.Commitment @@ -171,6 +219,7 @@ func makeSidecar(blobs []kzg4844.Blob) *types.BlobTxSidecar { } } +// randBlobs generates a slice of random blobs. func randBlobs(n int) []kzg4844.Blob { blobs := make([]kzg4844.Blob, n) for i := 0; i < n; i++ { @@ -179,6 +228,7 @@ func randBlobs(n int) []kzg4844.Blob { return blobs } +// randBlob generates a single random blob. func randBlob() kzg4844.Blob { var blob kzg4844.Blob for i := 0; i < len(blob); i += gokzg4844.SerializedScalarSize { @@ -188,14 +238,17 @@ func randBlob() kzg4844.Blob { return blob } +// randFieldElement generates a random field element. func randFieldElement() [32]byte { bytes := make([]byte, 32) _, err := rand.Read(bytes) if err != nil { - panic("failed to get 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) return gokzg4844.SerializeScalar(r) -} \ No newline at end of file +} diff --git a/internal/eth/service.go b/internal/eth/service.go new file mode 100644 index 0000000..be2b4a1 --- /dev/null +++ b/internal/eth/service.go @@ -0,0 +1,29 @@ +package eth + +import ( + "log/slog" + "time" + + "github.com/ethereum/go-ethereum/ethclient" + bb "github.com/primev/preconf_blob_bidder/internal/mevcommit" +) + +// Service is used to manage the struct for eth package stateful variables used for executing and sending transactions. +type Service struct { + Client *ethclient.Client + AuthAcct bb.AuthAcct + DefaultTimeout time.Duration + Logger *slog.Logger + RPCURL string +} + + +func NewService(client *ethclient.Client, authAcct bb.AuthAcct, defaultTimeout time.Duration, rpcurl string, logger *slog.Logger) *Service { + return &Service{ + Client: client, + AuthAcct: authAcct, + DefaultTimeout: defaultTimeout, + Logger: logger, + RPCURL: rpcurl, + } +} diff --git a/internal/mevcommit/bidderapi.go b/internal/mevcommit/bidderapi.go new file mode 100644 index 0000000..66f935c --- /dev/null +++ b/internal/mevcommit/bidderapi.go @@ -0,0 +1,245 @@ +// Package mevcommit provides functionality for interacting with the mev-commit protocol, +// including sending bids for blob transactions and saving bid requests and responses. +package mevcommit + +import ( + "context" + "encoding/hex" + "fmt" + "io" + "math/big" + "strings" + "time" + + "log/slog" + + "github.com/ethereum/go-ethereum/core/types" + pb "github.com/primev/preconf_blob_bidder/internal/bidderpb" +) + +// 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) +} + +// SendPreconfBid sends a preconfirmation bid to the bidder client +func SendPreconfBid(bidderClient BidderInterface, input interface{}, blockNumber int64, randomEthAmount float64) { + // Get current time in milliseconds + currentTime := time.Now().UnixMilli() + + // Define bid decay start and end + decayStart := currentTime + decayEnd := currentTime + int64(time.Duration(36*time.Second).Milliseconds()) // Bid decay is 36 seconds (2 blocks) + + // Convert the random ETH amount to wei (1 ETH = 10^18 wei) + bigEthAmount := big.NewFloat(randomEthAmount) + weiPerEth := big.NewFloat(1e18) + bigWeiAmount := new(big.Float).Mul(bigEthAmount, weiPerEth) + + // Convert big.Float to big.Int + randomWeiAmount := new(big.Int) + bigWeiAmount.Int(randomWeiAmount) + + // Convert the amount to a string for the bidder + amount := randomWeiAmount.String() + + // Determine how to handle the input + var responseClient pb.Bidder_SendBidClient + var err error + switch v := input.(type) { + case string: + // Input is a string, process it as a transaction hash + txHash := strings.TrimPrefix(v, "0x") + 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 { + slog.Warn("Transaction is nil, cannot send bid.") + return + } + // Input is a transaction object, send the transaction object + 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: + 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 { + 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 { + slog.Info("Bid response received: EOF", + "txHash", fmt.Sprintf("%v", input), + "blockNumber", blockNumber, + "amount_ETH", randomEthAmount, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) + } else if recvErr != nil { + slog.Warn("Error receiving bid response", + "err", recvErr, + "txHash", fmt.Sprintf("%v", input), + "blockNumber", blockNumber, + "decayStart", decayStart, + "decayEnd", decayEnd, + ) + } else { + 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) + if err != nil { + return nil, err + } + + bidRequest := b.createBidRequest(amount, blockNumber, decayStart, decayEnd, txHashes, rawTransactions) + + response, err := b.sendBidRequest(bidRequest) + if err != nil { + return nil, err + } + + b.receiveBidResponses(response) + + return response, nil +} + +// parseInput processes the input and converts it to either transaction hashes or raw transactions. +func (b *Bidder) parseInput(input interface{}) ([]string, []string, error) { + var txHashes []string + var rawTransactions []string + + switch v := input.(type) { + case []string: + txHashes = make([]string, len(v)) + for i, hash := range v { + txHashes[i] = strings.TrimPrefix(hash, "0x") + } + case []*types.Transaction: + rawTransactions = make([]string, len(v)) + for i, tx := range v { + rlpEncodedTx, err := tx.MarshalBinary() + if err != nil { + 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: + 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) + } + + return txHashes, rawTransactions, nil +} + +// createBidRequest builds a Bid request using the provided data. +func (b *Bidder) createBidRequest(amount string, blockNumber, decayStart, decayEnd int64, txHashes, rawTransactions []string) *pb.Bid { + bidRequest := &pb.Bid{ + Amount: amount, + BlockNumber: blockNumber, + DecayStartTimestamp: decayStart, + DecayEndTimestamp: decayEnd, + } + + if len(txHashes) > 0 { + bidRequest.TxHashes = txHashes + } else if len(rawTransactions) > 0 { + bidRequest.RawTransactions = rawTransactions + } + + return bidRequest +} + +// sendBidRequest sends the prepared bid request to the mev-commit client. +func (b *Bidder) sendBidRequest(bidRequest *pb.Bid) (pb.Bidder_SendBidClient, error) { + ctx := context.Background() + response, err := b.client.SendBid(ctx, bidRequest) + if err != nil { + slog.Error("Failed to send bid", + "err", err, + ) + return nil, fmt.Errorf("failed to send bid: %w", err) + } + + return response, nil +} + +// receiveBidResponses processes the responses from the bid request. +func (b *Bidder) receiveBidResponses(response pb.Bidder_SendBidClient) { + for { + msg, err := response.Recv() + if err == io.EOF { + // End of stream + break + } + if err != nil { + slog.Error("Failed to receive bid response", + "err", err, + ) + continue + } + + slog.Info("Bid accepted", + "commitmentDetails", msg, + ) + } + + startTimeBeforeSaveResponses := time.Now() + slog.Info("End Time", + "time", startTimeBeforeSaveResponses, + ) +} diff --git a/internal/mevcommit/bidderapi_test.go b/internal/mevcommit/bidderapi_test.go new file mode 100644 index 0000000..f40c226 --- /dev/null +++ b/internal/mevcommit/bidderapi_test.go @@ -0,0 +1,255 @@ +package mevcommit + +import ( + "context" + "errors" + "io" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/core/types" + pb "github.com/primev/preconf_blob_bidder/internal/bidderpb" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" +) + +// MockBidderClient is a mock implementation of BidderInterface. +type MockBidderClient struct { + mock.Mock +} + +func (m *MockBidderClient) SendBid(input interface{}, amount string, blockNumber, decayStart, decayEnd int64) (pb.Bidder_SendBidClient, error) { + args := m.Called(input, amount, blockNumber, decayStart, decayEnd) + return args.Get(0).(pb.Bidder_SendBidClient), args.Error(1) +} + +// MockBidderSendBidClient is a mock implementation of pb.Bidder_SendBidClient. +type MockBidderSendBidClient struct { + mock.Mock +} + +func (m *MockBidderSendBidClient) Recv() (*pb.Commitment, error) { + args := m.Called() + commitment, _ := args.Get(0).(*pb.Commitment) + return commitment, args.Error(1) +} + + +func (m *MockBidderSendBidClient) Header() (metadata.MD, error) { + return nil, nil +} + +func (m *MockBidderSendBidClient) Trailer() metadata.MD { + return nil +} + +func (m *MockBidderSendBidClient) CloseSend() error { + return nil +} + +func (m *MockBidderSendBidClient) Context() context.Context { + return context.Background() +} + +func (m *MockBidderSendBidClient) SendMsg(msg interface{}) error { + return nil +} + +func (m *MockBidderSendBidClient) RecvMsg(msg interface{}) error { + return nil +} + +// Define the custom mock transaction type outside of the test function +type MockTransaction struct { + types.Transaction + mock.Mock +} + +// Define the MarshalBinary method outside the test function +func (m *MockTransaction) MarshalBinary() ([]byte, error) { + args := m.Called() + return args.Get(0).([]byte), args.Error(1) +} + +func TestSendPreconfBid(t *testing.T) { + // Initialize the mock Bidder client + mockBidder := new(MockBidderClient) + mockSendBidClient := new(MockBidderSendBidClient) + + bidAmount := 1.0 + + // Correctly calculate bidAmountInWei as "1000000000000000000" + bigEthAmount := big.NewFloat(bidAmount) + weiPerEth := big.NewFloat(1e18) + bigWeiAmount := new(big.Float).Mul(bigEthAmount, weiPerEth) + randomWeiAmount := new(big.Int) + bigWeiAmount.Int(randomWeiAmount) + bidAmountInWei := randomWeiAmount.String() // "1000000000000000000" + + // Define the hard-coded legitimate transaction hash + transactionHash := "0xae0a7a0fd02f7617d815000d6322e564dcaccad49fc0b4cb3084b6c6036c37a2" + + // Expected input and parameters + expectedInput := []string{strings.TrimPrefix(transactionHash, "0x")} // "ae0a7a0fd02f7617d815000d6322e564dcaccad49fc0b4cb3084b6c6036c37a2" + expectedAmount := bidAmountInWei + expectedBlockNumber := int64(100) + + // Setup expectations for SendBid + mockBidder.On("SendBid", + expectedInput, + expectedAmount, + expectedBlockNumber, + mock.AnythingOfType("int64"), // decayStart + mock.AnythingOfType("int64"), // decayEnd + ).Return(mockSendBidClient, nil) + + // Setup expectations for Recv to return io.EOF (indicating end of response stream) + mockSendBidClient.On("Recv").Return(nil, io.EOF) + + // Call SendPreconfBid with the transaction hash, block number, and bid amount + SendPreconfBid(mockBidder, transactionHash, expectedBlockNumber, bidAmount) + + // Assert that all expectations were met + mockBidder.AssertExpectations(t) + mockSendBidClient.AssertExpectations(t) +} + +func TestUnsupportedInputType(t *testing.T) { + // Initialize the mock Bidder client + mockBidder := new(MockBidderClient) + + // No expectations set because SendBid should not be called + + // Call SendPreconfBid with an unsupported input type + SendPreconfBid(mockBidder, 12345, 100, 1.0) + + // Assert that SendBid was not called + mockBidder.AssertNotCalled(t, "SendBid", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + +func TestSendBidWithTxHashes(t *testing.T) { + // Initialize the mock Bidder client + mockBidder := new(MockBidderClient) + mockSendBidClient := new(MockBidderSendBidClient) + + // Setup parameters for SendBid with txHashes + transactionHashes := []string{"0x1234567890abcdef", "0xfedcba0987654321"} + + // Remove "0x" prefix from each transaction hash + for i, tx := range transactionHashes { + transactionHashes[i] = strings.TrimPrefix(tx, "0x") + } + + expectedTxHashes := []string{"1234567890abcdef", "fedcba0987654321"} + expectedAmount := "1000000000000000000" // Example amount in wei + expectedBlockNumber := int64(100) + decayStart := int64(1000) + decayEnd := int64(2000) + + // Setup expectations for SendBid + mockBidder.On("SendBid", expectedTxHashes, expectedAmount, expectedBlockNumber, decayStart, decayEnd).Return(mockSendBidClient, nil) + + // Setup expectations for Recv to return io.EOF + mockSendBidClient.On("Recv").Return(nil, io.EOF) + + // Call SendBid with []string input + response, err := mockBidder.SendBid(transactionHashes, expectedAmount, expectedBlockNumber, decayStart, decayEnd) + require.NoError(t, err) + require.NotNil(t, response) + + // Call Recv to satisfy the expectation + _, err = mockSendBidClient.Recv() + require.ErrorIs(t, err, io.EOF) // Check that Recv() returns io.EOF as expected + + // Verify expectations + mockBidder.AssertExpectations(t) + mockSendBidClient.AssertExpectations(t) +} +func TestSendBidUnsupportedInputType(t *testing.T) { + // Initialize the mock Bidder client and BidderSendBidClient + mockBidder := new(MockBidderClient) + mockSendBidClient := new(MockBidderSendBidClient) + + // Set up SendBid mock to return mockSendBidClient with an error + mockBidder.On("SendBid", mock.AnythingOfType("int"), mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(mockSendBidClient, errors.New("unsupported input type")) + + // Call SendBid with unsupported input type and verify the error + unsupportedInput := 12345 + _, err := mockBidder.SendBid(unsupportedInput, "1000000000000000000", 100, 1000, 2000) + + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported input type") +} + + +func TestSendBidWithRawTransactions(t *testing.T) { + // Initialize the mock Bidder client and SendBid client + mockBidder := new(MockBidderClient) + mockSendBidClient := new(MockBidderSendBidClient) + + t.Run("TestSendBidWithRawTransactions", func(t *testing.T) { + expectedAmount := "1000000000000000000" // Example amount in wei + expectedBlockNumber := int64(100) + decayStart := int64(1000) + decayEnd := int64(2000) + + // Use *types.Transaction instead of MockTransaction to match SendBid function signature + tx := new(types.Transaction) + + // Log to track the start of the test + t.Log("Starting TestSendBidWithRawTransactions") + + // Set up expectation for SendBid to return mockSendBidClient and a marshalling error + mockBidder.On("SendBid", mock.Anything, expectedAmount, expectedBlockNumber, decayStart, decayEnd). + Return(mockSendBidClient, errors.New("mock marshalling error")).Once() + + // Call SendBid with []*types.Transaction input + _, err := mockBidder.SendBid([]*types.Transaction{tx}, expectedAmount, expectedBlockNumber, decayStart, decayEnd) + + // Validate the error and log result + require.Error(t, err, "Expected an error due to mock marshalling error") + require.Contains(t, err.Error(), "mock marshalling error", "Error message should contain 'mock marshalling error'") + + // Verify expectations + mockBidder.AssertExpectations(t) + + t.Log("TestSendBidWithRawTransactions completed") + }) +} + +func TestSendBidSuccess(t *testing.T) { + mockBidder := new(MockBidderClient) + mockSendBidClient := new(MockBidderSendBidClient) + + txHashes := []string{"0xabc123", "0xdef456"} + expectedAmount := "1000000000000000000" + expectedBlockNumber := int64(100) + decayStart := int64(1000) + decayEnd := int64(2000) + + mockBidder.On("SendBid", mock.Anything, expectedAmount, expectedBlockNumber, decayStart, decayEnd). + Return(mockSendBidClient, nil).Once() + + _, err := mockBidder.SendBid(txHashes, expectedAmount, expectedBlockNumber, decayStart, decayEnd) + + require.NoError(t, err, "Expected no error for successful bid") + mockBidder.AssertExpectations(t) +} + + +func TestSendBidRequestError(t *testing.T) { + mockBidder := new(MockBidderClient) + mockSendBidClient := new(MockBidderSendBidClient) + + // Provide the mockSendBidClient instead of nil + mockBidder.On("SendBid", mock.Anything, "1000000000000000000", int64(100), int64(1000), int64(2000)). + Return(mockSendBidClient, errors.New("mock send bid error")) + + _, err := mockBidder.SendBid([]string{"0xabc123"}, "1000000000000000000", 100, 1000, 2000) + + require.Error(t, err, "Expected an error due to mock send bid error") + require.Contains(t, err.Error(), "mock send bid error", "Error message should contain 'mock send bid error'") +} diff --git a/internal/mevcommit/client.go b/internal/mevcommit/client.go new file mode 100644 index 0000000..714ada9 --- /dev/null +++ b/internal/mevcommit/client.go @@ -0,0 +1,300 @@ +// Package mevcommit provides functionality for interacting with the mev-commit protocol, +// including setting up a bidder client, connecting to an Ethereum node, and handling +// account authentication. +package mevcommit + +import ( + "context" + "crypto/ecdsa" + "fmt" + "log/slog" + "math" + "time" + + pb "github.com/primev/preconf_blob_bidder/internal/bidderpb" + "google.golang.org/grpc" + + "github.com/ethereum/go-ethereum" + "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/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "google.golang.org/grpc/credentials/insecure" +) + +// BidderConfig holds the configuration settings for the mev-commit bidder node. +type BidderConfig struct { + ServerAddress string `json:"server_address" yaml:"server_address"` // The address of the gRPC server for the bidder node. + LogFmt string `json:"log_fmt" yaml:"log_fmt"` // The format for logging output. + LogLevel string `json:"log_level" yaml:"log_level"` // The level of logging detail. +} + +// Bidder utilizes the mev-commit bidder client to interact with the mev-commit chain. +type Bidder struct { + client pb.BidderClient // gRPC client for interacting with the mev-commit bidder service. +} + +// GethConfig holds configuration settings for a Geth node to connect to the mev-commit chain. +type GethConfig struct { + Endpoint string `json:"endpoint" yaml:"endpoint"` // The RPC endpoint for connecting to the Ethereum node. +} + +// AuthAcct holds the private key, public key, address, and transaction authorization information for an account. +type AuthAcct struct { + PrivateKey *ecdsa.PrivateKey // The private key for the account. + PublicKey *ecdsa.PublicKey // The public key derived from the private key. + Address common.Address // The Ethereum address derived from the public key. + Auth *bind.TransactOpts // The transaction options for signing transactions. +} + +// NewBidderClient creates a new gRPC client connection to the bidder service and returns a Bidder instance. +// +// Parameters: +// - cfg: The BidderConfig struct containing the server address and logging settings. +// +// Returns: +// - A pointer to a Bidder struct, or an error if the connection fails. +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 { + slog.Error("Failed to connect to gRPC server", + "error", err, + "server_address", cfg.ServerAddress, + ) + return nil, err + } + + // Create a new bidder client using the gRPC connection + client := pb.NewBidderClient(conn) + return &Bidder{client: client}, nil +} + +// NewGethClient connects to an Ethereum-compatible chain using the provided RPC endpoint. +// +// Parameters: +// - endpoint: The RPC endpoint of the Ethereum node. +// +// Returns: +// - A pointer to an ethclient.Client for interacting with the Ethereum node, or an error if the connection fails. +func NewGethClient(endpoint string) (*ethclient.Client, error) { + // Create a context with a 15-second timeout + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + // Use DialContext to establish a connection with the 15-second timeout + client, err := rpc.DialContext(ctx, endpoint) + if err != nil { + 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) + slog.Info("Connected to Ethereum RPC endpoint", + "endpoint", MaskEndpoint(endpoint), + ) + return ec, nil +} + +// AuthenticateAddress converts a hex-encoded private key string to an AuthAcct struct, +// which contains the account's private key, public key, address, and transaction authorization. +// +// Parameters: +// - privateKeyHex: The hex-encoded private key string. +// - client: The ethclient.Client to interact with the Ethereum node. +// +// Returns: +// - An AuthAcct struct, or an error if authentication fails. +func AuthenticateAddress(privateKeyHex string, client *ethclient.Client) (AuthAcct, error) { + if privateKeyHex == "" { + 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 { + slog.Error("Failed to load private key", + "error", err, + ) + return AuthAcct{}, err + } + + // Extract the public key from the private key + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + slog.Error("Failed to assert public key type") + return AuthAcct{}, fmt.Errorf("failed to assert public key type") + } + + // Generate the Ethereum address from the public key + address := crypto.PubkeyToAddress(*publicKeyECDSA) + + // Set up a context with a 15-second timeout for fetching the chain ID + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() // Ensure the context is canceled after the operation + + chainID, err := client.ChainID(ctx) + if err != nil { + 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 { + 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 + slog.Info("Authenticated account", + "address", address.Hex(), + ) + + return AuthAcct{ + PrivateKey: privateKey, + PublicKey: publicKeyECDSA, + Address: address, + Auth: auth, + }, nil +} + +// ConnectRPCClientWithRetries attempts to connect to the RPC client with retries and exponential backoff. +// +// Parameters: +// - rpcEndpoint: The RPC endpoint to connect to. +// - maxRetries: The maximum number of retry attempts. +// - timeout: The timeout duration for each connection attempt. +// +// Returns: +// - A pointer to an ethclient.Client if successful, or an error if all retries fail. +func ConnectRPCClientWithRetries(rpcEndpoint string, maxRetries int, timeout time.Duration) (*ethclient.Client, error) { + var rpcClient *ethclient.Client + var err error + + for i := 0; i < maxRetries; i++ { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + rpcClient, err = ethclient.DialContext(ctx, rpcEndpoint) + if err == nil { + slog.Info("Successfully connected to RPC client", + "rpc_endpoint", MaskEndpoint(rpcEndpoint), + "attempt", i+1, + ) + return rpcClient, nil + } + + 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 + } + + slog.Error("Failed to connect to RPC client after maximum retries", + "error", err, + "rpc_endpoint", MaskEndpoint(rpcEndpoint), + "max_retries", maxRetries, + ) + return nil, err +} + +// ConnectWSClient attempts to connect to the WebSocket client with continuous retries. +// +// Parameters: +// - wsEndpoint: The WebSocket endpoint to connect to. +// +// Returns: +// - A pointer to an ethclient.Client if successful, or an error if unable to connect. +func ConnectWSClient(wsEndpoint string) (*ethclient.Client, error) { + for { + wsClient, err := NewGethClient(wsEndpoint) + if err == nil { + return wsClient, nil + } + slog.Warn("Failed to connect to WebSocket client, retrying in 10 seconds...", + "error", err, + "ws_endpoint", MaskEndpoint(wsEndpoint), + ) + time.Sleep(10 * time.Second) + } +} + +// ReconnectWSClient attempts to reconnect to the WebSocket client with limited retries. +// +// Parameters: +// - wsEndpoint: The WebSocket endpoint to reconnect to. +// - headers: The channel to subscribe to new headers. +// +// Returns: +// - A pointer to an ethclient.Client and an ethereum.Subscription if successful, or nil values and an error if all retries fail. +func ReconnectWSClient(wsEndpoint string, headers chan *types.Header) (*ethclient.Client, ethereum.Subscription, error) { + var wsClient *ethclient.Client + var sub ethereum.Subscription + var err error + + for i := 0; i < 10; i++ { // Retry logic for WebSocket connection + wsClient, err = ConnectWSClient(wsEndpoint) + if err == nil { + 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) + defer cancel() + + sub, err = wsClient.SubscribeNewHead(ctx, headers) + if err == nil { + return wsClient, sub, nil + } + + slog.Warn("Failed to subscribe to new headers after reconnecting", + "error", err, + ) + } + + 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) + } + + slog.Error("Failed to reconnect WebSocket client after maximum retries", + "error", err, + "ws_endpoint", MaskEndpoint(wsEndpoint), + "max_retries", 10, + ) + return nil, nil, err +} + +// MaskEndpoint masks sensitive parts of the endpoint URLs. +// +// Parameters: +// - endpoint: The full endpoint URL. +// +// Returns: +// - A masked version of the endpoint if its length exceeds 10 characters, otherwise a fixed mask. +func MaskEndpoint(endpoint string) string { + if len(endpoint) > 10 { + return endpoint[:10] + "*****" + } + return "*****" +} diff --git a/internal/mevcommit/contracts.go b/internal/mevcommit/contracts.go new file mode 100644 index 0000000..e45a97b --- /dev/null +++ b/internal/mevcommit/contracts.go @@ -0,0 +1,446 @@ +// Package mevcommit provides functionality for interacting with the mev-commit protocol, +// including managing bids, deposits, withdrawals, and event listeners on the Ethereum blockchain. +package mevcommit + +import ( + "context" + "fmt" + "math/big" + "os" + "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" +) + +type Service struct { + Client *ethclient.Client + AuthAcct *AuthAcct + Logger *slog.Logger + DefaultTimeout time.Duration + BidderRegistryAddress common.Address + BlockTrackerAddress common.Address + PreconfManagerAddress common.Address +} + +type CommitmentStoredEvent struct { + CommitmentIndex [32]byte + Bidder common.Address + Commiter common.Address + Bid uint64 + BlockNumber uint64 + BidHash [32]byte + DecayStartTimeStamp uint64 + DecayEndTimeStamp uint64 + TxnHash string + CommitmentHash [32]byte + BidSignature []byte + CommitmentSignature []byte + DispatchTimestamp uint64 + SharedSecretKey []byte +} + +func NewService(client *ethclient.Client, authAcct *AuthAcct, logger *slog.Logger, envFile string) (*Service, error) { + if envFile == "" { + envFile = ".env" + } + + if _, err := os.Stat(envFile); err == nil { + if err := loadEnvFile(envFile); err != nil { + logger.Error("Error loading .env file", + "err", err, + "env_file", envFile, + ) + return nil, err + } + } + + bidderRegistry := os.Getenv("BIDDER_REGISTRY_ADDRESS") + if bidderRegistry == "" { + bidderRegistry = "0x401B3287364f95694c43ACA3252831cAc02e5C41" + } + bidderRegistryAddress := common.HexToAddress(bidderRegistry) + + blockTracker := os.Getenv("BLOCK_TRACKER_ADDRESS") + if blockTracker == "" { + blockTracker = "0x7538F3AaA07dA1990486De21A0B438F55e9639e4" + } + blockTrackerAddress := common.HexToAddress(blockTracker) + + preconfManager := os.Getenv("PRECONF_MANAGER_ADDRESS") + if preconfManager == "" { + preconfManager = "0x9433bCD9e89F923ce587f7FA7E39e120E93eb84D" + } + preconfManagerAddress := common.HexToAddress(preconfManager) + + logger.Info("Loaded contract addresses", + "BidderRegistry", bidderRegistryAddress.Hex(), + "BlockTracker", blockTrackerAddress.Hex(), + "PreconfManager", preconfManagerAddress.Hex(), + ) + + service := &Service{ + Client: client, + AuthAcct: authAcct, + Logger: logger, + DefaultTimeout: 15 * time.Second, + BidderRegistryAddress: bidderRegistryAddress, + BlockTrackerAddress: blockTrackerAddress, + PreconfManagerAddress: preconfManagerAddress, + } + + return service, nil +} + +func loadEnvFile(filePath string) error { + data, err := os.ReadFile(filePath) + if err != nil { + slog.Error("Failed to read environment file", + "err", err, + "file_path", filePath, + ) + return err + } + + lines := strings.Split(string(data), "\n") + for _, line := range lines { + if line == "" || strings.HasPrefix(line, "#") { + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + os.Setenv(key, value) + } + + slog.Info("Environment variables loaded from file", + "file_path", filePath, + ) + + return nil +} + +func LoadABI(filePath string) (abi.ABI, error) { + data, err := os.ReadFile(filePath) + if err != nil { + 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 { + slog.Error("Failed to parse ABI file", + "err", err, + "file_path", filePath, + ) + return abi.ABI{}, err + } + + slog.Info("ABI file loaded and parsed successfully", + "file_path", filePath, + ) + + return parsedABI, nil +} + +func (s *Service) WindowHeight() (*big.Int, error) { + blockTrackerABI, err := LoadABI("abi/BlockTracker.abi") + if err != nil { + return nil, fmt.Errorf("failed to load ABI file: %v", err) + } + + blockTrackerContract := bind.NewBoundContract(s.BlockTrackerAddress, blockTrackerABI, s.Client, s.Client, s.Client) + + var currentWindowResult []interface{} + err = blockTrackerContract.Call(nil, ¤tWindowResult, "getCurrentWindow") + if err != nil { + s.Logger.Error("Failed to get current window", + "err", err, + "function", "getCurrentWindow", + ) + return nil, fmt.Errorf("failed to get current window: %v", err) + } + + currentWindow, ok := currentWindowResult[0].(*big.Int) + if !ok { + s.Logger.Error("Failed to convert current window to *big.Int") + return nil, fmt.Errorf("conversion to *big.Int failed") + } + + s.Logger.Info("Retrieved current bidding window height", + "current_window", currentWindow.String(), + ) + + return currentWindow, nil +} + +func (s *Service) GetMinDeposit() (*big.Int, error) { + bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") + if err != nil { + return nil, fmt.Errorf("failed to load ABI file: %v", err) + } + + bidderRegistryContract := bind.NewBoundContract(s.BidderRegistryAddress, bidderRegistryABI, s.Client, s.Client, s.Client) + + var minDepositResult []interface{} + err = bidderRegistryContract.Call(nil, &minDepositResult, "minDeposit") + if err != nil { + s.Logger.Error("Failed to call minDeposit function", + "err", err, + "function", "minDeposit", + ) + return nil, fmt.Errorf("failed to call minDeposit function: %v", err) + } + + minDeposit, ok := minDepositResult[0].(*big.Int) + if !ok { + s.Logger.Error("Failed to convert minDeposit to *big.Int") + return nil, fmt.Errorf("failed to convert minDeposit to *big.Int") + } + + s.Logger.Info("Retrieved minimum deposit amount", + "min_deposit", minDeposit.String(), + ) + + return minDeposit, nil +} + +func (s *Service) DepositIntoWindow(depositWindow *big.Int) (*types.Transaction, error) { + bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") + if err != nil { + return nil, fmt.Errorf("failed to load ABI file: %v", err) + } + + bidderRegistryContract := bind.NewBoundContract(s.BidderRegistryAddress, bidderRegistryABI, s.Client, s.Client, s.Client) + + minDeposit, err := s.GetMinDeposit() + if err != nil { + return nil, fmt.Errorf("failed to get minDeposit: %v", err) + } + + s.AuthAcct.Auth.Value = minDeposit + + tx, err := bidderRegistryContract.Transact(s.AuthAcct.Auth, "depositForSpecificWindow", depositWindow) + if err != nil { + s.Logger.Error("Failed to create deposit transaction", + "err", err, + "function", "depositForSpecificWindow", + ) + return nil, fmt.Errorf("failed to create transaction: %v", err) + } + + s.Logger.Info("Deposit transaction sent", + "tx_hash", tx.Hash().Hex(), + "window", depositWindow.String(), + ) + + ctx, cancel := context.WithTimeout(context.Background(), s.DefaultTimeout) + defer cancel() + receipt, err := bind.WaitMined(ctx, s.Client, tx) + if err != nil { + s.Logger.Error("Transaction mining error", + "err", err, + "tx_hash", tx.Hash().Hex(), + ) + return nil, fmt.Errorf("transaction mining error: %v", err) + } + + if receipt.Status == 1 { + s.Logger.Info("Deposit transaction successful", + "tx_hash", tx.Hash().Hex(), + ) + return tx, nil + } else { + s.Logger.Error("Deposit transaction failed", + "tx_hash", tx.Hash().Hex(), + ) + return nil, fmt.Errorf("transaction failed") + } +} + +func (s *Service) GetDepositAmount(address common.Address, window big.Int) (*big.Int, error) { + bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") + if err != nil { + return nil, fmt.Errorf("failed to load ABI file: %v", err) + } + + bidderRegistryContract := bind.NewBoundContract(s.BidderRegistryAddress, bidderRegistryABI, s.Client, s.Client, s.Client) + + var depositResult []interface{} + err = bidderRegistryContract.Call(nil, &depositResult, "getDeposit", address, window) + if err != nil { + s.Logger.Error("Failed to call getDeposit function", + "err", err, + "function", "getDeposit", + ) + return nil, fmt.Errorf("failed to call getDeposit function: %v", err) + } + + depositAmount, ok := depositResult[0].(*big.Int) + if !ok { + s.Logger.Error("Failed to convert deposit amount to *big.Int") + return nil, fmt.Errorf("failed to convert deposit amount to *big.Int") + } + + s.Logger.Info("Retrieved deposit amount for address and window", + "deposit_amount", depositAmount.String(), + ) + + return depositAmount, nil +} + +func (s *Service) WithdrawFromWindow(window *big.Int) (*types.Transaction, error) { + bidderRegistryABI, err := LoadABI("abi/BidderRegistry.abi") + if err != nil { + return nil, fmt.Errorf("failed to load ABI file: %v", err) + } + + bidderRegistryContract := bind.NewBoundContract(s.BidderRegistryAddress, bidderRegistryABI, s.Client, s.Client, s.Client) + + withdrawalTx, err := bidderRegistryContract.Transact(s.AuthAcct.Auth, "withdrawBidderAmountFromWindow", s.AuthAcct.Address, window) + if err != nil { + s.Logger.Error("Failed to create withdrawal transaction", + "err", err, + "function", "withdrawBidderAmountFromWindow", + ) + return nil, fmt.Errorf("failed to create withdrawal transaction: %v", err) + } + + s.Logger.Info("Withdrawal transaction sent", + "tx_hash", withdrawalTx.Hash().Hex(), + "window", window.String(), + ) + + ctx, cancel := context.WithTimeout(context.Background(), s.DefaultTimeout) + defer cancel() + withdrawalReceipt, err := bind.WaitMined(ctx, s.Client, withdrawalTx) + if err != nil { + s.Logger.Error("Withdrawal transaction mining error", + "err", err, + "tx_hash", withdrawalTx.Hash().Hex(), + ) + return nil, fmt.Errorf("withdrawal transaction mining error: %v", err) + } + + if withdrawalReceipt.Status == 1 { + s.Logger.Info("Withdrawal transaction successful", + "tx_hash", withdrawalTx.Hash().Hex(), + ) + return withdrawalTx, nil + } else { + s.Logger.Error("Withdrawal transaction failed", + "tx_hash", withdrawalTx.Hash().Hex(), + ) + return nil, fmt.Errorf("withdrawal failed") + } +} + +func (s *Service) ListenForCommitmentStoredEvent() { + contractAbi, err := LoadABI("abi/PreConfCommitmentStore.abi") + if err != nil { + s.Logger.Error("Failed to load contract ABI", + "contract", "PreConfCommitmentStore", + "err", err, + ) + return + } + + parentCtx, parentCancel := context.WithCancel(context.Background()) + defer parentCancel() + + headers := make(chan *types.Header) + sub, err := s.Client.SubscribeNewHead(parentCtx, headers) + if err != nil { + s.Logger.Error("Failed to subscribe to new block headers", + "err", err, + ) + return + } + + s.Logger.Info("Subscribed to new block headers for CommitmentStored events") + + for { + select { + case err := <-sub.Err(): + s.Logger.Error("Error with header subscription", + "err", err, + ) + parentCancel() + return + + case header := <-headers: + query := ethereum.FilterQuery{ + Addresses: []common.Address{s.PreconfManagerAddress}, + FromBlock: header.Number, + ToBlock: header.Number, + } + + logs := make(chan types.Log) + ctxLogs, cancelLogs := context.WithTimeout(parentCtx, s.DefaultTimeout) + + subLogs, err := s.Client.SubscribeFilterLogs(ctxLogs, query, logs) + if err != nil { + s.Logger.Error("Failed to subscribe to logs", + "err", err, + ) + cancelLogs() + continue + } + + go func() { + defer cancelLogs() + + for { + select { + case err := <-subLogs.Err(): + s.Logger.Error("Error with log subscription", + "err", err, + ) + return + + case vLog := <-logs: + var event CommitmentStoredEvent + + err := contractAbi.UnpackIntoInterface(&event, "CommitmentStored", vLog.Data) + if err != nil { + s.Logger.Error("Failed to unpack log data", + "err", err, + ) + continue + } + + s.Logger.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 new file mode 100644 index 0000000..58ba55a --- /dev/null +++ b/main.go @@ -0,0 +1,187 @@ +package main + +import ( + "context" + "fmt" + "log/slog" + "math" + "math/big" + "math/rand" + "os" + "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/urfave/cli/v2" +) + +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" +) + +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{ + // Your flags here... + }, + Action: func(c *cli.Context) error { + 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 + + 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, + } + + 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) + } + + slog.Info("Connected to mev-commit client") + + timeout := defaultTimeout + + var rpcClient *ethclient.Client + if !usePayload { + rpcClient, err = bb.ConnectRPCClientWithRetries(rpcEndpoint, 5, timeout) + if err != nil { + slog.Error("Failed to connect to RPC client", "rpcEndpoint", bb.MaskEndpoint(rpcEndpoint), "error", err) + } + if rpcClient == nil { + slog.Error("Failed to connect to RPC client", "rpcEndpoint", bb.MaskEndpoint(rpcEndpoint)) + } else { + slog.Info("Geth client connected (rpc)", + "endpoint", bb.MaskEndpoint(rpcEndpoint), + ) + } + } + + 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) + } + 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) + } + + 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) + } + + service := ee.NewService(wsClient, authAcct, defaultTimeout, rpcEndpoint, logger) + + for { + select { + case err := <-sub.Err(): + if err != nil { + slog.Error("Subscription error", "error", err) + } + case header := <-headers: + var signedTx *types.Transaction + var blockNumber uint64 + var err error + + if numBlob == 0 { + amount := big.NewInt(1e15) + signedTx, blockNumber, err = service.SelfETHTransfer(amount, offset) + } else { + signedTx, blockNumber, err = service.ExecuteBlobTransaction(int(numBlob), offset) + } + + if err != nil { + service.Logger.Error("Failed to execute transaction", "error", err) + continue + } + + if signedTx == nil { + slog.Error("Transaction was not signed or created.") + } else { + slog.Info("Transaction created successfully") + } + + 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 + + // Generate random amount with normal distribution + randomEthAmount := math.Max(rand.NormFloat64()*stdDev+bidAmount, bidAmount) + + if usePayload { + bb.SendPreconfBid(bidderClient, signedTx, int64(blockNumber), randomEthAmount) + } else { + _, err = service.SendBundle(signedTx, blockNumber) + if err != nil { + slog.Error("Failed to send transaction", "rpcEndpoint", bb.MaskEndpoint(rpcEndpoint), "error", err) + } + bb.SendPreconfBid(bidderClient, signedTx.Hash().String(), int64(blockNumber), randomEthAmount) + } + } + } + }, + } + + // Run the app + if err := app.Run(os.Args); err != nil { + slog.Error("Application error", "error", err) + os.Exit(1) + } +}