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