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