From 4625fe6e899e3959617213b16223e790902f2ee0 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:57:35 -0700 Subject: [PATCH 01/19] Update blockbuilder.go --- cl/blockbuilder/blockbuilder.go | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index dbbe775fd..95e4befb6 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -35,6 +35,7 @@ type EngineClient interface { GetPayloadV4(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) HeaderByNumber(ctx context.Context, number *big.Int) (*etypes.Header, error) + PendingTransactionCount(ctx context.Context) (uint, error) } type stateManager interface { @@ -121,6 +122,19 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { ) currentCallTime := time.Now() + pendingTxCount, err := bb.engineCl.PendingTransactionCount(ctx) + if err != nil { + return fmt.Errorf("failed to get pending transaction count: %w", err) + } + timeSinceLastBlock := currentCallTime.Sub(bb.lastBlockTime) + if pendingTxCount == 0 && timeSinceLastBlock < bb.buildEmptyBlocksDelay { + bb.logger.Info( + "Leader: Skipping empty block", + "timeSinceLastBlock", timeSinceLastBlock, + ) + return ErrEmptyBlock + } + // Load execution head to get previous block timestamp err = util.RetryWithBackoff(ctx, maxAttempts, bb.logger, func() error { head, err = bb.loadExecutionHead(ctx) @@ -221,17 +235,6 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { return fmt.Errorf("failed to get payload: %w", err) } - hasTransactions := len(payloadResp.ExecutionPayload.Transactions) > 0 - now := time.Now() - timeSinceLastBlock := now.Sub(bb.lastBlockTime) - if !hasTransactions && timeSinceLastBlock < bb.buildEmptyBlocksDelay { - bb.logger.Info( - "Leader: Skipping empty block", - "timeSinceLastBlock", timeSinceLastBlock, - ) - return ErrEmptyBlock - } - payloadData, err := msgpack.Marshal(payloadResp.ExecutionPayload) if err != nil { return fmt.Errorf("failed to marshal payload: %w", err) @@ -255,7 +258,7 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { "PayloadID", payloadIDStr, ) - bb.lastBlockTime = now + bb.lastBlockTime = time.Now() return nil } From 9ef727188692178792cc575245471082c2e3480f Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:08:38 -0700 Subject: [PATCH 02/19] Update singlenode.go --- cl/singlenode/singlenode.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index bbe13a016..911701072 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -270,7 +270,9 @@ func (app *SingleNodeApp) runLoop() { if err != nil { if errors.Is(err, blockbuilder.ErrEmptyBlock) { - app.logger.Info("empty block produced, waiting for new payload") + noPendingTxesTimeout := 10 * time.Millisecond + app.logger.Info("no pending transactions, will try again in: ", "timeout", noPendingTxesTimeout) + time.Sleep(noPendingTxesTimeout) continue } else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { app.logger.Info("context canceled or deadline exceeded, stopping block production") From 4dfe5f3bafbdb295bf2efa1e0fb8b16dfe643dca Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:09:30 -0700 Subject: [PATCH 03/19] Update blockbuilder_test.go --- cl/blockbuilder/blockbuilder_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cl/blockbuilder/blockbuilder_test.go b/cl/blockbuilder/blockbuilder_test.go index 93f69e1bb..d52f60739 100644 --- a/cl/blockbuilder/blockbuilder_test.go +++ b/cl/blockbuilder/blockbuilder_test.go @@ -59,6 +59,11 @@ func (m *MockEngineClient) HeaderByNumber(ctx context.Context, number *big.Int) return args.Get(0).(*etypes.Header), args.Error(1) } +func (m *MockEngineClient) PendingTransactionCount(ctx context.Context) (uint, error) { + args := m.Called(ctx) + return args.Get(0).(uint), args.Error(1) +} + func TestBlockBuilder_startBuild(t *testing.T) { ctx := context.Background() @@ -108,6 +113,7 @@ func TestBlockBuilder_startBuild(t *testing.T) { require.NoError(t, redisMock.ExpectationsWereMet()) } +// TODO func TestBlockBuilder_getPayload(t *testing.T) { ctx := context.Background() From 50145e5f445b8851a55326b7fdf3833ae77914ea Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:15:12 -0700 Subject: [PATCH 04/19] Update singlenode.go --- cl/singlenode/singlenode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 911701072..39b1aaefd 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -271,7 +271,7 @@ func (app *SingleNodeApp) runLoop() { if err != nil { if errors.Is(err, blockbuilder.ErrEmptyBlock) { noPendingTxesTimeout := 10 * time.Millisecond - app.logger.Info("no pending transactions, will try again in: ", "timeout", noPendingTxesTimeout) + app.logger.Debug("no pending transactions, will try again in: %s", "timeout", noPendingTxesTimeout) time.Sleep(noPendingTxesTimeout) continue } else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { From d266437ee647d5587af27699c6d6ed83f635253b Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:17:14 -0700 Subject: [PATCH 05/19] Update singlenode.go --- cl/singlenode/singlenode.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 39b1aaefd..ffae835ae 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -270,9 +270,8 @@ func (app *SingleNodeApp) runLoop() { if err != nil { if errors.Is(err, blockbuilder.ErrEmptyBlock) { - noPendingTxesTimeout := 10 * time.Millisecond - app.logger.Debug("no pending transactions, will try again in: %s", "timeout", noPendingTxesTimeout) - time.Sleep(noPendingTxesTimeout) + app.logger.Debug("no pending transactions, will try again in evm build delay amount of time", "evm_build_delay", app.cfg.EVMBuildDelay) + time.Sleep(app.cfg.EVMBuildDelay) continue } else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { app.logger.Info("context canceled or deadline exceeded, stopping block production") From 460aec35a5cb6bac934122610af6f8cc7e8d82ba Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:27:36 -0700 Subject: [PATCH 06/19] better logging --- cl/blockbuilder/blockbuilder.go | 14 ++++++++++---- cl/singlenode/singlenode.go | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index 95e4befb6..7ae2c5d9a 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -126,13 +126,19 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to get pending transaction count: %w", err) } - timeSinceLastBlock := currentCallTime.Sub(bb.lastBlockTime) - if pendingTxCount == 0 && timeSinceLastBlock < bb.buildEmptyBlocksDelay { + if pendingTxCount == 0 { + timeSinceLastBlock := currentCallTime.Sub(bb.lastBlockTime) + if timeSinceLastBlock < bb.buildEmptyBlocksDelay { + bb.logger.Debug( + "Leader: Skipping empty block", + "timeSinceLastBlock", timeSinceLastBlock, + ) + return ErrEmptyBlock + } bb.logger.Info( - "Leader: Skipping empty block", + "Leader: Empty block will be created", "timeSinceLastBlock", timeSinceLastBlock, ) - return ErrEmptyBlock } // Load execution head to get previous block timestamp diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index ffae835ae..39b1aaefd 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -270,8 +270,9 @@ func (app *SingleNodeApp) runLoop() { if err != nil { if errors.Is(err, blockbuilder.ErrEmptyBlock) { - app.logger.Debug("no pending transactions, will try again in evm build delay amount of time", "evm_build_delay", app.cfg.EVMBuildDelay) - time.Sleep(app.cfg.EVMBuildDelay) + noPendingTxesTimeout := 10 * time.Millisecond + app.logger.Debug("no pending transactions, will try again in: %s", "timeout", noPendingTxesTimeout) + time.Sleep(noPendingTxesTimeout) continue } else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { app.logger.Info("context canceled or deadline exceeded, stopping block production") From 5162919521faa6f310db7a73da5e308e4635ba9a Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:44:14 -0700 Subject: [PATCH 07/19] Update blockbuilder_test.go --- cl/blockbuilder/blockbuilder_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cl/blockbuilder/blockbuilder_test.go b/cl/blockbuilder/blockbuilder_test.go index d52f60739..a406baf8a 100644 --- a/cl/blockbuilder/blockbuilder_test.go +++ b/cl/blockbuilder/blockbuilder_test.go @@ -113,7 +113,6 @@ func TestBlockBuilder_startBuild(t *testing.T) { require.NoError(t, redisMock.ExpectationsWereMet()) } -// TODO func TestBlockBuilder_getPayload(t *testing.T) { ctx := context.Background() @@ -161,6 +160,8 @@ func TestBlockBuilder_getPayload(t *testing.T) { FinalizedBlockHash: hash, } + mockEngineClient.On("PendingTransactionCount", mock.Anything).Return(uint(1), nil) + payloadID := &engine.PayloadID{0x01, 0x02, 0x03} forkChoiceResponse := engine.ForkChoiceResponse{ PayloadStatus: engine.PayloadStatusV1{ @@ -401,6 +402,8 @@ func TestBlockBuilder_getPayload_GetPayloadUnknownPayload(t *testing.T) { FinalizedBlockHash: hash, } + mockEngineClient.On("PendingTransactionCount", mock.Anything).Return(uint(1), nil) + payloadID := &engine.PayloadID{0x01, 0x02, 0x03} forkChoiceResponse := engine.ForkChoiceResponse{ PayloadStatus: engine.PayloadStatusV1{ From cd4b165857864db805fe53d3a2f3ceaef695ed36 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:38:54 -0700 Subject: [PATCH 08/19] use txpool_status --- cl/blockbuilder/blockbuilder.go | 11 ++++++++--- cl/ethclient/engineclient.go | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index 7ae2c5d9a..b3d0105b7 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" etypes "github.com/ethereum/go-ethereum/core/types" + "github.com/primev/mev-commit/cl/ethclient" "github.com/primev/mev-commit/cl/types" "github.com/primev/mev-commit/cl/util" "github.com/vmihailenco/msgpack/v5" @@ -35,7 +36,7 @@ type EngineClient interface { GetPayloadV4(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) HeaderByNumber(ctx context.Context, number *big.Int) (*etypes.Header, error) - PendingTransactionCount(ctx context.Context) (uint, error) + GetMempoolStatus(ctx context.Context) (*ethclient.MempoolStatus, error) } type stateManager interface { @@ -122,22 +123,26 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { ) currentCallTime := time.Now() - pendingTxCount, err := bb.engineCl.PendingTransactionCount(ctx) + mempoolStatus, err := bb.engineCl.GetMempoolStatus(ctx) if err != nil { return fmt.Errorf("failed to get pending transaction count: %w", err) } - if pendingTxCount == 0 { + if mempoolStatus.Pending == 0 { timeSinceLastBlock := currentCallTime.Sub(bb.lastBlockTime) if timeSinceLastBlock < bb.buildEmptyBlocksDelay { bb.logger.Debug( "Leader: Skipping empty block", "timeSinceLastBlock", timeSinceLastBlock, + "pendingTxes", mempoolStatus.Pending, + "queuedTxes", mempoolStatus.Queued, ) return ErrEmptyBlock } bb.logger.Info( "Leader: Empty block will be created", "timeSinceLastBlock", timeSinceLastBlock, + "pendingTxes", mempoolStatus.Pending, + "queuedTxes", mempoolStatus.Queued, ) } diff --git a/cl/ethclient/engineclient.go b/cl/ethclient/engineclient.go index 6880f221c..760a7dddf 100644 --- a/cl/ethclient/engineclient.go +++ b/cl/ethclient/engineclient.go @@ -26,6 +26,11 @@ const ( getPayloadV4 = "engine_getPayloadV4" ) +type MempoolStatus struct { + Pending uint64 `json:"pending"` + Queued uint64 `json:"queued"` +} + // EngineClient defines the Engine API authenticated JSON-RPC endpoints. // It extends the normal Client interface with the Engine API. type EngineClient interface { @@ -54,6 +59,8 @@ type EngineClient interface { GetPayloadV2(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) // GetPayloadV3 returns a cached payload by id. GetPayloadV4(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) + + GetMempoolStatus(ctx context.Context) (*MempoolStatus, error) } // engineClient implements EngineClient using JSON-RPC. @@ -189,3 +196,17 @@ func (c engineClient) GetPayloadV4(ctx context.Context, payloadID engine.Payload return &resp, nil } + +func (c engineClient) GetMempoolStatus(ctx context.Context) (*MempoolStatus, error) { + const endpoint = "mempool_status" + defer latency(c.chain, endpoint)() + + var result MempoolStatus + err := c.cl.Client().CallContext(ctx, &result, "txpool_status") + if err != nil { + incError(c.chain, endpoint) + return nil, fmt.Errorf("rpc txpool_status: %w", err) + } + + return &result, nil +} From 8325098fc0e2bba97d1b5166cfc87d6a22681823 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:45:19 -0700 Subject: [PATCH 09/19] Update engineclient.go --- cl/ethclient/engineclient.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/ethclient/engineclient.go b/cl/ethclient/engineclient.go index 760a7dddf..ee896448f 100644 --- a/cl/ethclient/engineclient.go +++ b/cl/ethclient/engineclient.go @@ -198,7 +198,7 @@ func (c engineClient) GetPayloadV4(ctx context.Context, payloadID engine.Payload } func (c engineClient) GetMempoolStatus(ctx context.Context) (*MempoolStatus, error) { - const endpoint = "mempool_status" + const endpoint = "txpool_status" defer latency(c.chain, endpoint)() var result MempoolStatus From 55244a6ff477787abea170bde37672d0fea0d2b2 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:54:09 -0700 Subject: [PATCH 10/19] non auth client --- cl/blockbuilder/blockbuilder.go | 22 +++++++++++++++++++--- cl/redisapp/rapp.go | 2 +- cl/singlenode/singlenode.go | 9 +++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index b3d0105b7..e6791752c 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -17,7 +17,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" etypes "github.com/ethereum/go-ethereum/core/types" - "github.com/primev/mev-commit/cl/ethclient" + gethclient "github.com/ethereum/go-ethereum/ethclient" "github.com/primev/mev-commit/cl/types" "github.com/primev/mev-commit/cl/util" "github.com/vmihailenco/msgpack/v5" @@ -36,7 +36,6 @@ type EngineClient interface { GetPayloadV4(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) HeaderByNumber(ctx context.Context, number *big.Int) (*etypes.Header, error) - GetMempoolStatus(ctx context.Context) (*ethclient.MempoolStatus, error) } type stateManager interface { @@ -48,6 +47,7 @@ type stateManager interface { type BlockBuilder struct { stateManager stateManager engineCl EngineClient + ethClient *gethclient.Client logger *slog.Logger buildDelay time.Duration buildEmptyBlocksDelay time.Duration @@ -64,6 +64,7 @@ func NewBlockBuilder( buildDelay, buildDelayEmptyBlocks time.Duration, feeReceipt string, + ethClient *gethclient.Client, ) *BlockBuilder { return &BlockBuilder{ stateManager: stateManager, @@ -74,6 +75,7 @@ func NewBlockBuilder( buildEmptyBlocksDelay: buildDelayEmptyBlocks, feeRecipient: common.HexToAddress(feeReceipt), lastBlockTime: time.Now().Add(-buildDelayEmptyBlocks), + ethClient: ethClient, } } @@ -115,6 +117,20 @@ func (bb *BlockBuilder) startBuild(ctx context.Context, head *types.ExecutionHea return resp, nil } +type MempoolStatus struct { + Pending hexutil.Uint64 `json:"pending"` + Queued hexutil.Uint64 `json:"queued"` +} + +func getMempoolStatus(gethclient *gethclient.Client, ctx context.Context) (*MempoolStatus, error) { + var result MempoolStatus + err := gethclient.Client().CallContext(ctx, &result, "txpool_status") + if err != nil { + return nil, err + } + return &result, nil +} + func (bb *BlockBuilder) GetPayload(ctx context.Context) error { var ( payloadID *engine.PayloadID @@ -123,7 +139,7 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { ) currentCallTime := time.Now() - mempoolStatus, err := bb.engineCl.GetMempoolStatus(ctx) + mempoolStatus, err := getMempoolStatus(bb.ethClient, ctx) if err != nil { return fmt.Errorf("failed to get pending transaction count: %w", err) } diff --git a/cl/redisapp/rapp.go b/cl/redisapp/rapp.go index dabf3fc25..b2057d690 100644 --- a/cl/redisapp/rapp.go +++ b/cl/redisapp/rapp.go @@ -76,7 +76,7 @@ func NewMevCommitChain( ) return nil, err } - blockBuilder := blockbuilder.NewBlockBuilder(coordinator, engineCL, logger, buildDelay, buildDelayEmptyBlocks, feeReceipt) + blockBuilder := blockbuilder.NewBlockBuilder(coordinator, engineCL, logger, buildDelay, buildDelayEmptyBlocks, feeReceipt, nil) lfm, err := leaderfollower.NewLeaderFollowerManager( instanceID, diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 39b1aaefd..597e0ad7d 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -5,12 +5,14 @@ import ( "encoding/hex" "errors" "fmt" + "log" "log/slog" "net/http" "strings" "sync" "time" + gethclient "github.com/ethereum/go-ethereum/ethclient" "github.com/primev/mev-commit/cl/blockbuilder" "github.com/primev/mev-commit/cl/ethclient" "github.com/primev/mev-commit/cl/singlenode/api" @@ -88,6 +90,12 @@ func NewSingleNodeApp( return nil, err } + gethClient, err := gethclient.Dial("http://localhost:8545") + if err != nil { + log.Fatalf("Failed to connect to Ethereum client: %v", err) + } + defer gethClient.Close() + stateMgr := localstate.NewLocalStateManager(logger.With("component", "LocalStateManager")) bb := blockbuilder.NewBlockBuilder( stateMgr, @@ -96,6 +104,7 @@ func NewSingleNodeApp( cfg.EVMBuildDelay, cfg.EVMBuildDelayEmptyBlocks, cfg.PriorityFeeReceipt, + gethClient, ) var pRepo types.PayloadRepository From e8dbd98eadd67557db81a8acdfce96a5bff58ca6 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:58:59 -0700 Subject: [PATCH 11/19] Update singlenode.go --- cl/singlenode/singlenode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 597e0ad7d..a7134da0b 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -90,7 +90,7 @@ func NewSingleNodeApp( return nil, err } - gethClient, err := gethclient.Dial("http://localhost:8545") + gethClient, err := gethclient.Dial("http://erigon:8545") if err != nil { log.Fatalf("Failed to connect to Ethereum client: %v", err) } From 31924ab0ed48f4211b25ee030c36f711d9ba058d Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 13:11:40 -0700 Subject: [PATCH 12/19] simplify --- cl/blockbuilder/blockbuilder.go | 30 +++++++++++++++--------------- cl/cmd/singlenode/main.go | 9 +++++++++ cl/ethclient/engineclient.go | 21 --------------------- cl/singlenode/singlenode.go | 3 ++- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index e6791752c..12c6ad7a8 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -117,20 +117,6 @@ func (bb *BlockBuilder) startBuild(ctx context.Context, head *types.ExecutionHea return resp, nil } -type MempoolStatus struct { - Pending hexutil.Uint64 `json:"pending"` - Queued hexutil.Uint64 `json:"queued"` -} - -func getMempoolStatus(gethclient *gethclient.Client, ctx context.Context) (*MempoolStatus, error) { - var result MempoolStatus - err := gethclient.Client().CallContext(ctx, &result, "txpool_status") - if err != nil { - return nil, err - } - return &result, nil -} - func (bb *BlockBuilder) GetPayload(ctx context.Context) error { var ( payloadID *engine.PayloadID @@ -139,7 +125,7 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { ) currentCallTime := time.Now() - mempoolStatus, err := getMempoolStatus(bb.ethClient, ctx) + mempoolStatus, err := bb.GetMempoolStatus(ctx) if err != nil { return fmt.Errorf("failed to get pending transaction count: %w", err) } @@ -565,3 +551,17 @@ func (bb *BlockBuilder) loadExecutionHead(ctx context.Context) (*types.Execution func (bb *BlockBuilder) GetExecutionHead() *types.ExecutionHead { return bb.executionHead } + +type MempoolStatus struct { + Pending hexutil.Uint64 `json:"pending"` + Queued hexutil.Uint64 `json:"queued"` +} + +func (bb *BlockBuilder) GetMempoolStatus(ctx context.Context) (*MempoolStatus, error) { + var result MempoolStatus + err := bb.ethClient.Client().CallContext(ctx, &result, "txpool_status") + if err != nil { + return nil, err + } + return &result, nil +} diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index 3664519e0..3b9e2d2d7 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -57,6 +57,13 @@ var ( }, }) + nonAuthEthClientURLFlag = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "non-auth-eth-client-url", + Usage: "Ethereum Execution client Engine API URL (e.g., http://localhost:8545)", + EnvVars: []string{"LEADER_NON_AUTH_ETH_CLIENT_URL"}, + Value: "http://localhost:8545", + }) + ethClientURLFlag = altsrc.NewStringFlag(&cli.StringFlag{ Name: "eth-client-url", Usage: "Ethereum Execution client Engine API URL (e.g., http://localhost:8551)", @@ -238,6 +245,7 @@ func main() { healthAddrPortFlag, postgresDSNFlag, apiAddrFlag, + nonAuthEthClientURLFlag, } memberFlags := []cli.Flag{ @@ -337,6 +345,7 @@ func startLeaderNode(c *cli.Context) error { HealthAddr: c.String(healthAddrPortFlag.Name), PostgresDSN: c.String(postgresDSNFlag.Name), APIAddr: c.String(apiAddrFlag.Name), + NonAuthEthClientURL: c.String(nonAuthEthClientURLFlag.Name), } logger.Info("Starting leader node with configuration", "config", cfg) diff --git a/cl/ethclient/engineclient.go b/cl/ethclient/engineclient.go index ee896448f..6880f221c 100644 --- a/cl/ethclient/engineclient.go +++ b/cl/ethclient/engineclient.go @@ -26,11 +26,6 @@ const ( getPayloadV4 = "engine_getPayloadV4" ) -type MempoolStatus struct { - Pending uint64 `json:"pending"` - Queued uint64 `json:"queued"` -} - // EngineClient defines the Engine API authenticated JSON-RPC endpoints. // It extends the normal Client interface with the Engine API. type EngineClient interface { @@ -59,8 +54,6 @@ type EngineClient interface { GetPayloadV2(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) // GetPayloadV3 returns a cached payload by id. GetPayloadV4(ctx context.Context, payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) - - GetMempoolStatus(ctx context.Context) (*MempoolStatus, error) } // engineClient implements EngineClient using JSON-RPC. @@ -196,17 +189,3 @@ func (c engineClient) GetPayloadV4(ctx context.Context, payloadID engine.Payload return &resp, nil } - -func (c engineClient) GetMempoolStatus(ctx context.Context) (*MempoolStatus, error) { - const endpoint = "txpool_status" - defer latency(c.chain, endpoint)() - - var result MempoolStatus - err := c.cl.Client().CallContext(ctx, &result, "txpool_status") - if err != nil { - incError(c.chain, endpoint) - return nil, fmt.Errorf("rpc txpool_status: %w", err) - } - - return &result, nil -} diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index a7134da0b..616cdc128 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -37,6 +37,7 @@ type Config struct { HealthAddr string PostgresDSN string APIAddr string + NonAuthEthClientURL string } type BlockBuilder interface { @@ -90,7 +91,7 @@ func NewSingleNodeApp( return nil, err } - gethClient, err := gethclient.Dial("http://erigon:8545") + gethClient, err := gethclient.Dial(cfg.NonAuthEthClientURL) if err != nil { log.Fatalf("Failed to connect to Ethereum client: %v", err) } From 94077fea30e061966b7da079f4905becd2625837 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 13:35:59 -0700 Subject: [PATCH 13/19] TxPoolPollingInterval param --- cl/blockbuilder/blockbuilder.go | 2 ++ cl/cmd/singlenode/main.go | 10 ++++++++++ cl/singlenode/singlenode.go | 6 +++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index 12c6ad7a8..628fccad6 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -129,6 +129,8 @@ func (bb *BlockBuilder) GetPayload(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to get pending transaction count: %w", err) } + bb.logger.Debug("GetMempoolStatus rpc duration", "duration", time.Since(currentCallTime)) + if mempoolStatus.Pending == 0 { timeSinceLastBlock := currentCallTime.Sub(bb.lastBlockTime) if timeSinceLastBlock < bb.buildEmptyBlocksDelay { diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index 3b9e2d2d7..8912512a6 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -204,6 +204,14 @@ var ( }, }) + txPoolPollingIntervalFlag = altsrc.NewDurationFlag(&cli.DurationFlag{ + Name: "tx-pool-polling-interval", + Usage: "Wait interval for polling the tx pool while there are no pending transactions (e.g., '5ms')", + EnvVars: []string{"LEADER_TX_POOL_POLLING_INTERVAL"}, + Value: 5 * time.Millisecond, + Category: categoryDebug, + }) + // Member node specific flags leaderAPIURLFlag = altsrc.NewStringFlag(&cli.StringFlag{ Name: "leader-api-url", @@ -246,6 +254,7 @@ func main() { postgresDSNFlag, apiAddrFlag, nonAuthEthClientURLFlag, + txPoolPollingIntervalFlag, } memberFlags := []cli.Flag{ @@ -346,6 +355,7 @@ func startLeaderNode(c *cli.Context) error { PostgresDSN: c.String(postgresDSNFlag.Name), APIAddr: c.String(apiAddrFlag.Name), NonAuthEthClientURL: c.String(nonAuthEthClientURLFlag.Name), + TxPoolPollingInterval: c.Duration(txPoolPollingIntervalFlag.Name), } logger.Info("Starting leader node with configuration", "config", cfg) diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 616cdc128..97d8d9cc2 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -38,6 +38,7 @@ type Config struct { PostgresDSN string APIAddr string NonAuthEthClientURL string + TxPoolPollingInterval time.Duration } type BlockBuilder interface { @@ -280,9 +281,8 @@ func (app *SingleNodeApp) runLoop() { if err != nil { if errors.Is(err, blockbuilder.ErrEmptyBlock) { - noPendingTxesTimeout := 10 * time.Millisecond - app.logger.Debug("no pending transactions, will try again in: %s", "timeout", noPendingTxesTimeout) - time.Sleep(noPendingTxesTimeout) + app.logger.Debug("no pending transactions, will try again in: %s", "timeout", app.cfg.TxPoolPollingInterval) + time.Sleep(app.cfg.TxPoolPollingInterval) continue } else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { app.logger.Info("context canceled or deadline exceeded, stopping block production") From 525b51167fc920cfb98cd5abeafc839ee7a2e4bc Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:15:37 -0700 Subject: [PATCH 14/19] misc --- cl/blockbuilder/blockbuilder_test.go | 9 --------- cl/singlenode/singlenode.go | 10 ++++++++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/cl/blockbuilder/blockbuilder_test.go b/cl/blockbuilder/blockbuilder_test.go index a406baf8a..93f69e1bb 100644 --- a/cl/blockbuilder/blockbuilder_test.go +++ b/cl/blockbuilder/blockbuilder_test.go @@ -59,11 +59,6 @@ func (m *MockEngineClient) HeaderByNumber(ctx context.Context, number *big.Int) return args.Get(0).(*etypes.Header), args.Error(1) } -func (m *MockEngineClient) PendingTransactionCount(ctx context.Context) (uint, error) { - args := m.Called(ctx) - return args.Get(0).(uint), args.Error(1) -} - func TestBlockBuilder_startBuild(t *testing.T) { ctx := context.Background() @@ -160,8 +155,6 @@ func TestBlockBuilder_getPayload(t *testing.T) { FinalizedBlockHash: hash, } - mockEngineClient.On("PendingTransactionCount", mock.Anything).Return(uint(1), nil) - payloadID := &engine.PayloadID{0x01, 0x02, 0x03} forkChoiceResponse := engine.ForkChoiceResponse{ PayloadStatus: engine.PayloadStatusV1{ @@ -402,8 +395,6 @@ func TestBlockBuilder_getPayload_GetPayloadUnknownPayload(t *testing.T) { FinalizedBlockHash: hash, } - mockEngineClient.On("PendingTransactionCount", mock.Anything).Return(uint(1), nil) - payloadID := &engine.PayloadID{0x01, 0x02, 0x03} forkChoiceResponse := engine.ForkChoiceResponse{ PayloadStatus: engine.PayloadStatusV1{ diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 97d8d9cc2..4fcf64956 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -62,6 +62,7 @@ type SingleNodeApp struct { wg sync.WaitGroup connectionStatus sync.Mutex connectionRefused bool + ethClient *gethclient.Client } // NewSingleNodeApp creates and initializes a new SingleNodeApp. @@ -92,11 +93,10 @@ func NewSingleNodeApp( return nil, err } - gethClient, err := gethclient.Dial(cfg.NonAuthEthClientURL) + gethClient, err := gethclient.DialContext(ctx, cfg.NonAuthEthClientURL) if err != nil { log.Fatalf("Failed to connect to Ethereum client: %v", err) } - defer gethClient.Close() stateMgr := localstate.NewLocalStateManager(logger.With("component", "LocalStateManager")) bb := blockbuilder.NewBlockBuilder( @@ -149,6 +149,7 @@ func NewSingleNodeApp( appCtx: ctx, cancel: cancel, connectionRefused: false, + ethClient: gethClient, }, nil } @@ -383,5 +384,10 @@ func (app *SingleNodeApp) Stop() { } } + if app.ethClient != nil { + app.ethClient.Close() + app.logger.Info("Ethereum client closed.") + } + app.logger.Info("SingleNodeApp stopped.") } From abb96f921d23ef748e4a05dcca4acc5367baabb8 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:12:47 -0700 Subject: [PATCH 15/19] fix tests --- cl/blockbuilder/blockbuilder.go | 13 ++++++++----- cl/blockbuilder/blockbuilder_test.go | 26 ++++++++++++++++++++++++++ cl/singlenode/singlenode.go | 14 +++++++------- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/cl/blockbuilder/blockbuilder.go b/cl/blockbuilder/blockbuilder.go index 628fccad6..2d6fcd239 100644 --- a/cl/blockbuilder/blockbuilder.go +++ b/cl/blockbuilder/blockbuilder.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" etypes "github.com/ethereum/go-ethereum/core/types" - gethclient "github.com/ethereum/go-ethereum/ethclient" "github.com/primev/mev-commit/cl/types" "github.com/primev/mev-commit/cl/util" "github.com/vmihailenco/msgpack/v5" @@ -44,10 +43,14 @@ type stateManager interface { ResetBlockState(ctx context.Context) error } +type rpcClient interface { + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error +} + type BlockBuilder struct { stateManager stateManager engineCl EngineClient - ethClient *gethclient.Client + rpcClient rpcClient logger *slog.Logger buildDelay time.Duration buildEmptyBlocksDelay time.Duration @@ -64,7 +67,7 @@ func NewBlockBuilder( buildDelay, buildDelayEmptyBlocks time.Duration, feeReceipt string, - ethClient *gethclient.Client, + rpcClient rpcClient, ) *BlockBuilder { return &BlockBuilder{ stateManager: stateManager, @@ -75,7 +78,7 @@ func NewBlockBuilder( buildEmptyBlocksDelay: buildDelayEmptyBlocks, feeRecipient: common.HexToAddress(feeReceipt), lastBlockTime: time.Now().Add(-buildDelayEmptyBlocks), - ethClient: ethClient, + rpcClient: rpcClient, } } @@ -561,7 +564,7 @@ type MempoolStatus struct { func (bb *BlockBuilder) GetMempoolStatus(ctx context.Context) (*MempoolStatus, error) { var result MempoolStatus - err := bb.ethClient.Client().CallContext(ctx, &result, "txpool_status") + err := bb.rpcClient.CallContext(ctx, &result, "txpool_status") if err != nil { return nil, err } diff --git a/cl/blockbuilder/blockbuilder_test.go b/cl/blockbuilder/blockbuilder_test.go index 93f69e1bb..45c34950b 100644 --- a/cl/blockbuilder/blockbuilder_test.go +++ b/cl/blockbuilder/blockbuilder_test.go @@ -59,6 +59,23 @@ func (m *MockEngineClient) HeaderByNumber(ctx context.Context, number *big.Int) return args.Get(0).(*etypes.Header), args.Error(1) } +func createMockRPCClient() rpcClient { + mockRPC := &MockRPCClient{} + return mockRPC +} + +type MockRPCClient struct{} + +func (m *MockRPCClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + if method == "txpool_status" && result != nil { + if mempoolStatus, ok := result.(*MempoolStatus); ok { + mempoolStatus.Pending = 1 + mempoolStatus.Queued = 0 + } + } + return nil +} + func TestBlockBuilder_startBuild(t *testing.T) { ctx := context.Background() @@ -78,6 +95,7 @@ func TestBlockBuilder_startBuild(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, @@ -142,6 +160,7 @@ func TestBlockBuilder_getPayload(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, @@ -208,6 +227,7 @@ func TestBlockBuilder_FinalizeBlock(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, @@ -290,6 +310,7 @@ func TestBlockBuilder_startBuild_ForkchoiceUpdatedError(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, @@ -334,6 +355,7 @@ func TestBlockBuilder_startBuild_InvalidPayloadStatus(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, @@ -383,6 +405,7 @@ func TestBlockBuilder_getPayload_GetPayloadUnknownPayload(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: time.Duration(1 * time.Second), logger: stLog, } @@ -434,6 +457,7 @@ func TestBlockBuilder_FinalizeBlock_InvalidBlockHeight(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, @@ -489,6 +513,7 @@ func TestBlockBuilder_FinalizeBlock_NewPayloadInvalidStatus(t *testing.T) { blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, @@ -549,6 +574,7 @@ func TestBlockBuilder_FinalizeBlock_ForkchoiceUpdatedInvalidStatus(t *testing.T) blockBuilder := &BlockBuilder{ stateManager: stateManager, engineCl: mockEngineClient, + rpcClient: createMockRPCClient(), buildDelay: buildDelay, buildDelayMs: uint64(buildDelay.Milliseconds()), logger: stLog, diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 4fcf64956..af803a037 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -12,7 +12,7 @@ import ( "sync" "time" - gethclient "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" "github.com/primev/mev-commit/cl/blockbuilder" "github.com/primev/mev-commit/cl/ethclient" "github.com/primev/mev-commit/cl/singlenode/api" @@ -62,7 +62,7 @@ type SingleNodeApp struct { wg sync.WaitGroup connectionStatus sync.Mutex connectionRefused bool - ethClient *gethclient.Client + rpcClient *rpc.Client } // NewSingleNodeApp creates and initializes a new SingleNodeApp. @@ -93,7 +93,7 @@ func NewSingleNodeApp( return nil, err } - gethClient, err := gethclient.DialContext(ctx, cfg.NonAuthEthClientURL) + rpcClient, err := rpc.DialContext(ctx, cfg.NonAuthEthClientURL) if err != nil { log.Fatalf("Failed to connect to Ethereum client: %v", err) } @@ -106,7 +106,7 @@ func NewSingleNodeApp( cfg.EVMBuildDelay, cfg.EVMBuildDelayEmptyBlocks, cfg.PriorityFeeReceipt, - gethClient, + rpcClient, ) var pRepo types.PayloadRepository @@ -149,7 +149,7 @@ func NewSingleNodeApp( appCtx: ctx, cancel: cancel, connectionRefused: false, - ethClient: gethClient, + rpcClient: rpcClient, }, nil } @@ -384,8 +384,8 @@ func (app *SingleNodeApp) Stop() { } } - if app.ethClient != nil { - app.ethClient.Close() + if app.rpcClient != nil { + app.rpcClient.Close() app.logger.Info("Ethereum client closed.") } From 89b07d7ed783b48a4b41c0898cd15cfd29c39ce2 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:17:50 -0700 Subject: [PATCH 16/19] naming --- cl/cmd/singlenode/main.go | 21 ++++++++++----------- cl/singlenode/singlenode.go | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cl/cmd/singlenode/main.go b/cl/cmd/singlenode/main.go index 8912512a6..3d4e1b306 100644 --- a/cl/cmd/singlenode/main.go +++ b/cl/cmd/singlenode/main.go @@ -57,10 +57,10 @@ var ( }, }) - nonAuthEthClientURLFlag = altsrc.NewStringFlag(&cli.StringFlag{ - Name: "non-auth-eth-client-url", - Usage: "Ethereum Execution client Engine API URL (e.g., http://localhost:8545)", - EnvVars: []string{"LEADER_NON_AUTH_ETH_CLIENT_URL"}, + nonAuthRpcUrlFlag = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "non-auth-rpc-url", + Usage: "Non-authenticated Ethereum RPC URL (e.g., http://localhost:8545)", + EnvVars: []string{"LEADER_NON_AUTH_RPC_URL"}, Value: "http://localhost:8545", }) @@ -205,11 +205,10 @@ var ( }) txPoolPollingIntervalFlag = altsrc.NewDurationFlag(&cli.DurationFlag{ - Name: "tx-pool-polling-interval", - Usage: "Wait interval for polling the tx pool while there are no pending transactions (e.g., '5ms')", - EnvVars: []string{"LEADER_TX_POOL_POLLING_INTERVAL"}, - Value: 5 * time.Millisecond, - Category: categoryDebug, + Name: "tx-pool-polling-interval", + Usage: "Wait interval for polling the tx pool while there are no pending transactions (e.g., '5ms')", + EnvVars: []string{"LEADER_TX_POOL_POLLING_INTERVAL"}, + Value: 5 * time.Millisecond, }) // Member node specific flags @@ -253,7 +252,7 @@ func main() { healthAddrPortFlag, postgresDSNFlag, apiAddrFlag, - nonAuthEthClientURLFlag, + nonAuthRpcUrlFlag, txPoolPollingIntervalFlag, } @@ -354,7 +353,7 @@ func startLeaderNode(c *cli.Context) error { HealthAddr: c.String(healthAddrPortFlag.Name), PostgresDSN: c.String(postgresDSNFlag.Name), APIAddr: c.String(apiAddrFlag.Name), - NonAuthEthClientURL: c.String(nonAuthEthClientURLFlag.Name), + NonAuthRpcURL: c.String(nonAuthRpcUrlFlag.Name), TxPoolPollingInterval: c.Duration(txPoolPollingIntervalFlag.Name), } diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index af803a037..157246ca5 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -37,7 +37,7 @@ type Config struct { HealthAddr string PostgresDSN string APIAddr string - NonAuthEthClientURL string + NonAuthRpcURL string TxPoolPollingInterval time.Duration } @@ -93,7 +93,7 @@ func NewSingleNodeApp( return nil, err } - rpcClient, err := rpc.DialContext(ctx, cfg.NonAuthEthClientURL) + rpcClient, err := rpc.DialContext(ctx, cfg.NonAuthRpcURL) if err != nil { log.Fatalf("Failed to connect to Ethereum client: %v", err) } From bd7cd2ac9dd90d7e214d9c8968c31561a76357f3 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:32:56 -0700 Subject: [PATCH 17/19] Update singlenode_test.go --- cl/singlenode/singlenode_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cl/singlenode/singlenode_test.go b/cl/singlenode/singlenode_test.go index e11a7bec6..dbd4832c6 100644 --- a/cl/singlenode/singlenode_test.go +++ b/cl/singlenode/singlenode_test.go @@ -65,14 +65,23 @@ func TestNewSingleNodeApp(t *testing.T) { ctx := context.Background() logger := setupTestLogger() + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"pending":"0x1","queued":"0x0"}}`)) + })) + defer mockServer.Close() + validCfg := Config{ InstanceID: "test-instance", EthClientURL: "http://localhost:8545", + NonAuthRpcURL: mockServer.URL, JWTSecret: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", EVMBuildDelay: time.Second, EVMBuildDelayEmptyBlocks: time.Second * 2, PriorityFeeReceipt: "0x1234567890abcdef1234567890abcdef12345678", HealthAddr: ":8080", + TxPoolPollingInterval: time.Second, } app, err := NewSingleNodeApp(ctx, validCfg, logger) From 76f8a16c9b27ea931a540b095fc31c96fa8cc49b Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:37:43 -0700 Subject: [PATCH 18/19] Update singlenode_test.go --- cl/singlenode/singlenode_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cl/singlenode/singlenode_test.go b/cl/singlenode/singlenode_test.go index dbd4832c6..73e6ddd86 100644 --- a/cl/singlenode/singlenode_test.go +++ b/cl/singlenode/singlenode_test.go @@ -68,7 +68,8 @@ func TestNewSingleNodeApp(t *testing.T) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"pending":"0x1","queued":"0x0"}}`)) + _, err := w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"pending":"0x1","queued":"0x0"}}`)) + require.NoError(t, err) })) defer mockServer.Close() From eb523e3d9964292b202882b9529101ea279fba53 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:57:57 -0700 Subject: [PATCH 19/19] nits --- cl/singlenode/singlenode.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cl/singlenode/singlenode.go b/cl/singlenode/singlenode.go index 157246ca5..72252e774 100644 --- a/cl/singlenode/singlenode.go +++ b/cl/singlenode/singlenode.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "errors" "fmt" - "log" "log/slog" "net/http" "strings" @@ -95,7 +94,12 @@ func NewSingleNodeApp( rpcClient, err := rpc.DialContext(ctx, cfg.NonAuthRpcURL) if err != nil { - log.Fatalf("Failed to connect to Ethereum client: %v", err) + cancel() + logger.Error( + "failed to create non-authenticated Ethereum RPC client", + "error", err, + ) + return nil, err } stateMgr := localstate.NewLocalStateManager(logger.With("component", "LocalStateManager")) @@ -282,7 +286,7 @@ func (app *SingleNodeApp) runLoop() { if err != nil { if errors.Is(err, blockbuilder.ErrEmptyBlock) { - app.logger.Debug("no pending transactions, will try again in: %s", "timeout", app.cfg.TxPoolPollingInterval) + app.logger.Debug("no pending transactions, will try again after timeout", "timeout", app.cfg.TxPoolPollingInterval) time.Sleep(app.cfg.TxPoolPollingInterval) continue } else if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {