diff --git a/bridge/standard/pkg/store/store_test.go b/bridge/standard/pkg/store/store_test.go index 7a1c96b4b..961583418 100644 --- a/bridge/standard/pkg/store/store_test.go +++ b/bridge/standard/pkg/store/store_test.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/primev/mev-commit/bridge/standard/pkg/store" @@ -33,7 +34,12 @@ func TestStore(t *testing.T) { "POSTGRES_USER": "user", "POSTGRES_PASSWORD": "password", }, - WaitingFor: wait.ForListeningPort("5432/tcp"), + WaitingFor: wait.ForAll( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(60*time.Second), + wait.ForListeningPort("5432/tcp"), + ), } // Start the PostgreSQL container diff --git a/oracle/pkg/store/store_test.go b/oracle/pkg/store/store_test.go index 6ae63a4f1..3297b1852 100644 --- a/oracle/pkg/store/store_test.go +++ b/oracle/pkg/store/store_test.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/google/go-cmp/cmp" @@ -46,7 +47,12 @@ func TestStore(t *testing.T) { "POSTGRES_USER": "user", "POSTGRES_PASSWORD": "password", }, - WaitingFor: wait.ForListeningPort("5432/tcp"), + WaitingFor: wait.ForAll( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(60*time.Second), + wait.ForListeningPort("5432/tcp"), + ), } // Start the PostgreSQL container diff --git a/tools/preconf-rpc/main.go b/tools/preconf-rpc/main.go index 0fbc2a217..01ada4fa5 100644 --- a/tools/preconf-rpc/main.go +++ b/tools/preconf-rpc/main.go @@ -10,6 +10,7 @@ import ( "syscall" "github.com/ethereum/go-ethereum/common" + "github.com/primev/mev-commit/tools/preconf-rpc/sender" "github.com/primev/mev-commit/tools/preconf-rpc/service" "github.com/primev/mev-commit/x/keysigner" "github.com/primev/mev-commit/x/util" @@ -270,6 +271,12 @@ var ( EnvVars: []string{"PRECONF_RPC_POINTS_API_KEY"}, } + optionLogEncryptionKey = &cli.StringFlag{ + Name: "log-encryption-key", + Usage: "hex-encoded 32-byte AES-256 key for encrypting sensitive data in logs (e.g. raw transactions)", + EnvVars: []string{"PRECONF_RPC_LOG_ENCRYPTION_KEY"}, + } + optionLogFmt = &cli.StringFlag{ Name: "log-fmt", Usage: "log format to use, options are 'text' or 'json'", @@ -381,6 +388,7 @@ func main() { optionLogFmt, optionLogLevel, optionLogTags, + optionLogEncryptionKey, optionKeystorePath, optionKeystorePassword, optionL1RPCHTTPUrl, @@ -490,6 +498,11 @@ func main() { l1ReceiptsURL = c.String(optionL1RPCHTTPUrl.Name) } + logEncryptionKey, err := sender.ParseLogEncryptionKey(c.String(optionLogEncryptionKey.Name)) + if err != nil { + return fmt.Errorf("failed to parse log encryption key: %w", err) + } + sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) @@ -536,6 +549,7 @@ func main() { BarterAPIKey: c.String(optionBarterAPIKey.Name), FastSettlementAddress: common.HexToAddress(c.String(optionFastSettlementAddress.Name)), FastSwapSigner: fastSwapSigner, + LogEncryptionKey: logEncryptionKey, } s, err := service.New(&config) diff --git a/tools/preconf-rpc/sender/sender.go b/tools/preconf-rpc/sender/sender.go index 4585a05a8..777f81d40 100644 --- a/tools/preconf-rpc/sender/sender.go +++ b/tools/preconf-rpc/sender/sender.go @@ -2,8 +2,14 @@ package sender import ( "context" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" "errors" "fmt" + "io" "log/slog" "math" "math/big" @@ -80,6 +86,48 @@ type Transaction struct { isSwap bool } +// encryptForLog encrypts plaintext using AES-256-GCM and returns a base64-encoded +// ciphertext string suitable for logging. Returns empty string if key is nil. +func encryptForLog(key []byte, plaintext string) string { + if len(key) == 0 { + return "" + } + + block, err := aes.NewCipher(key) + if err != nil { + return "" + } + + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return "" + } + + nonce := make([]byte, aesGCM.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "" + } + + ciphertext := aesGCM.Seal(nonce, nonce, []byte(plaintext), nil) + return base64.StdEncoding.EncodeToString(ciphertext) +} + +// ParseLogEncryptionKey parses a hex-encoded 32-byte AES-256 key. +// Returns nil if the input is empty. +func ParseLogEncryptionKey(hexKey string) ([]byte, error) { + if hexKey == "" { + return nil, nil + } + key, err := hex.DecodeString(strings.TrimPrefix(hexKey, "0x")) + if err != nil { + return nil, fmt.Errorf("invalid log encryption key hex: %w", err) + } + if len(key) != 32 { + return nil, fmt.Errorf("log encryption key must be 32 bytes, got %d", len(key)) + } + return key, nil +} + func effectiveFeePerGas(tx *types.Transaction) *big.Int { if tx == nil { return big.NewInt(0) @@ -198,6 +246,7 @@ type TxSender struct { receiptMtx sync.Mutex metrics *metrics explorerSubmitter ExplorerSubmitter + logEncryptionKey []byte } func noOpFastTrack(_ []*bidderapiv1.Commitment, _ bool) bool { @@ -215,6 +264,7 @@ func NewTxSender( backrunner Backrunner, settlementChainId *big.Int, explorerSubmitter ExplorerSubmitter, + logEncryptionKey []byte, logger *slog.Logger, ) (*TxSender, error) { txnAttemptHistory, err := lru.New[common.Hash, *txnAttempt](1000) @@ -251,6 +301,7 @@ func NewTxSender( receiptSignal: make(map[common.Hash][]chan struct{}), metrics: newMetrics(), explorerSubmitter: explorerSubmitter, + logEncryptionKey: logEncryptionKey, }, nil } @@ -649,13 +700,16 @@ BID_LOOP: } retryTicker.Reset(result.timeUntillNextBlock + 1*time.Second) default: - logger.Warn( - "Not all builders committed to the bid", + warnFields := []any{ "noOfProviders", txn.noOfProviders, "noOfCommitments", len(txn.commitments), "blockNumber", result.blockNumber, "bidAmount", result.bidAmount.String(), - ) + } + if encrypted := encryptForLog(t.logEncryptionKey, txn.Raw); encrypted != "" { + warnFields = append(warnFields, "encryptedRawTx", encrypted) + } + logger.Warn("Not all builders committed to the bid", warnFields...) retryTicker.Reset(defaultRetryDelay) } select { diff --git a/tools/preconf-rpc/sender/sender_test.go b/tools/preconf-rpc/sender/sender_test.go index 635455f69..9a6e0ebe9 100644 --- a/tools/preconf-rpc/sender/sender_test.go +++ b/tools/preconf-rpc/sender/sender_test.go @@ -344,6 +344,7 @@ func TestSender(t *testing.T) { &mockBackrunner{}, big.NewInt(1), // Settlement chain ID &MockExplorerSubmitter{}, + nil, // no log encryption key in tests util.NewTestLogger(os.Stdout), ) if err != nil { @@ -599,6 +600,7 @@ func TestCancelTransaction(t *testing.T) { &mockBackrunner{}, big.NewInt(1), // Settlement chain ID &MockExplorerSubmitter{}, + nil, // no log encryption key in tests util.NewTestLogger(os.Stdout), ) if err != nil { @@ -688,6 +690,7 @@ func TestIgnoreProvidersOnRetry(t *testing.T) { &mockBackrunner{}, big.NewInt(1), // Settlement chain ID &MockExplorerSubmitter{}, + nil, // no log encryption key in tests util.NewTestLogger(io.Discard), ) if err != nil { diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index 61bb84f9d..f2f6a2377 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -95,6 +95,7 @@ type Config struct { BarterAPIKey string FastSettlementAddress common.Address FastSwapSigner keysigner.KeySigner // Separate wallet for FastSwap executor + LogEncryptionKey []byte } type Service struct { @@ -353,6 +354,7 @@ func New(config *Config) (*Service, error) { brunner, settlementChainID, expSubmitter, + config.LogEncryptionKey, config.Logger.With("module", "txsender"), ) if err != nil { diff --git a/tools/preconf-rpc/store/store_test.go b/tools/preconf-rpc/store/store_test.go index 8773410e3..8f4464527 100644 --- a/tools/preconf-rpc/store/store_test.go +++ b/tools/preconf-rpc/store/store_test.go @@ -33,7 +33,12 @@ func TestStore(t *testing.T) { "POSTGRES_USER": "user", "POSTGRES_PASSWORD": "password", }, - WaitingFor: wait.ForListeningPort("5432/tcp"), + WaitingFor: wait.ForAll( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2). + WithStartupTimeout(60*time.Second), + wait.ForListeningPort("5432/tcp"), + ), } // Start the PostgreSQL container