arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/test/til/txs.go
test/til/txs.goRaw
  1package til
  2
  3import (
  4	"crypto/ecdsa"
  5	"fmt"
  6	"math/big"
  7	"strconv"
  8	"strings"
  9	"time"
 10
 11	ethCommon "github.com/ethereum/go-ethereum/common"
 12	ethCrypto "github.com/ethereum/go-ethereum/crypto"
 13	"github.com/hermeznetwork/hermez-node/common"
 14	"github.com/hermeznetwork/hermez-node/log"
 15	"github.com/iden3/go-iden3-crypto/babyjub"
 16)
 17
 18func newBatchData(batchNum int) common.BatchData {
 19	return common.BatchData{
 20		L1CoordinatorTxs: []common.L1Tx{},
 21		L2Txs:            []common.L2Tx{},
 22		Batch: common.Batch{
 23			BatchNum:  common.BatchNum(batchNum),
 24			StateRoot: big.NewInt(0), ExitRoot: big.NewInt(0)},
 25	}
 26}
 27
 28// Context contains the data of the test
 29type Context struct {
 30	Instructions          []instruction
 31	userNames             []string
 32	Users                 map[string]*User
 33	lastRegisteredTokenID common.TokenID
 34	l1CreatedAccounts     map[string]*Account
 35
 36	// rollupConstMaxL1UserTx Maximum L1-user transactions allowed to be queued in a batch
 37	rollupConstMaxL1UserTx int
 38
 39	idx           int
 40	currBlock     common.BlockData
 41	currBatch     common.BatchData
 42	currBatchNum  int
 43	queues        [][]L1Tx
 44	toForgeNum    int
 45	openToForge   int
 46	currBatchTest struct {
 47		l1CoordinatorTxs []L1Tx
 48		l2Txs            []L2Tx
 49	}
 50	blockNum int64
 51}
 52
 53// NewContext returns a new Context
 54func NewContext(rollupConstMaxL1UserTx int) *Context {
 55	currBatchNum := 1 // The protocol defines the first batchNum to be 1
 56	return &Context{
 57		Users:                 make(map[string]*User),
 58		l1CreatedAccounts:     make(map[string]*Account),
 59		lastRegisteredTokenID: 0,
 60
 61		rollupConstMaxL1UserTx: rollupConstMaxL1UserTx,
 62		idx:                    common.UserThreshold,
 63		// We use some placeholder values for StateRoot and ExitTree
 64		// because these values will never be nil
 65		currBatch:    newBatchData(currBatchNum),
 66		currBatchNum: currBatchNum,
 67		// start with 2 queues, one for toForge, and the other for openToForge
 68		queues:      make([][]L1Tx, 2),
 69		toForgeNum:  0,
 70		openToForge: 1,
 71		//nolint:gomnd
 72		blockNum: 2, // rollup genesis blockNum
 73	}
 74}
 75
 76// Account contains the data related to the account for a specific TokenID of a User
 77type Account struct {
 78	Idx   common.Idx
 79	Nonce common.Nonce
 80}
 81
 82// User contains the data related to a testing user
 83type User struct {
 84	BJJ      *babyjub.PrivateKey
 85	Addr     ethCommon.Address
 86	Accounts map[common.TokenID]*Account
 87}
 88
 89// L1Tx is the data structure used internally for transaction test generation,
 90// which contains a common.L1Tx data plus some intermediate data for the
 91// transaction generation.
 92type L1Tx struct {
 93	lineNum     int
 94	fromIdxName string
 95	toIdxName   string
 96
 97	L1Tx common.L1Tx
 98}
 99
