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