arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/test/transakcio/txs.go
test/transakcio/txs.goRaw
  1package transakcio
  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// TestContext contains the data of the test
 19type TestContext 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// NewTestContext returns a new TestContext
 39func NewTestContext(rollupConstMaxL1UserTx int) *TestContext {
 40	return &TestContext{
 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	CreatedAccounts []common.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// accounts (keys & nonces) of the TestContext.
113func (tc *TestContext) GenerateBlocks(set string) ([]BlockData, error) {
114	parser := newParser(strings.NewReader(set))
115	parsedSet, err := parser.parse()
116	if err != nil {
117		return nil, err
118	}
119
120	tc.Instructions = parsedSet.instructions
121	tc.accountsNames = parsedSet.accounts
122
123	tc.generateKeys(tc.accountsNames)
124
125	var blocks []BlockData
126	for _, inst := range parsedSet.instructions {
127		switch inst.typ {
128		case txTypeCreateAccountDepositCoordinator:
129			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
130				log.Error(err)
131				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
132			}
133			tx := common.L1Tx{
134				FromEthAddr: tc.Users[inst.from].Addr,
135				FromBJJ:     tc.Users[inst.from].BJJ.Public(),
136				TokenID:     inst.tokenID,
137				LoadAmount:  big.NewInt(int64(inst.loadAmount)),
138				Type:        common.TxTypeCreateAccountDeposit, // as txTypeCreateAccountDepositCoordinator is not valid oustide Transakcio package
139			}
140			testTx := L1Tx{
141				lineNum:     inst.lineNum,
142				fromIdxName: inst.from,
143				L1Tx:        tx,
144			}
145			tc.currBatch.testL1CoordinatorTxs = append(tc.currBatch.testL1CoordinatorTxs, testTx)
146		case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer:
147			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
148				log.Error(err)
149				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
150			}
151			tx := common.L1Tx{
152				FromEthAddr: tc.Users[inst.from].Addr,
153				FromBJJ:     tc.Users[inst.from].BJJ.Public(),
154				TokenID:     inst.tokenID,
155				LoadAmount:  big.NewInt(int64(inst.loadAmount)),
156				Type:        inst.typ,
157			}
158			if inst.typ == common.TxTypeCreateAccountDepositTransfer {
159				tx.Amount = big.NewInt(int64(inst.amount))
160			}
161			testTx := L1Tx{
162				lineNum:     inst.lineNum,
163				fromIdxName: inst.from,
164				toIdxName:   inst.to,
165				L1Tx:        tx,
166			}
167			tc.addToL1Queue(testTx)
168		case common.TxTypeDeposit, common.TxTypeDepositTransfer:
169			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
170				log.Error(err)
171				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
172			}
173			if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
174				log.Error(err)
175				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
176			}
177			tx := common.L1Tx{
178				TokenID:    inst.tokenID,
179				LoadAmount: big.NewInt(int64(inst.loadAmount)),
180				Type:       inst.typ,
181			}
182			if inst.typ == common.TxTypeDepositTransfer {
183				tx.Amount = big.NewInt(int64(inst.amount))
184			}
185			testTx := L1Tx{
186				lineNum:     inst.lineNum,
187				fromIdxName: inst.from,
188				toIdxName:   inst.to,
189				L1Tx:        tx,
190			}
191			tc.addToL1Queue(testTx)
192		case common.TxTypeTransfer:
193			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
194				log.Error(err)
195				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
196			}
197			tx := common.L2Tx{
198				Amount: big.NewInt(int64(inst.amount)),
199				Fee:    common.FeeSelector(inst.fee),
200				Type:   common.TxTypeTransfer,
201			}
202			nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx())
203			if err != nil {
204				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
205			}
206			tx = nTx.L2Tx()
207			tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
208			testTx := L2Tx{
209				lineNum:     inst.lineNum,
210				fromIdxName: inst.from,
211				toIdxName:   inst.to,
212				tokenID:     inst.tokenID,
213				L2Tx:        tx,
214			}
215			tc.currBatch.testL2Txs = append(tc.currBatch.testL2Txs, testTx)
216		case common.TxTypeExit:
217			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
218				log.Error(err)
219				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
220			}
221			tx := common.L2Tx{
222				ToIdx:  common.Idx(1), // as is an Exit
223				Amount: big.NewInt(int64(inst.amount)),
224				Type:   common.TxTypeExit,
225			}
226			tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
227			testTx := L2Tx{
228				lineNum:     inst.lineNum,
229				fromIdxName: inst.from,
230				toIdxName:   inst.to,
231				tokenID:     inst.tokenID,
232				L2Tx:        tx,
233			}
234			tc.currBatch.testL2Txs = append(tc.currBatch.testL2Txs, testTx)
235		case common.TxTypeForceExit:
236			if err := tc.checkIfTokenIsRegistered(inst); err != nil {
237				log.Error(err)
238				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
239			}
240			tx := common.L1Tx{
241				ToIdx:   common.Idx(1), // as is an Exit
242				TokenID: inst.tokenID,
243				Amount:  big.NewInt(int64(inst.amount)),
244				Type:    common.TxTypeExit,
245			}
246			testTx := L1Tx{
247				lineNum:     inst.lineNum,
248				fromIdxName: inst.from,
249				toIdxName:   inst.to,
250				L1Tx:        tx,
251			}
252			tc.addToL1Queue(testTx)
253		case typeNewBatch:
254			if err = tc.calculateIdxForL1Txs(true, tc.currBatch.testL1CoordinatorTxs); err != nil {
255				return nil, err
256			}
257			if err = tc.setIdxs(); err != nil {
258				log.Error(err)
259				return nil, err
260			}
261		case typeNewBatchL1:
262			// for each L1UserTx of the queues[ToForgeNum], calculate the Idx
263			if err = tc.calculateIdxForL1Txs(false, tc.queues[tc.toForgeNum]); err != nil {
264				return nil, err
265			}
266			if err = tc.calculateIdxForL1Txs(true, tc.currBatch.testL1CoordinatorTxs); err != nil {
267				return nil, err
268			}
269
270			// once Idxs are calculated, update transactions to use the new Idxs
271			for i := 0; i < len(tc.queues[tc.toForgeNum]); i++ {
272				testTx := &tc.queues[tc.toForgeNum][i]
273				// set real Idx
274				testTx.L1Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.L1Tx.TokenID].Idx
275				testTx.L1Tx.FromEthAddr = tc.Users[testTx.fromIdxName].Addr
276				testTx.L1Tx.FromBJJ = tc.Users[testTx.fromIdxName].BJJ.Public()
277				if testTx.toIdxName == "" {
278					testTx.L1Tx.ToIdx = common.Idx(0)
279				} else {
280					testTx.L1Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.L1Tx.TokenID].Idx
281				}
282				tc.currBlock.L1UserTxs = append(tc.currBlock.L1UserTxs, testTx.L1Tx)
283			}
284			if err = tc.setIdxs(); err != nil {
285				log.Error(err)
286				return nil, err
287			}
288			// advance batch
289			tc.toForgeNum++
290			if tc.toForgeNum == tc.openToForge {
291				tc.openToForge++
292				newQueue := []L1Tx{}
293				tc.queues = append(tc.queues, newQueue)
294			}
295		case typeNewBlock:
296			blocks = append(blocks, tc.currBlock)
297			tc.currBlock = BlockData{}
298		case typeRegisterToken:
299			newToken := common.Token{
300				TokenID:     inst.tokenID,
301				EthBlockNum: int64(len(blocks)),
302			}
303			if inst.tokenID != tc.lastRegisteredTokenID+1 {
304				return nil, fmt.Errorf("Line %d: RegisterToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", inst.lineNum, tc.lastRegisteredTokenID+1, inst.tokenID)
305			}
306			tc.lastRegisteredTokenID++
307			tc.currBlock.RegisteredTokens = append(tc.currBlock.RegisteredTokens, newToken)
308		default:
309			return nil, fmt.Errorf("Line %d: Unexpected type: %s", inst.lineNum, inst.typ)
310		}
311	}
312
313	return blocks, nil
314}
315
316// calculateIdxsForL1Txs calculates new Idx for new created accounts. If
317// 'isCoordinatorTxs==true', adds the tx to tc.currBatch.L1CoordinatorTxs.
318func (tc *TestContext) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error {
319	// for each batch.L1CoordinatorTxs of the queues[ToForgeNum], calculate the Idx
320	for i := 0; i < len(txs); i++ {
321		tx := txs[i]
322		if tx.L1Tx.Type == common.TxTypeCreateAccountDeposit || tx.L1Tx.Type == common.TxTypeCreateAccountDepositTransfer {
323			if tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] != nil { // if account already exists, return error
324				return fmt.Errorf("Can not create same account twice (same User & same TokenID) (this is a design property of Transakcio)")
325			}
326			tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] = &Account{
327				Idx:   common.Idx(tc.idx),
328				Nonce: common.Nonce(0),
329			}
330			tc.l1CreatedAccounts[idxTokenIDToString(tx.fromIdxName, tx.L1Tx.TokenID)] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
331			tc.idx++
332		}
333		if isCoordinatorTxs {
334			tc.currBatch.L1CoordinatorTxs = append(tc.currBatch.L1CoordinatorTxs, tx.L1Tx)
335		}
336	}
337	return nil
338}
339
340// setIdxs sets the Idxs to the transactions of the tc.currBatch
341func (tc *TestContext) setIdxs() error {
342	// once Idxs are calculated, update transactions to use the new Idxs
343	for i := 0; i < len(tc.currBatch.testL2Txs); i++ {
344		testTx := &tc.currBatch.testL2Txs[i]
345
346		if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil {
347			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)
348		}
349		if testTx.L2Tx.Type == common.TxTypeTransfer {
350			if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(testTx.toIdxName, testTx.tokenID)]; !ok {
351				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)
352			}
353		}
354		tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce++
355		testTx.L2Tx.Nonce = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce
356
357		// set real Idx
358		testTx.L2Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Idx
359		if testTx.L2Tx.Type == common.TxTypeTransfer {
360			testTx.L2Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.tokenID].Idx
361		}
362		nTx, err := common.NewL2Tx(&testTx.L2Tx)
363		if err != nil {
364			return err
365		}
366		testTx.L2Tx = *nTx
367
368		tc.currBatch.L2Txs = append(tc.currBatch.L2Txs, testTx.L2Tx)
369	}
370
371	tc.currBlock.Batches = append(tc.currBlock.Batches, tc.currBatch)
372	tc.currBatchNum++
373	tc.currBatch = BatchData{}
374
375	return nil
376}
377
378// addToL1Queue adds the L1Tx into the queue that is open and has space
379func (tc *TestContext) addToL1Queue(tx L1Tx) {
380	if len(tc.queues[tc.openToForge]) >= tc.rollupConstMaxL1UserTx {
381		// if current OpenToForge queue reached its Max, move into a
382		// new queue
383		tc.openToForge++
384		newQueue := []L1Tx{}
385		tc.queues = append(tc.queues, newQueue)
386	}
387	tc.queues[tc.openToForge] = append(tc.queues[tc.openToForge], tx)
388}
389
390func (tc *TestContext) checkIfAccountExists(tf string, inst instruction) error {
391	if tc.Users[tf].Accounts[inst.tokenID] == nil {
392		return fmt.Errorf("%s at User: %s, for TokenID: %d, while account not created yet", inst.typ, tf, inst.tokenID)
393	}
394	return nil
395}
396func (tc *TestContext) checkIfTokenIsRegistered(inst instruction) error {
397	if inst.tokenID > tc.lastRegisteredTokenID {
398		return fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.typ, inst.tokenID, tc.lastRegisteredTokenID)
399	}
400	return nil
401}
402
403// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
404// uses the accounts (keys & nonces) of the TestContext.
405func (tc *TestContext) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) {
406	parser := newParser(strings.NewReader(set))
407	parsedSet, err := parser.parse()
408	if err != nil {
409		return nil, err
410	}
411
412	tc.Instructions = parsedSet.instructions
413	tc.accountsNames = parsedSet.accounts
414
415	tc.generateKeys(tc.accountsNames)
416
417	txs := []common.PoolL2Tx{}
418	for _, inst := range tc.Instructions {
419		switch inst.typ {
420		case common.TxTypeTransfer:
421			if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
422				log.Error(err)
423				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
424			}
425			if err := tc.checkIfAccountExists(inst.to, inst); err != nil {
426				log.Error(err)
427				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
428			}
429			tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
430			// if account of receiver does not exist, don't use
431			// ToIdx, and use only ToEthAddr & ToBJJ
432			tx := common.PoolL2Tx{
433				FromIdx:     tc.Users[inst.from].Accounts[inst.tokenID].Idx,
434				ToIdx:       tc.Users[inst.to].Accounts[inst.tokenID].Idx,
435				ToEthAddr:   tc.Users[inst.to].Addr,
436				ToBJJ:       tc.Users[inst.to].BJJ.Public(),
437				TokenID:     inst.tokenID,
438				Amount:      big.NewInt(int64(inst.amount)),
439				Fee:         common.FeeSelector(inst.fee),
440				Nonce:       tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
441				State:       common.PoolL2TxStatePending,
442				Timestamp:   time.Now(),
443				RqToEthAddr: common.EmptyAddr,
444				RqToBJJ:     nil,
445				Type:        common.TxTypeTransfer,
446			}
447			nTx, err := common.NewPoolL2Tx(&tx)
448			if err != nil {
449				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
450			}
451			tx = *nTx
452			// perform signature and set it to tx.Signature
453			toSign, err := tx.HashToSign()
454			if err != nil {
455				return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
456			}
457			sig := tc.Users[inst.to].BJJ.SignPoseidon(toSign)
458			tx.Signature = sig
459
460			txs = append(txs, tx)
461		case common.TxTypeExit:
462			tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
463			tx := common.PoolL2Tx{
464				FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
465				ToIdx:   common.Idx(1), // as is an Exit
466				TokenID: inst.tokenID,
467				Amount:  big.NewInt(int64(inst.amount)),
468				Nonce:   tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
469				Type:    common.TxTypeExit,
470			}
471			txs = append(txs, tx)
472		default:
473			return nil, fmt.Errorf("Line %d: instruction type unrecognized: %s", inst.lineNum, inst.typ)
474		}
475	}
476
477	return txs, nil
478}
479
480// generateKeys generates BabyJubJub & Address keys for the given list of
481// account names in a deterministic way. This means, that for the same given
482// 'accNames' in a certain order, the keys will be always the same.
483func (tc *TestContext) generateKeys(accNames []string) {
484	for i := 1; i < len(accNames)+1; i++ {
485		if _, ok := tc.Users[accNames[i-1]]; ok {
486			// account already created
487			continue
488		}
489		// babyjubjub key
490		var sk babyjub.PrivateKey
491		copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
492
493		// eth address
494		var key ecdsa.PrivateKey
495		key.D = big.NewInt(int64(i)) // only for testing
496		key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
497		key.Curve = ethCrypto.S256()
498		addr := ethCrypto.PubkeyToAddress(key.PublicKey)
499
500		u := User{
501			BJJ:      &sk,
502			Addr:     addr,
503			Accounts: make(map[common.TokenID]*Account),
504		}
505		tc.Users[accNames[i-1]] = &u
506	}
507}