github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/async/asyncgossiper_test.go (about)

     1  package async
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/ethereum-optimism/optimism/op-service/eth"
    10  	"github.com/ethereum/go-ethereum/common/hexutil"
    11  	"github.com/ethereum/go-ethereum/log"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  type mockNetwork struct {
    16  	reqs []*eth.ExecutionPayloadEnvelope
    17  }
    18  
    19  func (m *mockNetwork) PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error {
    20  	m.reqs = append(m.reqs, payload)
    21  	return nil
    22  }
    23  
    24  type mockMetrics struct{}
    25  
    26  func (m *mockMetrics) RecordPublishingError() {}
    27  
    28  // TestAsyncGossiper tests the AsyncGossiper component
    29  // because the component is small and simple, it is tested as a whole
    30  // this test starts, runs, clears and stops the AsyncGossiper
    31  // because the AsyncGossiper is run in an async component, it is tested with eventually
    32  func TestAsyncGossiper(t *testing.T) {
    33  	m := &mockNetwork{}
    34  	// Create a new instance of AsyncGossiper
    35  	p := NewAsyncGossiper(context.Background(), m, log.New(), &mockMetrics{})
    36  
    37  	// Start the AsyncGossiper
    38  	p.Start()
    39  
    40  	// Test that the AsyncGossiper is running within a short duration
    41  	require.Eventually(t, func() bool {
    42  		return p.running.Load()
    43  	}, 10*time.Second, 10*time.Millisecond)
    44  
    45  	// send a payload
    46  	payload := &eth.ExecutionPayload{
    47  		BlockNumber: hexutil.Uint64(1),
    48  	}
    49  	envelope := &eth.ExecutionPayloadEnvelope{
    50  		ExecutionPayload: payload,
    51  	}
    52  	p.Gossip(envelope)
    53  	require.Eventually(t, func() bool {
    54  		// Test that the gossiper has content at all
    55  		return p.Get() == envelope &&
    56  			// Test that the payload has been sent to the (mock) network
    57  			m.reqs[0] == envelope
    58  	}, 10*time.Second, 10*time.Millisecond)
    59  
    60  	p.Clear()
    61  	require.Eventually(t, func() bool {
    62  		// Test that the gossiper has no payload
    63  		return p.Get() == nil
    64  	}, 10*time.Second, 10*time.Millisecond)
    65  
    66  	// Stop the AsyncGossiper
    67  	p.Stop()
    68  
    69  	// Test that the AsyncGossiper stops within a short duration
    70  	require.Eventually(t, func() bool {
    71  		return !p.running.Load()
    72  	}, 10*time.Second, 10*time.Millisecond)
    73  }
    74  
    75  // TestAsyncGossiperLoop confirms that when called repeatedly, the AsyncGossiper holds the latest payload
    76  // and sends all payloads to the network
    77  func TestAsyncGossiperLoop(t *testing.T) {
    78  	m := &mockNetwork{}
    79  	// Create a new instance of AsyncGossiper
    80  	p := NewAsyncGossiper(context.Background(), m, log.New(), &mockMetrics{})
    81  
    82  	// Start the AsyncGossiper
    83  	p.Start()
    84  
    85  	// Test that the AsyncGossiper is running within a short duration
    86  	require.Eventually(t, func() bool {
    87  		return p.running.Load()
    88  	}, 10*time.Second, 10*time.Millisecond)
    89  
    90  	// send multiple payloads
    91  	for i := 0; i < 10; i++ {
    92  		payload := &eth.ExecutionPayload{
    93  			BlockNumber: hexutil.Uint64(i),
    94  		}
    95  		envelope := &eth.ExecutionPayloadEnvelope{
    96  			ExecutionPayload: payload,
    97  		}
    98  		p.Gossip(envelope)
    99  		require.Eventually(t, func() bool {
   100  			// Test that the gossiper has content at all
   101  			return p.Get() == envelope &&
   102  				// Test that the payload has been sent to the (mock) network
   103  				m.reqs[len(m.reqs)-1] == envelope
   104  		}, 10*time.Second, 10*time.Millisecond)
   105  	}
   106  	require.Equal(t, 10, len(m.reqs))
   107  	// Stop the AsyncGossiper
   108  	p.Stop()
   109  	// Test that the AsyncGossiper stops within a short duration
   110  	require.Eventually(t, func() bool {
   111  		return !p.running.Load()
   112  	}, 10*time.Second, 10*time.Millisecond)
   113  }
   114  
   115  // failingNetwork is a mock network that always fails to publish
   116  type failingNetwork struct{}
   117  
   118  func (f *failingNetwork) PublishL2Payload(ctx context.Context, payload *eth.ExecutionPayloadEnvelope) error {
   119  	return errors.New("failed to publish")
   120  }
   121  
   122  // TestAsyncGossiperFailToPublish tests that the AsyncGossiper clears the stored payload if the network fails
   123  func TestAsyncGossiperFailToPublish(t *testing.T) {
   124  	m := &failingNetwork{}
   125  	// Create a new instance of AsyncGossiper
   126  	p := NewAsyncGossiper(context.Background(), m, log.New(), &mockMetrics{})
   127  
   128  	// Start the AsyncGossiper
   129  	p.Start()
   130  
   131  	// send a payload
   132  	payload := &eth.ExecutionPayload{
   133  		BlockNumber: hexutil.Uint64(1),
   134  	}
   135  	envelope := &eth.ExecutionPayloadEnvelope{
   136  		ExecutionPayload: payload,
   137  	}
   138  	p.Gossip(envelope)
   139  	// Rather than expect the payload to become available, we should never see it, due to the publish failure
   140  	require.Never(t, func() bool {
   141  		return p.Get() == envelope
   142  	}, 10*time.Second, 10*time.Millisecond)
   143  	// Stop the AsyncGossiper
   144  	p.Stop()
   145  	// Test that the AsyncGossiper stops within a short duration
   146  	require.Eventually(t, func() bool {
   147  		return !p.running.Load()
   148  	}, 10*time.Second, 10*time.Millisecond)
   149  }