@@ -19,6 +19,8 @@ import (
1919 "github.com/ethereum-optimism/optimism/op-service/testlog"
2020)
2121
22+ // mockBTOBackend mocks BTOBackend for testing.
23+
2224type mockBTOBackend struct {
2325 mock.Mock
2426}
@@ -44,38 +46,8 @@ func (m *mockBTOBackend) BlockByNumber(ctx context.Context, number *big.Int) (*t
4446 return args .Get (0 ).(* types.Block ), args .Error (1 )
4547}
4648
47- func (m * mockBTOBackend ) SubscribeNewHead (ctx context.Context , ch chan <- * types.Header ) (ethereum.Subscription , error ) {
48- args := m .Called (ctx , ch )
49- if args .Get (0 ) == nil {
50- return nil , args .Error (1 )
51- }
52- return args .Get (0 ).(ethereum.Subscription ), args .Error (1 )
53- }
54-
5549var _ BTOBackend = (* mockBTOBackend )(nil )
5650
57- // mockSubscription implements ethereum.Subscription for testing.
58- type mockSubscription struct {
59- errCh chan error
60- unsubbed bool
61- }
62-
63- func newMockSubscription () * mockSubscription {
64- return & mockSubscription {
65- errCh : make (chan error , 1 ),
66- }
67- }
68-
69- func (s * mockSubscription ) Unsubscribe () {
70- if ! s .unsubbed {
71- s .unsubbed = true
72- }
73- }
74-
75- func (s * mockSubscription ) Err () <- chan error {
76- return s .errCh
77- }
78-
7951func createHeader (blockNum uint64 , excessBlobGas * uint64 ) * types.Header {
8052 header := & types.Header {
8153 Number : big .NewInt (int64 (blockNum )),
@@ -433,19 +405,20 @@ func TestExtractBlobFeeCaps(t *testing.T) {
433405}
434406
435407func TestOracleLifecycle (t * testing.T ) {
436- mbackend := new (mockBTOBackend )
437408 chainConfig := params .MainnetChainConfig
438409 logger := testlog .Logger (t , log .LevelDebug )
439410
440- oracle := NewBlobTipOracle (mbackend , chainConfig , logger , & BlobTipOracleConfig {
441- PricesCacheSize : 10 ,
442- BlockCacheSize : 10 ,
443- MaxBlocks : 2 ,
444- Percentile : 60 ,
445- NetworkTimeout : time .Second ,
446- })
411+ t .Run ("start and close with polling" , func (t * testing.T ) {
412+ mbackend := new (mockBTOBackend )
413+ oracle := NewBlobTipOracle (mbackend , chainConfig , logger , & BlobTipOracleConfig {
414+ PricesCacheSize : 10 ,
415+ BlockCacheSize : 10 ,
416+ MaxBlocks : 2 ,
417+ Percentile : 60 ,
418+ NetworkTimeout : time .Second ,
419+ PollRate : 50 * time .Millisecond , // Fast polling for test
420+ })
447421
448- t .Run ("start and close" , func (t * testing.T ) {
449422 latestBlock := uint64 (100 )
450423
451424 // Mock pre-population calls
@@ -459,12 +432,15 @@ func TestOracleLifecycle(t *testing.T) {
459432 mbackend .On ("BlockByNumber" , mock .Anything , big .NewInt (int64 (i ))).Return (block , nil ).Once ()
460433 }
461434
462- // Mock subscription
463- sub := newMockSubscription ()
464- var headerCh chan <- * types.Header
465- mbackend .On ("SubscribeNewHead" , mock .Anything , mock .Anything ).Run (func (args mock.Arguments ) {
466- headerCh = args .Get (1 ).(chan <- * types.Header )
467- }).Return (sub , nil ).Once ()
435+ // Mock polling: first return NotFound, then return a new header
436+ mbackend .On ("HeaderByNumber" , mock .Anything , big .NewInt (101 )).Return (nil , ethereum .NotFound ).Once ()
437+ newHeader := createHeader (101 , & excessBlobGas )
438+ newBlock := createBlock (101 , newHeader .BaseFee , []* types.Transaction {})
439+ mbackend .On ("HeaderByNumber" , mock .Anything , big .NewInt (101 )).Return (newHeader , nil ).Once ()
440+ mbackend .On ("BlockByNumber" , mock .Anything , big .NewInt (101 )).Return (newBlock , nil ).Once ()
441+
442+ // After processing block 101, polling will try block 102 which doesn't exist
443+ mbackend .On ("HeaderByNumber" , mock .Anything , big .NewInt (102 )).Return (nil , ethereum .NotFound ).Maybe ()
468444
469445 // Start the oracle
470446 err := oracle .Start ()
@@ -481,14 +457,7 @@ func TestOracleLifecycle(t *testing.T) {
481457 require .Equal (t , uint64 (100 ), latestBlockNum )
482458 require .NotNil (t , fee )
483459
484- // Send a new header through the subscription to verify processing works
485- newHeader := createHeader (101 , & excessBlobGas )
486- newBlock := createBlock (101 , newHeader .BaseFee , []* types.Transaction {})
487- mbackend .On ("BlockByNumber" , mock .Anything , big .NewInt (101 )).Return (newBlock , nil ).Once ()
488-
489- headerCh <- newHeader
490-
491- // Give the goroutine time to process
460+ // Wait for polling to pick up the new header
492461 require .Eventually (t , func () bool {
493462 latestBlockNum , _ = oracle .GetLatestBlobBaseFee ()
494463 return latestBlockNum == 101
@@ -497,8 +466,6 @@ func TestOracleLifecycle(t *testing.T) {
497466 // Close the oracle
498467 oracle .Close ()
499468
500- // Verify subscription was unsubscribed
501- require .True (t , sub .unsubbed , "subscription should be unsubscribed after Close" )
502469 select {
503470 case <- oracle .loopDone :
504471 // Expect loop to have exited
@@ -510,6 +477,7 @@ func TestOracleLifecycle(t *testing.T) {
510477 })
511478
512479 t .Run ("close before start is safe" , func (t * testing.T ) {
480+ mbackend := new (mockBTOBackend )
513481 oracle2 := NewBlobTipOracle (mbackend , chainConfig , logger , & BlobTipOracleConfig {
514482 PricesCacheSize : 10 ,
515483 BlockCacheSize : 10 ,
0 commit comments