From 4573dc356a45bbe474f4f88200d6ab126b1ba746 Mon Sep 17 00:00:00 2001 From: Michael Fletcher Date: Sun, 19 Apr 2026 19:41:13 +0100 Subject: [PATCH] fix: resolve two root causes of vrf v1 nightly race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scheduled go_core_race_tests job on develop reports three data races in TestDelegate_InvalidLog. They trace to two independent root causes: 1. core/services/vrf/delegate_test.go double-spawns the v1 listener's head and log goroutines. Listener.Start already spawns both (listener_v1.go:154-155). The extra goroutines send to the unbuffered WaitOnStop channel, which Close drains exactly twice (listener_v1.go: 519-520), so two goroutines leak per test. A leaked head listener can still invoke pipeline.Run → Debugw after t.done=true is written at testing.go:2023, racing with the stdlib's deliberately-unsynchronized test-completion flag. 2. core/internal/cltest.NewEthMocksWithStartupAssertions omits a BalanceAt expectation. The chain's default-enabled BalanceMonitor worker calls BalanceAt on the mock while its ctx is being cancelled by StopRChan.CtxCancel. Without an expectation, testify's m.fail → callString path uses %#v (mock.go:463), which reflects through *cancelCtx's unexported atomic.Value fields and races with the concurrent atomic stores in (*cancelCtx).cancel. Registering a Maybe() expectation routes calls through the happy path, which only uses %v via Stringer and never reflects. --- core/internal/cltest/cltest.go | 1 + core/services/vrf/delegate_test.go | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 5b6cac6ab91..11499074f99 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -631,6 +631,7 @@ func NewEthMocksWithStartupAssertions(t testing.TB) *clienttest.Client { c.On("CodeAt", mock.Anything, mock.Anything, mock.Anything).Maybe().Return([]byte{}, nil) c.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(uint64(0), nil) c.On("PendingNonceAt", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(uint64(0), nil) + c.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Maybe().Return(big.NewInt(0), nil) c.On("Close").Maybe().Return() c.On("BatchCallContext", mock.Anything, mock.Anything).Maybe().Return(nil) diff --git a/core/services/vrf/delegate_test.go b/core/services/vrf/delegate_test.go index 3a703ccfa4c..f73509637d6 100644 --- a/core/services/vrf/delegate_test.go +++ b/core/services/vrf/delegate_test.go @@ -198,13 +198,6 @@ func setup(t *testing.T) (vrfUniverse, *v1.Listener, job.Job) { require.NoError(t, err) require.Len(t, vl, 1) listener := vl[0].(*v1.Listener) - // Start the listenerV1 - go func() { - listener.RunLogListener([]func(){}, 6) - }() - go func() { - listener.RunHeadListener(func() {}) - }() servicetest.Run(t, listener) return vuni, listener, jb }