hermez-node/coordinator/coordinator_test.go
coordinator/coordinator_test.goRaw
1package coordinator
2
3import (
4 "context"
5 "fmt"
6 "io/ioutil"
7 "math/big"
8 "os"
9 "testing"
10 "time"
11
12 ethCommon "github.com/ethereum/go-ethereum/common"
13 "github.com/hermeznetwork/hermez-node/batchbuilder"
14 dbUtils "github.com/hermeznetwork/hermez-node/db"
15 "github.com/hermeznetwork/hermez-node/db/historydb"
16 "github.com/hermeznetwork/hermez-node/db/l2db"
17 "github.com/hermeznetwork/hermez-node/db/statedb"
18 "github.com/hermeznetwork/hermez-node/log"
19 "github.com/hermeznetwork/hermez-node/prover"
20 "github.com/hermeznetwork/hermez-node/synchronizer"
21 "github.com/hermeznetwork/hermez-node/test"
22 "github.com/hermeznetwork/hermez-node/txselector"
23 "github.com/hermeznetwork/tracerr"
24 "github.com/iden3/go-merkletree/db/pebble"
25 "github.com/stretchr/testify/assert"
26 "github.com/stretchr/testify/require"
27)
28
29var deleteme = []string{}
30
31func pebbleMakeCheckpoint(source, dest string) error {
32 // Remove dest folder (if it exists) before doing the checkpoint
33 if _, err := os.Stat(dest); !os.IsNotExist(err) {
34 err := os.RemoveAll(dest)
35 if err != nil {
36 return tracerr.Wrap(err)
37 }
38 } else if err != nil && !os.IsNotExist(err) {
39 return tracerr.Wrap(err)
40 }
41
42 sto, err := pebble.NewPebbleStorage(source, false)
43 if err != nil {
44 return tracerr.Wrap(err)
45 }
46 defer func() {
47 errClose := sto.Pebble().Close()
48 if errClose != nil {
49 log.Errorw("Pebble.Close", "err", errClose)
50 }
51 }()
52
53 // execute Checkpoint
54 err = sto.Pebble().Checkpoint(dest)
55 if err != nil {
56 return tracerr.Wrap(err)
57 }
58
59 return nil
60}
61
62func TestMain(m *testing.M) {
63 exitVal := m.Run()
64 for _, dir := range deleteme {
65 if err := os.RemoveAll(dir); err != nil {
66 panic(err)
67 }
68 }
69 os.Exit(exitVal)
70}
71
72var syncDBPath string
73var txSelDBPath string
74var batchBuilderDBPath string
75
76func newTestModules(t *testing.T) (*historydb.HistoryDB, *l2db.L2DB,
77 *txselector.TxSelector, *batchbuilder.BatchBuilder) { // FUTURE once Synchronizer is ready, should return it also
78 nLevels := 32
79
80 var err error
81 syncDBPath, err = ioutil.TempDir("", "tmpSyncDB")
82 require.NoError(t, err)
83 deleteme = append(deleteme, syncDBPath)
84 syncSdb, err := statedb.NewStateDB(syncDBPath, statedb.TypeSynchronizer, nLevels)
85 assert.NoError(t, err)
86
87 pass := os.Getenv("POSTGRES_PASS")
88 db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
89 require.NoError(t, err)
90 test.WipeDB(db)
91 l2DB := l2db.NewL2DB(db, 10, 100, 24*time.Hour)
92 historyDB := historydb.NewHistoryDB(db)
93
94 txSelDBPath, err = ioutil.TempDir("", "tmpTxSelDB")
95 require.NoError(t, err)
96 deleteme = append(deleteme, txSelDBPath)
97 txsel, err := txselector.NewTxSelector(txSelDBPath, syncSdb, l2DB, 10, 10, 10)
98 assert.NoError(t, err)
99
100 batchBuilderDBPath, err = ioutil.TempDir("", "tmpBatchBuilderDB")
101 require.NoError(t, err)
102 deleteme = append(deleteme, batchBuilderDBPath)
103 bb, err := batchbuilder.NewBatchBuilder(batchBuilderDBPath, syncSdb, nil, 0, uint64(nLevels))
104 assert.NoError(t, err)
105
106 // l1Txs, coordinatorL1Txs, poolL2Txs := test.GenerateTestTxsFromSet(t, test.SetTest0)
107
108 return historyDB, l2DB, txsel, bb
109}
110
111type timer struct {
112 time int64
113}
114
115func (t *timer) Time() int64 {
116 currentTime := t.time
117 t.time++
118 return currentTime
119}
120
121var bidder = ethCommon.HexToAddress("0x6b175474e89094c44da98b954eedeac495271d0f")
122var forger = ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
123
124func newTestCoordinator(t *testing.T, forgerAddr ethCommon.Address, ethClient *test.Client, ethClientSetup *test.ClientSetup) *Coordinator {
125 historyDB, l2DB, txsel, bb := newTestModules(t)
126
127 debugBatchPath, err := ioutil.TempDir("", "tmpDebugBatch")
128 require.NoError(t, err)
129 deleteme = append(deleteme, debugBatchPath)
130
131 conf := Config{
132 ForgerAddress: forgerAddr,
133 ConfirmBlocks: 5,
134 L1BatchTimeoutPerc: 0.5,
135 EthClientAttempts: 5,
136 EthClientAttemptsDelay: 100 * time.Millisecond,
137 TxManagerCheckInterval: 300 * time.Millisecond,
138 DebugBatchPath: debugBatchPath,
139 }
140 serverProofs := []prover.Client{&prover.MockClient{}, &prover.MockClient{}}
141
142 scConsts := &synchronizer.SCConsts{
143 Rollup: *ethClientSetup.RollupConstants,
144 Auction: *ethClientSetup.AuctionConstants,
145 WDelayer: *ethClientSetup.WDelayerConstants,
146 }
147 initSCVars := &synchronizer.SCVariables{
148 Rollup: *ethClientSetup.RollupVariables,
149 Auction: *ethClientSetup.AuctionVariables,
150 WDelayer: *ethClientSetup.WDelayerVariables,
151 }
152 coord, err := NewCoordinator(conf, historyDB, l2DB, txsel, bb, serverProofs,
153 ethClient, scConsts, initSCVars)
154 require.NoError(t, err)
155 return coord
156}
157
158func TestCoordinatorFlow(t *testing.T) {
159 if os.Getenv("TEST_COORD_FLOW") == "" {
160 return
161 }
162 ethClientSetup := test.NewClientSetupExample()
163 var timer timer
164 ethClient := test.NewClient(true, &timer, &bidder, ethClientSetup)
165 coord := newTestCoordinator(t, forger, ethClient, ethClientSetup)
166
167 // Bid for slot 2 and 4
168 _, err := ethClient.AuctionSetCoordinator(forger, "https://foo.bar")
169 require.NoError(t, err)
170 _, err = ethClient.AuctionBidSimple(2, big.NewInt(9999))
171 require.NoError(t, err)
172 _, err = ethClient.AuctionBidSimple(4, big.NewInt(9999))
173 require.NoError(t, err)
174
175 coord.Start()
176 time.Sleep(1 * time.Second)
177
178 waitForSlot := func(slot int64) {
179 for {
180 blockNum, err := ethClient.EthLastBlock()
181 require.NoError(t, err)
182 nextBlockSlot, err := ethClient.AuctionGetSlotNumber(blockNum + 1)
183 require.NoError(t, err)
184 if nextBlockSlot == slot {
185 break
186 }
187 ethClient.CtlMineBlock()
188 time.Sleep(100 * time.Millisecond)
189 var stats synchronizer.Stats
190 stats.Eth.LastBlock = *ethClient.CtlLastBlock()
191 stats.Sync.LastBlock = stats.Eth.LastBlock
192 stats.Eth.LastBatch = ethClient.CtlLastForgedBatch()
193 stats.Sync.LastBatch = stats.Eth.LastBatch
194 canForge, err := ethClient.AuctionCanForge(forger, blockNum+1)
195 require.NoError(t, err)
196 if canForge {
197 // fmt.Println("DBG canForge")
198 stats.Sync.Auction.CurrentSlot.Forger = forger
199 }
200 // Copy stateDB to synchronizer if there was a new batch
201 source := fmt.Sprintf("%v/BatchNum%v", batchBuilderDBPath, stats.Sync.LastBatch)
202 dest := fmt.Sprintf("%v/BatchNum%v", syncDBPath, stats.Sync.LastBatch)
203 if stats.Sync.LastBatch != 0 {
204 if _, err := os.Stat(dest); os.IsNotExist(err) {
205 log.Infow("Making pebble checkpoint for sync",
206 "source", source, "dest", dest)
207 err = pebbleMakeCheckpoint(source, dest)
208 require.NoError(t, err)
209 }
210 }
211 coord.SendMsg(MsgSyncBlock{
212 Stats: stats,
213 })
214 }
215 }
216
217 // NOTE: With the current test, the coordinator will enter in forge
218 // time before the bidded slot because no one else is forging in the
219 // other slots before the slot deadline.
220 // simulate forgeSequence time
221 waitForSlot(2)
222 log.Info("~~~ simulate entering in forge time")
223 time.Sleep(1 * time.Second)
224
225 // simulate going out from forgeSequence
226 waitForSlot(3)
227 log.Info("~~~ simulate going out from forge time")
228 time.Sleep(1 * time.Second)
229
230 // simulate entering forgeSequence time again
231 waitForSlot(4)
232 log.Info("~~~ simulate entering in forge time again")
233 time.Sleep(2 * time.Second)
234
235 // simulate stopping forgerLoop by channel
236 log.Info("~~~ simulate stopping forgerLoop by closing coordinator stopch")
237 coord.Stop()
238 time.Sleep(1 * time.Second)
239}
240
241func TestCoordinatorStartStop(t *testing.T) {
242 ethClientSetup := test.NewClientSetupExample()
243 var timer timer
244 ethClient := test.NewClient(true, &timer, &bidder, ethClientSetup)
245 coord := newTestCoordinator(t, forger, ethClient, ethClientSetup)
246 coord.Start()
247 coord.Stop()
248}
249
250func TestCoordCanForge(t *testing.T) {
251 ethClientSetup := test.NewClientSetupExample()
252 bootForger := ethClientSetup.AuctionVariables.BootCoordinator
253
254 var timer timer
255 ethClient := test.NewClient(true, &timer, &bidder, ethClientSetup)
256 coord := newTestCoordinator(t, forger, ethClient, ethClientSetup)
257 _, err := ethClient.AuctionSetCoordinator(forger, "https://foo.bar")
258 require.NoError(t, err)
259 _, err = ethClient.AuctionBidSimple(2, big.NewInt(9999))
260 require.NoError(t, err)
261
262 bootCoord := newTestCoordinator(t, bootForger, ethClient, ethClientSetup)
263
264 assert.Equal(t, forger, coord.cfg.ForgerAddress)
265 assert.Equal(t, bootForger, bootCoord.cfg.ForgerAddress)
266 ethBootCoord, err := ethClient.AuctionGetBootCoordinator()
267 require.NoError(t, err)
268 assert.Equal(t, &bootForger, ethBootCoord)
269
270 var stats synchronizer.Stats
271
272 // Slot 0. No bid, so the winner is the boot coordinator
273 stats.Eth.LastBlock.Num = ethClientSetup.AuctionConstants.GenesisBlockNum
274 stats.Sync.LastBlock = stats.Eth.LastBlock
275 stats.Sync.Auction.CurrentSlot.Forger = bootForger
276 assert.Equal(t, false, coord.canForge(&stats))
277 assert.Equal(t, true, bootCoord.canForge(&stats))
278
279 // Slot 0. No bid, and we reach the deadline, so anyone can forge
280 stats.Eth.LastBlock.Num = ethClientSetup.AuctionConstants.GenesisBlockNum +
281 int64(ethClientSetup.AuctionVariables.SlotDeadline)
282 stats.Sync.LastBlock = stats.Eth.LastBlock
283 stats.Sync.Auction.CurrentSlot.Forger = bootForger
284 assert.Equal(t, true, coord.canForge(&stats))
285 assert.Equal(t, true, bootCoord.canForge(&stats))
286
287 // Slot 1. coordinator bid, so the winner is the coordinator
288 stats.Eth.LastBlock.Num = ethClientSetup.AuctionConstants.GenesisBlockNum +
289 1*int64(ethClientSetup.AuctionConstants.BlocksPerSlot)
290 stats.Sync.LastBlock = stats.Eth.LastBlock
291 stats.Sync.Auction.CurrentSlot.Forger = forger
292 assert.Equal(t, true, coord.canForge(&stats))
293 assert.Equal(t, false, bootCoord.canForge(&stats))
294}
295
296func TestCoordHandleMsgSyncStats(t *testing.T) {
297 ethClientSetup := test.NewClientSetupExample()
298 bootForger := ethClientSetup.AuctionVariables.BootCoordinator
299
300 var timer timer
301 ethClient := test.NewClient(true, &timer, &bidder, ethClientSetup)
302 coord := newTestCoordinator(t, forger, ethClient, ethClientSetup)
303 _, err := ethClient.AuctionSetCoordinator(forger, "https://foo.bar")
304 require.NoError(t, err)
305 _, err = ethClient.AuctionBidSimple(2, big.NewInt(9999))
306 require.NoError(t, err)
307
308 var msg MsgSyncBlock
309 stats := &msg.Stats
310 ctx := context.Background()
311
312 // Slot 0. No bid, so the winner is the boot coordinator
313 // pipelineStarted: false -> false
314 stats.Eth.LastBlock.Num = ethClientSetup.AuctionConstants.GenesisBlockNum
315 stats.Sync.LastBlock = stats.Eth.LastBlock
316 stats.Sync.Auction.CurrentSlot.Forger = bootForger
317 assert.Equal(t, false, coord.canForge(stats))
318 require.NoError(t, coord.handleMsgSyncBlock(ctx, &msg))
319 assert.Nil(t, coord.pipeline)
320
321 // Slot 0. No bid, and we reach the deadline, so anyone can forge
322 // pipelineStarted: false -> true
323 stats.Eth.LastBlock.Num = ethClientSetup.AuctionConstants.GenesisBlockNum +
324 int64(ethClientSetup.AuctionVariables.SlotDeadline)
325 stats.Sync.LastBlock = stats.Eth.LastBlock
326 stats.Sync.Auction.CurrentSlot.Forger = bootForger
327 assert.Equal(t, true, coord.canForge(stats))
328 require.NoError(t, coord.handleMsgSyncBlock(ctx, &msg))
329 assert.NotNil(t, coord.pipeline)
330
331 // Slot 0. No bid, and we reach the deadline, so anyone can forge
332 // pipelineStarted: true -> true
333 stats.Eth.LastBlock.Num = ethClientSetup.AuctionConstants.GenesisBlockNum +
334 int64(ethClientSetup.AuctionVariables.SlotDeadline) + 1
335 stats.Sync.LastBlock = stats.Eth.LastBlock
336 stats.Sync.Auction.CurrentSlot.Forger = bootForger
337 assert.Equal(t, true, coord.canForge(stats))
338 require.NoError(t, coord.handleMsgSyncBlock(ctx, &msg))
339 assert.NotNil(t, coord.pipeline)
340
341 // Slot 0. No bid, so the winner is the boot coordinator
342 // pipelineStarted: true -> false
343 stats.Eth.LastBlock.Num = ethClientSetup.AuctionConstants.GenesisBlockNum +
344 1*int64(ethClientSetup.AuctionConstants.BlocksPerSlot)
345 stats.Sync.LastBlock = stats.Eth.LastBlock
346 stats.Sync.Auction.CurrentSlot.Forger = bootForger
347 assert.Equal(t, false, coord.canForge(stats))
348 require.NoError(t, coord.handleMsgSyncBlock(ctx, &msg))
349 assert.Nil(t, coord.pipeline)
350}
351
352func TestPipelineShouldL1L2Batch(t *testing.T) {
353 ethClientSetup := test.NewClientSetupExample()
354
355 var timer timer
356 ctx := context.Background()
357 ethClient := test.NewClient(true, &timer, &bidder, ethClientSetup)
358 coord := newTestCoordinator(t, forger, ethClient, ethClientSetup)
359 pipeline, err := coord.newPipeline(ctx)
360 require.NoError(t, err)
361 pipeline.vars = coord.vars
362
363 // Check that the parameters are the ones we expect and use in this test
364 require.Equal(t, 0.5, pipeline.cfg.L1BatchTimeoutPerc)
365 require.Equal(t, int64(9), ethClientSetup.RollupVariables.ForgeL1L2BatchTimeout)
366 l1BatchTimeoutPerc := pipeline.cfg.L1BatchTimeoutPerc
367 l1BatchTimeout := ethClientSetup.RollupVariables.ForgeL1L2BatchTimeout
368
369 var stats synchronizer.Stats
370
371 startBlock := int64(100)
372
373 //
374 // No scheduled L1Batch
375 //
376
377 // Last L1Batch was a long time ago
378 stats.Eth.LastBlock.Num = startBlock
379 stats.Sync.LastBlock = stats.Eth.LastBlock
380 stats.Sync.LastL1BatchBlock = 0
381 pipeline.stats = stats
382 assert.Equal(t, true, pipeline.shouldL1L2Batch())
383
384 stats.Sync.LastL1BatchBlock = startBlock
385
386 // We are are one block before the timeout range * 0.5
387 stats.Eth.LastBlock.Num = startBlock + int64(float64(l1BatchTimeout)*l1BatchTimeoutPerc) - 1
388 stats.Sync.LastBlock = stats.Eth.LastBlock
389 pipeline.stats = stats
390 assert.Equal(t, false, pipeline.shouldL1L2Batch())
391
392 // We are are at timeout range * 0.5
393 stats.Eth.LastBlock.Num = startBlock + int64(float64(l1BatchTimeout)*l1BatchTimeoutPerc)
394 stats.Sync.LastBlock = stats.Eth.LastBlock
395 pipeline.stats = stats
396 assert.Equal(t, true, pipeline.shouldL1L2Batch())
397
398 //
399 // Scheduled L1Batch
400 //
401 pipeline.lastScheduledL1BatchBlockNum = startBlock
402 stats.Sync.LastL1BatchBlock = startBlock - 10
403
404 // We are are one block before the timeout range * 0.5
405 stats.Eth.LastBlock.Num = startBlock + int64(float64(l1BatchTimeout)*l1BatchTimeoutPerc) - 1
406 stats.Sync.LastBlock = stats.Eth.LastBlock
407 pipeline.stats = stats
408 assert.Equal(t, false, pipeline.shouldL1L2Batch())
409
410 // We are are at timeout range * 0.5
411 stats.Eth.LastBlock.Num = startBlock + int64(float64(l1BatchTimeout)*l1BatchTimeoutPerc)
412 stats.Sync.LastBlock = stats.Eth.LastBlock
413 pipeline.stats = stats
414 assert.Equal(t, true, pipeline.shouldL1L2Batch())
415}
416
417// TODO: Test Reorg
418// TODO: Test Pipeline
419// TODO: Test TxMonitor
420// TODO: Test forgeSendServerProof
421// TODO: Test waitServerProof
422// TODO: Test handleReorg