100// L2Tx is the data structure used internally for transaction test generation,
101// which contains a common.L2Tx data plus some intermediate data for the
102// transaction generation.
103type L2Tx struct {
104	lineNum     int
105	fromIdxName string
106	toIdxName   string
107	tokenID     common.TokenID
108	L2Tx        common.L2Tx
109}
110
111// GenerateBlocks returns an array of BlockData for a given set. It uses the
112// users (keys & nonces) of the Context.
113func (tc *Context) GenerateBlocks(set string) ([]common.BlockData, error) {
114	parser := newParser(strings.NewReader(set))
115	parsedSet, err := parser.parse()
116	if err != nil {
117		return nil, err
118	}
119	if parsedSet.typ != setTypeBlockchain {
120		return nil, fmt.Errorf("Expected set type: %s, found: %s", setTypeBlockchain, parsedSet.typ)
121	}
122
123	tc.Instructions = parsedSet.instructions
124	tc.userNames = parsedSet.users
125
126	tc.generateKeys(tc.userNames)
127
128	var blocks []common.BlockData
129	for _, inst := range parsedSet.instructions {
130		switch inst.typ {
131		case txTypeCreateAccountDepositCoordinator: // tx source: L1CoordinatorTx
132			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
133				log.Error(err)
134				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
135			}
136			tx := common.L1Tx{
137				FromEthAddr: tc.Users[inst.from].Addr,
138				FromBJJ:     tc.Users[inst.from].BJJ.Public(),
139				TokenID:     inst.tokenID,
140				Amount:      big.NewInt(0),
141				LoadAmount:  big.NewInt(int64(inst.loadAmount)),
142				Type:        common.TxTypeCreateAccountDeposit, // as txTypeCreateAccountDepositCoordinator is not valid oustide Til package
143			}
144			testTx := L1Tx{
145				lineNum:     inst.lineNum,
146				fromIdxName: inst.from,
147				L1Tx:        tx,
148			}
149
150			tc.currBatchTest.l1CoordinatorTxs = append(tc.currBatchTest.l1CoordinatorTxs, testTx)
151		case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer: // tx source: L1UserTx
152			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
153				log.Error(err)
154				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
155			}
156			tx := common.L1Tx{
157				FromEthAddr: tc.Users[inst.from].Addr,
158				FromBJJ:     tc.Users[inst.from].BJJ.Public(),
159				TokenID:     inst.tokenID,
160				Amount:      big.NewInt(0),
161				LoadAmount:  big.NewInt(int64(inst.loadAmount)),
162				Type:        inst.typ,
163			}
164			if inst.typ == common.TxTypeCreateAccountDepositTransfer {
165				tx.Amount = big.NewInt(int64(inst.amount))
166			}
167			testTx := L1Tx{
168				lineNum:     inst.lineNum,
169				fromIdxName: inst.from,
170				toIdxName:   inst.to,
171				L1Tx:        tx,
172			}
173			if err := tc.addToL1Queue(testTx); err != nil {
174				return nil, err
175			}
176		case common.TxTypeDeposit, common.TxTypeDepositTransfer: // tx source: L1UserTx
177			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
178				log.Error(err)
179				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
180			}
181			if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
182				log.Error(err)
183				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
184			}
185			tx := common.L1Tx{
186				TokenID:    inst.tokenID,
187				Amount:     big.NewInt(0),
188				LoadAmount: big.NewInt(int64(inst.loadAmount)),
189				Type:       inst.typ,
190			}
191			if inst.typ == common.TxTypeDepositTransfer {
192				tx.Amount = big.NewInt(int64(inst.amount))
193			}
194			testTx := L1Tx{
195				lineNum:     inst.lineNum,
196				fromIdxName: inst.from,
197				toIdxName:   inst.to,
198				L1Tx:        tx,
199			}
200			if err := tc.addToL1Queue(testTx); err != nil {
201				return nil, err
202			}
203		case common.TxTypeTransfer: // L2Tx
204			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
205				log.Error(err)
206				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
207			}
208			tx := common.L2Tx{
209				Amount: big.NewInt(int64(inst.amount)),
210				Fee:    common.FeeSelector(inst.fee),
211				Type:   common.TxTypeTransfer,
212			}
213			tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
214			testTx := L2Tx{
215				lineNum:     inst.lineNum,
216				fromIdxName: inst.from,
217				toIdxName:   inst.to,
218				tokenID:     inst.tokenID,
219				L2Tx:        tx,
220			}
221			tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
222		case common.TxTypeForceTransfer: // tx source: L1UserTx
223			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
224				log.Error(err)
225				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
226			}
227			tx := common.L1Tx{
228				TokenID:    inst.tokenID,
229				Amount:     big.NewInt(int64(inst.amount)),
230				LoadAmount: big.NewInt(0),
231				Type:       common.TxTypeForceTransfer,
232			}
233			testTx := L1Tx{
234				lineNum:     inst.lineNum,
235				fromIdxName: inst.from,
236				toIdxName:   inst.to,
237				L1Tx:        tx,
238			}
239			if err := tc.addToL1Queue(testTx); err != nil {
240				return nil, err
241			}
242		case common.TxTypeExit: // tx source: L2Tx
243			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
244				log.Error(err)
245				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
246			}
247			tx := common.L2Tx{
248				ToIdx:  common.Idx(1), // as is an Exit
249				Amount: big.NewInt(int64(inst.amount)),
250				Type:   common.TxTypeExit,
251			}
252			tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
253			testTx := L2Tx{
254				lineNum:     inst.lineNum,
255				fromIdxName: inst.from,
256				toIdxName:   inst.to,
257				tokenID:     inst.tokenID,
258				L2Tx:        tx,
259			}
260			tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
261		case common.TxTypeForceExit: // tx source: L1UserTx
262			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
263				log.Error(err)
264				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
265			}
266			tx := common.L1Tx{
267				ToIdx:      common.Idx(1), // as is an Exit
268				TokenID:    inst.tokenID,
269				Amount:     big.NewInt(int64(inst.amount)),
270				LoadAmount: big.NewInt(0),
271				Type:       common.TxTypeForceExit,
272			}
273			testTx := L1Tx{
274				lineNum:     inst.lineNum,
275				fromIdxName: inst.from,
276				toIdxName:   inst.to,
277				L1Tx:        tx,
278			}
279			if err := tc.addToL1Queue(testTx); err != nil {
280				return nil, err
281			}
282		case typeNewBatch:
283			if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
284				return nil, err
285			}
286			if err = tc.setIdxs(); err != nil {
287				log.Error(err)
288				return nil, err
289			}
290		case typeNewBatchL1:
291			// for each L1UserTx of the queues[ToForgeNum], calculate the Idx
292			if err = tc.calculateIdxForL1Txs(false, tc.queues[tc.toForgeNum]); err != nil {
293				return nil, err
294			}
295			if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
296				return nil, err
297			}
298			tc.currBatch.L1Batch = true
299			if err = tc.setIdxs(); err != nil {
300				log.Error(err)
301				return nil, err
302			}
303			// advance batch
304			tc.toForgeNum++
305			if tc.toForgeNum == tc.openToForge {
306				tc.openToForge++
307				newQueue := []L1Tx{}
308				tc.queues = append(tc.queues, newQueue)
309			}
310		case typeNewBlock:
311			tc.currBlock.Block = common.Block{
312				EthBlockNum: tc.blockNum,
313			}
314			blocks = append(blocks, tc.currBlock)
315			tc.blockNum++
316			tc.currBlock = common.BlockData{}
317		case typeAddToken:
318			newToken := common.Token{
319				EthAddr: ethCommon.BigToAddress(big.NewInt(int64(inst.tokenID * 100))), //nolint:gomnd
320				// Name:        fmt.Sprintf("Token %d", inst.tokenID),
321				// Symbol:      fmt.Sprintf("TK%d", inst.tokenID),
322				// Decimals:    18,
323				TokenID:     inst.tokenID,
324				EthBlockNum: tc.blockNum,
325			}
326			if inst.tokenID != tc.lastRegisteredTokenID+1 {
327				return nil, fmt.Errorf("Line %d: AddToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", inst.lineNum, tc.lastRegisteredTokenID+1, inst.tokenID)
328			}
329			tc.lastRegisteredTokenID++
330			tc.currBlock.AddedTokens = append(tc.currBlock.AddedTokens, newToken)
331		default:
332			return nil, fmt.Errorf("Line %d: Unexpected type: %s", inst.lineNum, inst.typ)
333		}
334	}
335
336	return blocks, nil
337}
338
339// calculateIdxsForL1Txs calculates new Idx for new created accounts. If
340// 'isCoordinatorTxs==true', adds the tx to tc.currBatch.L1CoordinatorTxs.
341func (tc *Context) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error {
342	// for each batch.L1CoordinatorTxs of the queues[ToForgeNum], calculate the Idx
343	for i := 0; i < len(txs); i++ {
344		tx := txs[i]
345		if tx.L1Tx.Type == common.TxTypeCreateAccountDeposit || tx.L1Tx.Type == common.TxTypeCreateAccountDepositTransfer {
346			if tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] != nil { // if account already exists, return error
347				return fmt.Errorf("Can not create same account twice (same User & same TokenID) (this is a design property of Til)")
348			}
349			tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] = &Account{
350				Idx:   common.Idx(tc.idx),
351				Nonce: common.Nonce(0),
352			}
353			tc.l1CreatedAccounts[idxTokenIDToString(tx.fromIdxName, tx.L1Tx.TokenID)] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
354			tc.idx++
355		}
356		if isCoordinatorTxs {
357			tc.currBatch.L1CoordinatorTxs = append(tc.currBatch.L1CoordinatorTxs, tx.L1Tx)
358		}
359	}
360	return nil
361}
362
363// setIdxs sets the Idxs to the transactions of the tc.currBatch
364func (tc *Context) setIdxs() error {
365	// once Idxs are calculated, update transactions to use the new Idxs
366	for i := 0; i < len(tc.currBatchTest.l2Txs); i++ {
367		testTx := &tc.currBatchTest.l2Txs[i]
368
369		if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil {
370			return fmt.Errorf("Line %d: %s from User %s for TokenID %d while account not created yet", testTx.lineNum, testTx.L2Tx.Type, testTx.fromIdxName, testTx.tokenID)
371		}
372		if testTx.L2Tx.Type == common.TxTypeTransfer {
373			if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(testTx.toIdxName, testTx.tokenID)]; !ok {
374				return fmt.Errorf("Line %d: Can not create Transfer for a non existing account. Batch %d, ToIdx name: %s, TokenID: %d", testTx.lineNum, tc.currBatchNum, testTx.toIdxName, testTx.tokenID)
375			}
376		}
377		tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce++
378		testTx.L2Tx.Nonce = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce
379
380		// set real Idx
381		testTx.L2Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Idx
382		if testTx.L2Tx.Type == common.TxTypeTransfer {
383			testTx.L2Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.tokenID].Idx
384		}
385		// in case Type==Exit, ToIdx=1, already set at the
386		// GenerateBlocks main switch inside TxTypeExit case
387
388		nTx, err := common.NewL2Tx(&testTx.L2Tx)
389		if err != nil {
390			return fmt.Errorf("Line %d: %s", testTx.lineNum, err.Error())
391		}
392		testTx.L2Tx = *nTx
393
394		tc.currBatch.L2Txs = append(tc.currBatch.L2Txs, testTx.L2Tx)
395	}
396
397	tc.currBatch.Batch.LastIdx = int64(tc.idx - 1) // `-1` because tc.idx is the next available idx
398	tc.currBlock.Batches = append(tc.currBlock.Batches, tc.currBatch)
399	tc.currBatchNum++
400	tc.currBatch = newBatchData(tc.currBatchNum)
401	tc.currBatchTest.l1CoordinatorTxs = nil
402	tc.currBatchTest.l2Txs = nil
403	return nil
404}
405
406// addToL1Queue adds the L1Tx into the queue that is open and has space
407func (tc *Context) addToL1Queue(tx L1Tx) error {
408	if len(tc.queues[tc.openToForge]) >= tc.rollupConstMaxL1UserTx {
409		// if current OpenToForge queue reached its Max, move into a
410		// new queue
411		tc.openToForge++
412		newQueue := []L1Tx{}
413		tc.queues = append(tc.queues, newQueue)
414	}
415	// Fill L1UserTx specific parameters
416	tx.L1Tx.UserOrigin = true
417	toForgeL1TxsNum := int64(tc.openToForge)
418	tx.L1Tx.ToForgeL1TxsNum = &toForgeL1TxsNum
419	tx.L1Tx.EthBlockNum = tc.blockNum
420	tx.L1Tx.Position = len(tc.queues[tc.openToForge])
421
422	// When an L1UserTx is generated, all idxs must be available (except when idx == 0 or idx == 1)
423	if tx.L1Tx.Type != common.TxTypeCreateAccountDeposit && tx.L1Tx.Type != common.TxTypeCreateAccountDepositTransfer {
424		tx.L1Tx.FromIdx = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID].Idx
425	}
426	tx.L1Tx.FromEthAddr = tc.Users[tx.fromIdxName].Addr
427	tx.L1Tx.FromBJJ = tc.Users[tx.fromIdxName].BJJ.Public()
428	if tx.toIdxName == "" {
429		tx.L1Tx.ToIdx = common.Idx(0)
430	} else {
431		account, ok := tc.Users[tx.toIdxName].Accounts[tx.L1Tx.TokenID]
432		if !ok {
433			return fmt.Errorf("Line %d: Transfer to User: %s, for TokenID: %d, "+
434				"while account not created yet", tx.lineNum, tx.toIdxName, tx.L1Tx.TokenID)
435		}
436		tx.L1Tx.ToIdx = account.Idx
437	}
438	if tx.L1Tx.Type == common.TxTypeForceExit {
439		tx.L1Tx.ToIdx = common.Idx(1)
440	}
441	nTx, err := common.NewL1Tx(&tx.L1Tx)
442	if err != nil {
443		return fmt.Errorf("Line %d: %s", tx.lineNum, err.Error())
444	}
445	tx.L1Tx = *nTx
446
447	tc.queues[tc.openToForge] = append(tc.queues[tc.openToForge], tx)
448	tc.currBlock.L1UserTxs = append(tc.currBlock.L1UserTxs, tx.L1Tx)
449
450	return nil
451}
452
453func (tc *Context) checkIfAccountExists(tf string, inst instruction) error {
454	if tc.Users[tf].Accounts[inst.tokenID] == nil {
455		return fmt.Errorf("%s at User: %s, for TokenID: %d, while account not created yet", inst.typ, tf, inst.tokenID)
456	}
457	return nil
458}
459func (tc *Context) checkIfTokenIsRegistered(inst instruction) error {
460	if inst.tokenID > tc.lastRegisteredTokenID {
461		return fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.typ, inst.tokenID, tc.lastRegisteredTokenID)
462	}
463	return nil
464}
465
466// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
467// uses the users (keys) of the Context.
468func (tc *Context) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) {
469	parser := newParser(strings.NewReader(set))
470	parsedSet, err := parser.parse()
471	if err != nil {
472		return nil, err
473	}
474	if parsedSet.typ != setTypePoolL2 {
475		return nil, fmt.Errorf("Expected set type: %s, found: %s", setTypePoolL2, parsedSet.typ)
476	}
477
478	tc.Instructions = parsedSet.instructions
479	tc.userNames = parsedSet.users
480
481	tc.generateKeys(tc.userNames)
482
483	txs := []common.PoolL2Tx{}
484	for _, inst := range tc.Instructions {
485		switch inst.typ {
486		case common.TxTypeTransfer, common.TxTypeTransferToEthAddr, common.TxTypeTransferToBJJ:
487			if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
488				log.Error(err)
489				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
490			}
491			if inst.typ == common.TxTypeTransfer {
492				// if TxTypeTransfer, need to exist the ToIdx account
493				if err := tc.checkIfAccountExists(inst.to, inst); err != nil {
494					log.Error(err)
495					return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
496				}
497			}
498			tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
499			// if account of receiver does not exist, don't use
500			// ToIdx, and use only ToEthAddr & ToBJJ
501			tx := common.PoolL2Tx{
502				FromIdx:     tc.Users[inst.from].Accounts[inst.tokenID].Idx,
503				TokenID:     inst.tokenID,
504				Amount:      big.NewInt(int64(inst.amount)),
505				Fee:         common.FeeSelector(inst.fee),
506				Nonce:       tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
507				State:       common.PoolL2TxStatePending,
508				Timestamp:   time.Now(),
509				RqToEthAddr: common.EmptyAddr,
510				RqToBJJ:     nil,
511				Type:        inst.typ,
512			}
513			if tx.Type == common.TxTypeTransfer {
514				tx.ToIdx = tc.Users[inst.to].Accounts[inst.tokenID].Idx
515				tx.ToEthAddr = tc.Users[inst.to].Addr
516				tx.ToBJJ = tc.Users[inst.to].BJJ.Public()
517			} else if tx.Type == common.TxTypeTransferToEthAddr {
518				tx.ToIdx = common.Idx(0)
519				tx.ToEthAddr = tc.Users[inst.to].Addr
520			} else if tx.Type == common.TxTypeTransferToBJJ {
521				tx.ToIdx = common.Idx(0)
522				tx.ToEthAddr = common.FFAddr
523				tx.ToBJJ = tc.Users[inst.to].BJJ.Public()
524			}
525			nTx, err := common.NewPoolL2Tx(&tx)
526			if err != nil {
527				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
528			}
529			tx = *nTx
530			// perform signature and set it to tx.Signature
531			toSign, err := tx.HashToSign()
532			if err != nil {
533				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
534			}
535			sig := tc.Users[inst.from].BJJ.SignPoseidon(toSign)
536			tx.Signature = sig.Compress()
537
538			txs = append(txs, tx)
539		case common.TxTypeExit:
540			tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
541			tx := common.PoolL2Tx{
542				FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
543				ToIdx:   common.Idx(1), // as is an Exit
544				TokenID: inst.tokenID,
545				Amount:  big.NewInt(int64(inst.amount)),
546				Nonce:   tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
547				Type:    common.TxTypeExit,
548			}
549			nTx, err := common.NewPoolL2Tx(&tx)
550			if err != nil {
551				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
552			}
553			tx = *nTx
554			// perform signature and set it to tx.Signature
555			toSign, err := tx.HashToSign()
556			if err != nil {
557				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
558			}
559			sig := tc.Users[inst.from].BJJ.SignPoseidon(toSign)
560			tx.Signature = sig.Compress()
561			txs = append(txs, tx)
562		default:
563			return nil, fmt.Errorf("Line %d: instruction type unrecognized: %s", inst.lineNum, inst.typ)
564		}
565	}
566
567	return txs, nil
568}
569
570// generateKeys generates BabyJubJub & Address keys for the given list of user
571// names in a deterministic way. This means, that for the same given
572// 'userNames' in a certain order, the keys will be always the same.
573func (tc *Context) generateKeys(userNames []string) {
574	for i := 1; i < len(userNames)+1; i++ {
575		if _, ok := tc.Users[userNames[i-1]]; ok {
576			// account already created
577			continue
578		}
579		// babyjubjub key
580		var sk babyjub.PrivateKey
581		copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
582
583		// eth address
584		var key ecdsa.PrivateKey
585		key.D = big.NewInt(int64(i)) // only for testing
586		key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
587		key.Curve = ethCrypto.S256()
588		addr := ethCrypto.PubkeyToAddress(key.PublicKey)
589
590		u := User{
591			BJJ:      &sk,
592			Addr:     addr,
593			Accounts: make(map[common.TokenID]*Account),
594		}
595		tc.Users[userNames[i-1]] = &u
596	}
597}