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.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