arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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 forgeBatch
421// TODO: Test waitServerProof
422// TODO: Test handleReorg