Skip to content

Commit c98d9e6

Browse files
authored
[DX-3716] Fixes Flaky VRFv2 Tests + CI Core Timeouts (#22324)
* Fixes flaky VRFv2 Tests * Remove 90 second timeout * go fix vrf/v2/... * Linting * More linting * lint again
1 parent 38ae628 commit c98d9e6

16 files changed

Lines changed: 739 additions & 451 deletions

core/internal/testutils/testutils.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,39 @@ func RandomizeName(n string) string {
8686
// DefaultWaitTimeout is the default wait timeout. If you have a *testing.T, use WaitTimeout instead.
8787
const DefaultWaitTimeout = 30 * time.Second
8888

89-
// WaitTimeout returns a timeout based on the test's Deadline, if available.
89+
// deadlineRemainingBudget returns ~90% of time until the test deadline, or false if none.
90+
func deadlineRemainingBudget(t *testing.T) (time.Duration, bool) {
91+
if d, ok := t.Deadline(); ok {
92+
return time.Until(d) * 9 / 10, true // 10% buffer for cleanup
93+
}
94+
return 0, false
95+
}
96+
97+
// WaitTimeout returns a timeout capped by the test's Deadline, if available.
9098
// Especially important to use in parallel tests, as their individual execution
9199
// can get paused for arbitrary amounts of time.
100+
//
101+
// When a deadline exists, it uses the full remaining budget (90% of time until the
102+
// deadline), not [DefaultWaitTimeout], so long-running tests still get enough wall
103+
// clock under package timeouts.
92104
func WaitTimeout(t *testing.T) time.Duration {
93-
if d, ok := t.Deadline(); ok {
94-
// 10% buffer for cleanup and scheduling delay
95-
return time.Until(d) * 9 / 10
105+
if budget, ok := deadlineRemainingBudget(t); ok {
106+
return budget
96107
}
97108
return DefaultWaitTimeout
98109
}
99110

111+
// WaitTimeoutCustom uses the requested duration when there is no test deadline.
112+
// When the test has a deadline, it returns the lesser of the requested duration and
113+
// the remaining budget (90% of time until deadline), so callers can ask for e.g. 5m
114+
// without exceeding the test process deadline.
115+
func WaitTimeoutCustom(t *testing.T, requested time.Duration) time.Duration {
116+
if budget, ok := deadlineRemainingBudget(t); ok {
117+
return min(budget, requested)
118+
}
119+
return requested
120+
}
121+
100122
// Context returns a context with the test's deadline, if available.
101123
// Deprecated: use [testing.TB.Context] directly
102124
func Context(tb testing.TB) context.Context {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package testutils
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestWaitTimeoutUsesDeadlineBudgetNotDefaultCap(t *testing.T) {
11+
if d, ok := t.Deadline(); ok {
12+
expectBefore := time.Until(d) * 9 / 10
13+
got := WaitTimeout(t)
14+
expectAfter := time.Until(d) * 9 / 10
15+
require.Greater(t, got, time.Duration(0))
16+
// got uses time.Until(deadline) inside WaitTimeout between these snapshots
17+
require.GreaterOrEqual(t, got, expectAfter)
18+
require.LessOrEqual(t, got, expectBefore)
19+
} else {
20+
require.Equal(t, DefaultWaitTimeout, WaitTimeout(t))
21+
}
22+
}
23+
24+
func TestWaitTimeoutCustom(t *testing.T) {
25+
requested := 10 * time.Second
26+
27+
if d, ok := t.Deadline(); ok {
28+
expectBefore := time.Until(d) * 9 / 10
29+
got := WaitTimeoutCustom(t, requested)
30+
expectAfter := time.Until(d) * 9 / 10
31+
require.Greater(t, got, time.Duration(0))
32+
require.LessOrEqual(t, got, requested)
33+
require.GreaterOrEqual(t, got, min(expectAfter, requested))
34+
require.LessOrEqual(t, got, min(expectBefore, requested))
35+
} else {
36+
require.Equal(t, requested, WaitTimeoutCustom(t, requested))
37+
}
38+
}

core/services/vrf/v2/bhs_feeder_test.go

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package v2_test
22

33
import (
4+
"math/big"
45
"testing"
56
"time"
67

@@ -11,7 +12,6 @@ import (
1112

1213
"github.com/smartcontractkit/chainlink-evm/pkg/assets"
1314
"github.com/smartcontractkit/chainlink-evm/pkg/config/toml"
14-
"github.com/smartcontractkit/chainlink-evm/pkg/types"
1515
"github.com/smartcontractkit/chainlink/v2/core/internal/cltest"
1616
"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
1717
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"
@@ -27,38 +27,38 @@ func TestStartHeartbeats(t *testing.T) {
2727
vrfKey := cltest.MustGenerateRandomKey(t)
2828
sendEth(t, ownerKey, uni.backend, vrfKey.Address, 10)
2929
gasLanePriceWei := assets.GWei(1)
30-
gasLimit := 3_000_000
30+
gasLimit := uint64(3_000_000)
3131

3232
consumers := uni.vrfConsumers
3333

3434
// generate n BHS keys to make sure BHS job rotates sending keys
35-
var bhsKeyAddresses []string
36-
var keySpecificOverrides []toml.KeySpecific
37-
var keys []any
35+
bhsKeyAddresses := make([]string, 0, len(consumers))
36+
keySpecificOverrides := make([]toml.KeySpecific, 0, len(consumers)+1)
37+
keys := make([]any, 0, len(consumers)+2)
3838
for range consumers {
3939
bhsKey := cltest.MustGenerateRandomKey(t)
4040
bhsKeyAddresses = append(bhsKeyAddresses, bhsKey.Address.String())
4141
keys = append(keys, bhsKey)
4242
keySpecificOverrides = append(keySpecificOverrides, toml.KeySpecific{
43-
Key: ptr[types.EIP55Address](bhsKey.EIP55Address),
43+
Key: new(bhsKey.EIP55Address),
4444
GasEstimator: toml.KeySpecificGasEstimator{PriceMax: gasLanePriceWei},
4545
})
4646
sendEth(t, ownerKey, uni.backend, bhsKey.Address, 10)
4747
}
4848
keySpecificOverrides = append(keySpecificOverrides, toml.KeySpecific{
4949
// Gas lane.
50-
Key: ptr[types.EIP55Address](vrfKey.EIP55Address),
50+
Key: new(vrfKey.EIP55Address),
5151
GasEstimator: toml.KeySpecificGasEstimator{PriceMax: gasLanePriceWei},
5252
})
5353

5454
keys = append(keys, ownerKey, vrfKey)
5555

5656
config, _ := heavyweight.FullTestDBV2(t, func(c *chainlink.Config, s *chainlink.Secrets) {
5757
simulatedOverrides(t, gasLanePriceWei, keySpecificOverrides...)(c, s)
58-
c.EVM[0].MinIncomingConfirmations = ptr[uint32](2)
59-
c.Feature.LogPoller = ptr(true)
60-
c.EVM[0].FinalityDepth = ptr[uint32](2)
61-
c.EVM[0].GasEstimator.LimitDefault = ptr(uint64(gasLimit))
58+
c.EVM[0].MinIncomingConfirmations = new(uint32(2))
59+
c.Feature.LogPoller = new(true)
60+
c.EVM[0].FinalityDepth = new(uint32(2))
61+
c.EVM[0].GasEstimator.LimitDefault = new(gasLimit)
6262
c.EVM[0].LogPollInterval = commonconfig.MustNewDuration(time.Second)
6363
})
6464

@@ -86,11 +86,18 @@ func TestStartHeartbeats(t *testing.T) {
8686
diff := heartbeatPeriod + 1*time.Second
8787
t.Logf("Sleeping %.2f seconds before checking blockhash in BHS added by BHS_Heartbeats_Service\n", diff.Seconds())
8888
time.Sleep(diff)
89-
// storeEarliest in BHS contract stores blocktip - 256 in the Blockhash Store (BHS)
90-
tipHeader, err := uni.backend.Client().HeaderByNumber(testutils.Context(t), nil)
91-
require.NoError(t, err)
92-
// the storeEarliest transaction will end up in a new block, hence the + 1 below.
93-
blockNumberStored := tipHeader.Number.Uint64() - 256 + 1
94-
verifyBlockhashStored(t, uni.coordinatorV2UniverseCommon, blockNumberStored)
89+
// The heartbeat store tx may not reach the mempool before the first
90+
// Commit under load, so we can't predict which block it mines in.
91+
// Commit blocks and check current_tip-256 on each attempt until BHS
92+
// has a blockhash stored at that offset.
93+
require.Eventually(t, func() bool {
94+
uni.backend.Commit()
95+
tip, tipErr := uni.backend.Client().HeaderByNumber(testutils.Context(t), nil)
96+
if tipErr != nil || tip == nil || tip.Number.Uint64() < 256 {
97+
return false
98+
}
99+
_, err := uni.bhsContract.GetBlockhash(nil, new(big.Int).SetUint64(tip.Number.Uint64()-256))
100+
return err == nil
101+
}, testutils.WaitTimeoutCustom(t, 5*time.Minute), time.Second)
95102
})
96103
}

0 commit comments

Comments
 (0)