Skip to content

Commit bbfe105

Browse files
committed
add reaper tests
1 parent 71491a3 commit bbfe105

1 file changed

Lines changed: 204 additions & 0 deletions

File tree

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package executing
2+
3+
import (
4+
"context"
5+
crand "crypto/rand"
6+
"testing"
7+
"time"
8+
9+
ds "github.com/ipfs/go-datastore"
10+
dssync "github.com/ipfs/go-datastore/sync"
11+
"github.com/libp2p/go-libp2p/core/crypto"
12+
"github.com/rs/zerolog"
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
"github.com/stretchr/testify/mock"
16+
17+
coresequencer "github.com/evstack/ev-node/core/sequencer"
18+
"github.com/evstack/ev-node/block/internal/common"
19+
"github.com/evstack/ev-node/pkg/config"
20+
"github.com/evstack/ev-node/pkg/genesis"
21+
"github.com/evstack/ev-node/pkg/signer/noop"
22+
testmocks "github.com/evstack/ev-node/test/mocks"
23+
)
24+
25+
// helper to create a minimal executor to capture notifications
26+
func newTestExecutor(t *testing.T) *Executor {
27+
t.Helper()
28+
29+
// signer is required by NewExecutor
30+
priv, _, err := crypto.GenerateEd25519Key(crand.Reader)
31+
require.NoError(t, err)
32+
s, err := noop.NewNoopSigner(priv)
33+
require.NoError(t, err)
34+
35+
exec := NewExecutor(
36+
nil, // store (unused)
37+
nil, // core executor (unused)
38+
nil, // sequencer (unused)
39+
s, // signer (required)
40+
nil, // cache (unused)
41+
nil, // metrics (unused)
42+
config.DefaultConfig,
43+
genesis.Genesis{ // minimal genesis
44+
ChainID: "test-chain",
45+
InitialHeight: 1,
46+
StartTime: time.Now(),
47+
ProposerAddress: []byte("p"),
48+
},
49+
nil, // header broadcaster
50+
nil, // data broadcaster
51+
zerolog.Nop(),
52+
common.DefaultBlockOptions(),
53+
)
54+
return exec
55+
}
56+
57+
// reaper with mocks and in-memory seen store
58+
func newTestReaper(t *testing.T, chainID string, execMock *testmocks.MockExecutor, seqMock *testmocks.MockSequencer, e *Executor) (*Reaper, ds.Batching) {
59+
t.Helper()
60+
store := dssync.MutexWrap(ds.NewMapDatastore())
61+
r := NewReaper(context.Background(), execMock, seqMock, chainID, 10*time.Millisecond, zerolog.Nop(), store, e)
62+
return r, store
63+
}
64+
65+
// no extra default options helper needed
66+
67+
func TestReaper_SubmitTxs_NewTxs_SubmitsAndPersistsAndNotifies(t *testing.T) {
68+
mockExec := testmocks.NewMockExecutor(t)
69+
mockSeq := testmocks.NewMockSequencer(t)
70+
71+
// Two new transactions
72+
tx1 := []byte("tx1")
73+
tx2 := []byte("tx2")
74+
mockExec.EXPECT().GetTxs(mock.Anything).Return([][]byte{tx1, tx2}, nil).Once()
75+
76+
// Expect a single SubmitBatchTxs with both txs
77+
mockSeq.EXPECT().SubmitBatchTxs(mock.Anything, mock.AnythingOfType("sequencer.SubmitBatchTxsRequest")).
78+
RunAndReturn(func(ctx context.Context, req coresequencer.SubmitBatchTxsRequest) (*coresequencer.SubmitBatchTxsResponse, error) {
79+
require.Equal(t, []byte("chain-A"), req.Id)
80+
require.NotNil(t, req.Batch)
81+
assert.Equal(t, [][]byte{tx1, tx2}, req.Batch.Transactions)
82+
return &coresequencer.SubmitBatchTxsResponse{}, nil
83+
}).Once()
84+
85+
// Minimal executor to capture NotifyNewTransactions
86+
e := newTestExecutor(t)
87+
88+
r, store := newTestReaper(t, "chain-A", mockExec, mockSeq, e)
89+
90+
r.SubmitTxs()
91+
92+
// Seen keys persisted
93+
has1, err := store.Has(context.Background(), ds.NewKey(hashTx(tx1)))
94+
require.NoError(t, err)
95+
has2, err := store.Has(context.Background(), ds.NewKey(hashTx(tx2)))
96+
require.NoError(t, err)
97+
assert.True(t, has1)
98+
assert.True(t, has2)
99+
100+
// Executor notified (non-blocking read)
101+
select {
102+
case <-e.txNotifyCh:
103+
// ok
104+
case <-time.After(100 * time.Millisecond):
105+
t.Fatal("expected NotifyNewTransactions to signal txNotifyCh")
106+
}
107+
}
108+
109+
func TestReaper_SubmitTxs_AllSeen_NoSubmit(t *testing.T) {
110+
mockExec := testmocks.NewMockExecutor(t)
111+
mockSeq := testmocks.NewMockSequencer(t)
112+
113+
tx1 := []byte("tx1")
114+
tx2 := []byte("tx2")
115+
116+
// Pre-populate seen store
117+
e := newTestExecutor(t)
118+
r, store := newTestReaper(t, "chain-B", mockExec, mockSeq, e)
119+
require.NoError(t, store.Put(context.Background(), ds.NewKey(hashTx(tx1)), []byte{1}))
120+
require.NoError(t, store.Put(context.Background(), ds.NewKey(hashTx(tx2)), []byte{1}))
121+
122+
mockExec.EXPECT().GetTxs(mock.Anything).Return([][]byte{tx1, tx2}, nil).Once()
123+
// No SubmitBatchTxs expected
124+
125+
r.SubmitTxs()
126+
127+
// Ensure no notification occurred
128+
select {
129+
case <-e.txNotifyCh:
130+
t.Fatal("did not expect notification when all txs are seen")
131+
default:
132+
// ok
133+
}
134+
}
135+
136+
func TestReaper_SubmitTxs_PartialSeen_FiltersAndPersists(t *testing.T) {
137+
mockExec := testmocks.NewMockExecutor(t)
138+
mockSeq := testmocks.NewMockSequencer(t)
139+
140+
txOld := []byte("old")
141+
txNew := []byte("new")
142+
143+
e := newTestExecutor(t)
144+
r, store := newTestReaper(t, "chain-C", mockExec, mockSeq, e)
145+
146+
// Mark txOld as seen
147+
require.NoError(t, store.Put(context.Background(), ds.NewKey(hashTx(txOld)), []byte{1}))
148+
149+
mockExec.EXPECT().GetTxs(mock.Anything).Return([][]byte{txOld, txNew}, nil).Once()
150+
mockSeq.EXPECT().SubmitBatchTxs(mock.Anything, mock.AnythingOfType("sequencer.SubmitBatchTxsRequest")).
151+
RunAndReturn(func(ctx context.Context, req coresequencer.SubmitBatchTxsRequest) (*coresequencer.SubmitBatchTxsResponse, error) {
152+
// Should only include txNew
153+
assert.Equal(t, [][]byte{txNew}, req.Batch.Transactions)
154+
return &coresequencer.SubmitBatchTxsResponse{}, nil
155+
}).Once()
156+
157+
r.SubmitTxs()
158+
159+
// Both should be seen after successful submit
160+
hasOld, err := store.Has(context.Background(), ds.NewKey(hashTx(txOld)))
161+
require.NoError(t, err)
162+
hasNew, err := store.Has(context.Background(), ds.NewKey(hashTx(txNew)))
163+
require.NoError(t, err)
164+
assert.True(t, hasOld)
165+
assert.True(t, hasNew)
166+
167+
// Notification should occur since a new tx was submitted
168+
select {
169+
case <-e.txNotifyCh:
170+
// ok
171+
case <-time.After(100 * time.Millisecond):
172+
t.Fatal("expected notification when new tx submitted")
173+
}
174+
}
175+
176+
func TestReaper_SubmitTxs_SequencerError_NoPersistence_NoNotify(t *testing.T) {
177+
mockExec := testmocks.NewMockExecutor(t)
178+
mockSeq := testmocks.NewMockSequencer(t)
179+
180+
tx := []byte("oops")
181+
mockExec.EXPECT().GetTxs(mock.Anything).Return([][]byte{tx}, nil).Once()
182+
mockSeq.EXPECT().SubmitBatchTxs(mock.Anything, mock.AnythingOfType("sequencer.SubmitBatchTxsRequest")).
183+
Return((*coresequencer.SubmitBatchTxsResponse)(nil), assert.AnError).Once()
184+
185+
e := newTestExecutor(t)
186+
r, store := newTestReaper(t, "chain-D", mockExec, mockSeq, e)
187+
188+
r.SubmitTxs()
189+
190+
// Should not be marked seen
191+
has, err := store.Has(context.Background(), ds.NewKey(hashTx(tx)))
192+
require.NoError(t, err)
193+
assert.False(t, has)
194+
195+
// Should not notify
196+
select {
197+
case <-e.txNotifyCh:
198+
t.Fatal("did not expect notification on sequencer error")
199+
default:
200+
// ok
201+
}
202+
}
203+
204+
// no additional helpers

0 commit comments

Comments
 (0)