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