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 FeeIdxsCoordinator: make([]common.Idx, 0),
26 CollectedFees: make(map[common.TokenID]*big.Int),
27 },
28 }
29}
30
31func newBlock(blockNum int64) common.BlockData {
32 return common.BlockData{
33 Block: common.Block{
34 EthBlockNum: blockNum,
35 },
36 Rollup: common.RollupData{
37 L1UserTxs: []common.L1Tx{},
38 },
39 }
40}
41
42type contextExtra struct {
43 openToForge int64
44 toForgeL1TxsNum int64
45 nonces map[common.Idx]common.Nonce
46 idx int
47}
48
49// Context contains the data of the test
50type Context struct {
51 Instructions []instruction
52 userNames []string
53 Users map[string]*User // Name -> *User
54 UsersByIdx map[int]*User
55 accountsByIdx map[int]*Account
56 LastRegisteredTokenID common.TokenID
57 l1CreatedAccounts map[string]*Account // (Name, TokenID) -> *Account
58
59 // rollupConstMaxL1UserTx Maximum L1-user transactions allowed to be queued in a batch
60 rollupConstMaxL1UserTx int
61
62 idx int
63 currBlock common.BlockData
64 currBatch common.BatchData
65 currBatchNum int
66 Queues [][]L1Tx
67 ToForgeNum int
68 openToForge int
69 currBatchTest struct {
70 l1CoordinatorTxs []L1Tx
71 l2Txs []L2Tx
72 }
73 blockNum int64
74
75 extra contextExtra
76}
77
78// NewContext returns a new Context
79func NewContext(rollupConstMaxL1UserTx int) *Context {
80 currBatchNum := 1 // The protocol defines the first batchNum to be 1
81 return &Context{
82 Users: make(map[string]*User),
83 l1CreatedAccounts: make(map[string]*Account),
84 UsersByIdx: make(map[int]*User),
85 accountsByIdx: make(map[int]*Account),
86 LastRegisteredTokenID: 0,
87
88 rollupConstMaxL1UserTx: rollupConstMaxL1UserTx,
89 idx: common.UserThreshold,
90 // We use some placeholder values for StateRoot and ExitTree
91 // because these values will never be nil
92 currBlock: newBlock(2), //nolint:gomnd
93 currBatch: newBatchData(currBatchNum),
94 currBatchNum: currBatchNum,
95 // start with 2 queues, one for toForge, and the other for openToForge
96 Queues: make([][]L1Tx, 2),
97 ToForgeNum: 0,
98 openToForge: 1,
99 //nolint:gomnd
100 blockNum: 2, // rollup genesis blockNum
101 extra: contextExtra{
102 openToForge: 0,
103 toForgeL1TxsNum: 0,
104 nonces: make(map[common.Idx]common.Nonce),
105 idx: common.UserThreshold,
106 },
107 }
108}
109
110// Account contains the data related to the account for a specific TokenID of a User
111type Account struct {
112 Idx common.Idx
113 TokenID common.TokenID
114 Nonce common.Nonce
115 BatchNum int
116}
117
118// User contains the data related to a testing user
119type User struct {
120 Name string
121 BJJ *babyjub.PrivateKey
122 Addr ethCommon.Address
123 Accounts map[common.TokenID]*Account
124}
125
126// L1Tx is the data structure used internally for transaction test generation,
127// which contains a common.L1Tx data plus some intermediate data for the
128// transaction generation.
129type L1Tx struct {
130 lineNum int
131 fromIdxName string
132 toIdxName string
133
134 L1Tx common.L1Tx
135}
136
137// L2Tx is the data structure used internally for transaction test generation,
138// which contains a common.L2Tx data plus some intermediate data for the
139// transaction generation.
140type L2Tx struct {
141 lineNum int
142 fromIdxName string
143 toIdxName string
144 tokenID common.TokenID
145 L2Tx common.L2Tx
146}
147
148// GenerateBlocks returns an array of BlockData for a given set. It uses the
149// users (keys & nonces) of the Context.
150func (tc *Context) GenerateBlocks(set string) ([]common.BlockData, error) {
151 parser := newParser(strings.NewReader(set))
152 parsedSet, err := parser.parse()
153 if err != nil {
154 return nil, err
155 }
156 if parsedSet.typ != setTypeBlockchain {
157 return nil, fmt.Errorf("Expected set type: %s, found: %s", setTypeBlockchain, parsedSet.typ)
158 }
159
160 tc.Instructions = parsedSet.instructions
161 tc.userNames = parsedSet.users
162
163 tc.generateKeys(tc.userNames)
164
165 var blocks []common.BlockData
166 for _, inst := range parsedSet.instructions {
167 switch inst.typ {
168 case txTypeCreateAccountDepositCoordinator: // tx source: L1CoordinatorTx
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 tx := common.L1Tx{
174 FromEthAddr: tc.Users[inst.from].Addr,
175 FromBJJ: tc.Users[inst.from].BJJ.Public(),
176 TokenID: inst.tokenID,
177 Amount: big.NewInt(0),
178 LoadAmount: big.NewInt(0),
179 Type: common.TxTypeCreateAccountDeposit, // as txTypeCreateAccountDepositCoordinator is not valid oustide Til package
180 }
181 testTx := L1Tx{
182 lineNum: inst.lineNum,
183 fromIdxName: inst.from,
184 L1Tx: tx,
185 }
186
187 tc.currBatchTest.l1CoordinatorTxs = append(tc.currBatchTest.l1CoordinatorTxs, testTx)
188 case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer: // tx source: L1UserTx
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.L1Tx{
194 FromEthAddr: tc.Users[inst.from].Addr,
195 FromBJJ: tc.Users[inst.from].BJJ.Public(),
196 TokenID: inst.tokenID,
197 Amount: big.NewInt(0),
198 LoadAmount: big.NewInt(int64(inst.loadAmount)),
199 Type: inst.typ,
200 }
201 if inst.typ == common.TxTypeCreateAccountDepositTransfer {
202 tx.Amount = big.NewInt(int64(inst.amount))
203 }
204 testTx := L1Tx{
205 lineNum: inst.lineNum,
206 fromIdxName: inst.from,
207 toIdxName: inst.to,
208 L1Tx: tx,
209 }
210 if err := tc.addToL1Queue(testTx); err != nil {
211 return nil, err
212 }
213 case common.TxTypeDeposit, common.TxTypeDepositTransfer: // tx source: L1UserTx
214 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
215 log.Error(err)
216 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
217 }
218 if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
219 log.Error(err)
220 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
221 }
222 tx := common.L1Tx{
223 TokenID: inst.tokenID,
224 Amount: big.NewInt(0),
225 LoadAmount: big.NewInt(int64(inst.loadAmount)),
226 Type: inst.typ,
227 }
228 if inst.typ == common.TxTypeDepositTransfer {
229 tx.Amount = big.NewInt(int64(inst.amount))
230 }
231 testTx := L1Tx{
232 lineNum: inst.lineNum,
233 fromIdxName: inst.from,
234 toIdxName: inst.to,
235 L1Tx: tx,
236 }
237 if err := tc.addToL1Queue(testTx); err != nil {
238 return nil, err
239 }
240 case common.TxTypeTransfer: // L2Tx
241 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
242 log.Error(err)
243 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
244 }
245 tx := common.L2Tx{
246 Amount: big.NewInt(int64(inst.amount)),
247 Fee: common.FeeSelector(inst.fee),
248 Type: common.TxTypeTransfer,
249 EthBlockNum: tc.blockNum,
250 }
251 tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
252 testTx := L2Tx{
253 lineNum: inst.lineNum,
254 fromIdxName: inst.from,
255 toIdxName: inst.to,
256 tokenID: inst.tokenID,
257 L2Tx: tx,
258 }
259 tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
260 case common.TxTypeForceTransfer: // tx source: L1UserTx
261 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
262 log.Error(err)
263 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
264 }
265 tx := common.L1Tx{
266 TokenID: inst.tokenID,
267 Amount: big.NewInt(int64(inst.amount)),
268 LoadAmount: big.NewInt(0),
269 Type: common.TxTypeForceTransfer,
270 }
271 testTx := L1Tx{
272 lineNum: inst.lineNum,
273 fromIdxName: inst.from,
274 toIdxName: inst.to,
275 L1Tx: tx,
276 }
277 if err := tc.addToL1Queue(testTx); err != nil {
278 return nil, err
279 }
280 case common.TxTypeExit: // tx source: L2Tx
281 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
282 log.Error(err)
283 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
284 }
285 tx := common.L2Tx{
286 ToIdx: common.Idx(1), // as is an Exit
287 Fee: common.FeeSelector(inst.fee),
288 Amount: big.NewInt(int64(inst.amount)),
289 Type: common.TxTypeExit,
290 EthBlockNum: tc.blockNum,
291 }
292 tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
293 testTx := L2Tx{
294 lineNum: inst.lineNum,
295 fromIdxName: inst.from,
296 toIdxName: inst.to,
297 tokenID: inst.tokenID,
298 L2Tx: tx,
299 }
300 tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
301 case common.TxTypeForceExit: // tx source: L1UserTx
302 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
303 log.Error(err)
304 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
305 }
306 tx := common.L1Tx{
307 ToIdx: common.Idx(1), // as is an Exit
308 TokenID: inst.tokenID,
309 Amount: big.NewInt(int64(inst.amount)),
310 LoadAmount: big.NewInt(0),
311 Type: common.TxTypeForceExit,
312 }
313 testTx := L1Tx{
314 lineNum: inst.lineNum,
315 fromIdxName: inst.from,
316 toIdxName: inst.to,
317 L1Tx: tx,
318 }
319 if err := tc.addToL1Queue(testTx); err != nil {
320 return nil, err
321 }
322 case typeNewBatch:
323 if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
324 return nil, err
325 }
326 if err = tc.setIdxs(); err != nil {
327 log.Error(err)
328 return nil, err
329 }
330 case typeNewBatchL1:
331 // for each L1UserTx of the Queues[ToForgeNum], calculate the Idx
332 if err = tc.calculateIdxForL1Txs(false, tc.Queues[tc.ToForgeNum]); err != nil {
333 return nil, err
334 }
335 if err = tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
336 return nil, err
337 }
338 tc.currBatch.L1Batch = true
339 if err = tc.setIdxs(); err != nil {
340 log.Error(err)
341 return nil, err
342 }
343 toForgeL1TxsNum := int64(tc.openToForge)
344 tc.currBatch.Batch.ForgeL1TxsNum = &toForgeL1TxsNum
345 // advance batch
346 tc.ToForgeNum++
347 if tc.ToForgeNum == tc.openToForge {
348 tc.openToForge++
349 newQueue := []L1Tx{}
350 tc.Queues = append(tc.Queues, newQueue)
351 }
352 case typeNewBlock:
353 blocks = append(blocks, tc.currBlock)
354 tc.blockNum++
355 tc.currBlock = newBlock(tc.blockNum)
356 case typeAddToken:
357 newToken := common.Token{
358 EthAddr: ethCommon.BigToAddress(big.NewInt(int64(inst.tokenID * 100))), //nolint:gomnd
359 // Name: fmt.Sprintf("Token %d", inst.tokenID),
360 // Symbol: fmt.Sprintf("TK%d", inst.tokenID),
361 // Decimals: 18,
362 TokenID: inst.tokenID,
363 EthBlockNum: tc.blockNum,
364 }
365 if inst.tokenID != tc.LastRegisteredTokenID+1 {
366 return nil, fmt.Errorf("Line %d: AddToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", inst.lineNum, tc.LastRegisteredTokenID+1, inst.tokenID)
367 }
368 tc.LastRegisteredTokenID++
369 tc.currBlock.Rollup.AddedTokens = append(tc.currBlock.Rollup.AddedTokens, newToken)
370 default:
371 return nil, fmt.Errorf("Line %d: Unexpected type: %s", inst.lineNum, inst.typ)
372 }
373 }
374
375 return blocks, nil
376}
377
378// calculateIdxsForL1Txs calculates new Idx for new created accounts. If
379// 'isCoordinatorTxs==true', adds the tx to tc.currBatch.L1CoordinatorTxs.
380func (tc *Context) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error {
381 // for each batch.L1CoordinatorTxs of the Queues[ToForgeNum], calculate the Idx
382 for i := 0; i < len(txs); i++ {
383 tx := txs[i]
384 if tx.L1Tx.Type == common.TxTypeCreateAccountDeposit || tx.L1Tx.Type == common.TxTypeCreateAccountDepositTransfer {
385 if tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] != nil { // if account already exists, return error
386 return fmt.Errorf("Can not create same account twice (same User (%s) & same TokenID (%d)) (this is a design property of Til)", tx.fromIdxName, tx.L1Tx.TokenID)
387 }
388 tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] = &Account{
389 Idx: common.Idx(tc.idx),
390 TokenID: tx.L1Tx.TokenID,
391 Nonce: common.Nonce(0),
392 BatchNum: tc.currBatchNum,
393 }
394 tc.l1CreatedAccounts[idxTokenIDToString(tx.fromIdxName, tx.L1Tx.TokenID)] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
395 tc.accountsByIdx[tc.idx] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
396 tc.UsersByIdx[tc.idx] = tc.Users[tx.fromIdxName]
397 tc.idx++
398 }
399 if isCoordinatorTxs {
400 tc.currBatch.L1CoordinatorTxs = append(tc.currBatch.L1CoordinatorTxs, tx.L1Tx)
401 }
402 }
403 return nil
404}
405
406// setIdxs sets the Idxs to the transactions of the tc.currBatch
407func (tc *Context) setIdxs() error {
408 // once Idxs are calculated, update transactions to use the new Idxs
409 for i := 0; i < len(tc.currBatchTest.l2Txs); i++ {
410 testTx := &tc.currBatchTest.l2Txs[i]
411
412 if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil {
413 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)
414 }
415 if testTx.L2Tx.Type == common.TxTypeTransfer {
416 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(testTx.toIdxName, testTx.tokenID)]; !ok {
417 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)
418 }
419 }
420 tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce++
421 // next line is commented to avoid Blockchain L2Txs to have
422 // Nonce different from 0, as from Blockchain those
423 // transactions will come without Nonce
424 // testTx.L2Tx.Nonce = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce
425
426 // set real Idx
427 testTx.L2Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Idx
428 if testTx.L2Tx.Type == common.TxTypeTransfer {
429 testTx.L2Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.tokenID].Idx
430 }
431 // in case Type==Exit, ToIdx=1, already set at the
432 // GenerateBlocks main switch inside TxTypeExit case
433
434 nTx, err := common.NewL2Tx(&testTx.L2Tx)
435 if err != nil {
436 return fmt.Errorf("Line %d: %s", testTx.lineNum, err.Error())
437 }
438 testTx.L2Tx = *nTx
439
440 tc.currBatch.L2Txs = append(tc.currBatch.L2Txs, testTx.L2Tx)
441 }
442
443 tc.currBatch.Batch.LastIdx = int64(tc.idx - 1) // `-1` because tc.idx is the next available idx
444 tc.currBlock.Rollup.Batches = append(tc.currBlock.Rollup.Batches, tc.currBatch)
445 tc.currBatchNum++
446 tc.currBatch = newBatchData(tc.currBatchNum)
447 tc.currBatchTest.l1CoordinatorTxs = nil
448 tc.currBatchTest.l2Txs = nil
449 return nil
450}
451
452// addToL1Queue adds the L1Tx into the queue that is open and has space
453func (tc *Context) addToL1Queue(tx L1Tx) error {
454 if len(tc.Queues[tc.openToForge]) >= tc.rollupConstMaxL1UserTx {
455 // if current OpenToForge queue reached its Max, move into a
456 // new queue
457 tc.openToForge++
458 newQueue := []L1Tx{}
459 tc.Queues = append(tc.Queues, newQueue)
460 }
461 // Fill L1UserTx specific parameters
462 tx.L1Tx.UserOrigin = true
463 toForgeL1TxsNum := int64(tc.openToForge)
464 tx.L1Tx.ToForgeL1TxsNum = &toForgeL1TxsNum
465 tx.L1Tx.EthBlockNum = tc.blockNum
466 tx.L1Tx.Position = len(tc.Queues[tc.openToForge])
467
468 // When an L1UserTx is generated, all idxs must be available (except when idx == 0 or idx == 1)
469 if tx.L1Tx.Type != common.TxTypeCreateAccountDeposit && tx.L1Tx.Type != common.TxTypeCreateAccountDepositTransfer {
470 tx.L1Tx.FromIdx = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID].Idx
471 }
472 tx.L1Tx.FromEthAddr = tc.Users[tx.fromIdxName].Addr
473 tx.L1Tx.FromBJJ = tc.Users[tx.fromIdxName].BJJ.Public()
474 if tx.toIdxName == "" {
475 tx.L1Tx.ToIdx = common.Idx(0)
476 } else {
477 account, ok := tc.Users[tx.toIdxName].Accounts[tx.L1Tx.TokenID]
478 if !ok {
479 return fmt.Errorf("Line %d: Transfer to User: %s, for TokenID: %d, "+
480 "while account not created yet", tx.lineNum, tx.toIdxName, tx.L1Tx.TokenID)
481 }
482 tx.L1Tx.ToIdx = account.Idx
483 }
484 if tx.L1Tx.Type == common.TxTypeForceExit {
485 tx.L1Tx.ToIdx = common.Idx(1)
486 }
487 nTx, err := common.NewL1Tx(&tx.L1Tx)
488 if err != nil {
489 return fmt.Errorf("Line %d: %s", tx.lineNum, err.Error())
490 }
491 tx.L1Tx = *nTx
492
493 tc.Queues[tc.openToForge] = append(tc.Queues[tc.openToForge], tx)
494 tc.currBlock.Rollup.L1UserTxs = append(tc.currBlock.Rollup.L1UserTxs, tx.L1Tx)
495
496 return nil
497}
498
499func (tc *Context) checkIfAccountExists(tf string, inst instruction) error {
500 if tc.Users[tf].Accounts[inst.tokenID] == nil {
501 return fmt.Errorf("%s at User: %s, for TokenID: %d, while account not created yet", inst.typ, tf, inst.tokenID)
502 }
503 return nil
504}
505func (tc *Context) checkIfTokenIsRegistered(inst instruction) error {
506 if inst.tokenID > tc.LastRegisteredTokenID {
507 return fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.typ, inst.tokenID, tc.LastRegisteredTokenID)
508 }
509 return nil
510}
511
512// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set. It
513// uses the users (keys) of the Context.
514func (tc *Context) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) {
515 parser := newParser(strings.NewReader(set))
516 parsedSet, err := parser.parse()
517 if err != nil {
518 return nil, err
519 }
520 if parsedSet.typ != setTypePoolL2 {
521 return nil, fmt.Errorf("Expected set type: %s, found: %s", setTypePoolL2, parsedSet.typ)
522 }
523
524 tc.Instructions = parsedSet.instructions
525 tc.userNames = parsedSet.users
526
527 tc.generateKeys(tc.userNames)
528
529 txs := []common.PoolL2Tx{}
530 for _, inst := range tc.Instructions {
531 switch inst.typ {
532 case common.TxTypeTransfer, common.TxTypeTransferToEthAddr, common.TxTypeTransferToBJJ:
533 if err := tc.checkIfAccountExists(inst.from, inst); err != nil {
534 log.Error(err)
535 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
536 }
537 if inst.typ == common.TxTypeTransfer {
538 // if TxTypeTransfer, need to exist the ToIdx account
539 if err := tc.checkIfAccountExists(inst.to, inst); err != nil {
540 log.Error(err)
541 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
542 }
543 }
544 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
545 // if account of receiver does not exist, don't use
546 // ToIdx, and use only ToEthAddr & ToBJJ
547 tx := common.PoolL2Tx{
548 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
549 TokenID: inst.tokenID,
550 Amount: big.NewInt(int64(inst.amount)),
551 Fee: common.FeeSelector(inst.fee),
552 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
553 State: common.PoolL2TxStatePending,
554 Timestamp: time.Now(),
555 RqToEthAddr: common.EmptyAddr,
556 RqToBJJ: nil,
557 Type: inst.typ,
558 }
559 if tx.Type == common.TxTypeTransfer {
560 tx.ToIdx = tc.Users[inst.to].Accounts[inst.tokenID].Idx
561 tx.ToEthAddr = tc.Users[inst.to].Addr
562 tx.ToBJJ = tc.Users[inst.to].BJJ.Public()
563 } else if tx.Type == common.TxTypeTransferToEthAddr {
564 tx.ToIdx = common.Idx(0)
565 tx.ToEthAddr = tc.Users[inst.to].Addr
566 } else if tx.Type == common.TxTypeTransferToBJJ {
567 tx.ToIdx = common.Idx(0)
568 tx.ToEthAddr = common.FFAddr
569 tx.ToBJJ = tc.Users[inst.to].BJJ.Public()
570 }
571 nTx, err := common.NewPoolL2Tx(&tx)
572 if err != nil {
573 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
574 }
575 tx = *nTx
576 // perform signature and set it to tx.Signature
577 toSign, err := tx.HashToSign()
578 if err != nil {
579 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
580 }
581 sig := tc.Users[inst.from].BJJ.SignPoseidon(toSign)
582 tx.Signature = sig.Compress()
583
584 txs = append(txs, tx)
585 case common.TxTypeExit:
586 tc.Users[inst.from].Accounts[inst.tokenID].Nonce++
587 tx := common.PoolL2Tx{
588 FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx,
589 ToIdx: common.Idx(1), // as is an Exit
590 Fee: common.FeeSelector(inst.fee),
591 TokenID: inst.tokenID,
592 Amount: big.NewInt(int64(inst.amount)),
593 Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce,
594 State: common.PoolL2TxStatePending,
595 Type: common.TxTypeExit,
596 }
597 nTx, err := common.NewPoolL2Tx(&tx)
598 if err != nil {
599 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
600 }
601 tx = *nTx
602 // perform signature and set it to tx.Signature
603 toSign, err := tx.HashToSign()
604 if err != nil {
605 return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error())
606 }
607 sig := tc.Users[inst.from].BJJ.SignPoseidon(toSign)
608 tx.Signature = sig.Compress()
609 txs = append(txs, tx)
610 default:
611 return nil, fmt.Errorf("Line %d: instruction type unrecognized: %s", inst.lineNum, inst.typ)
612 }
613 }
614
615 return txs, nil
616}
617
618// generateKeys generates BabyJubJub & Address keys for the given list of user
619// names in a deterministic way. This means, that for the same given
620// 'userNames' in a certain order, the keys will be always the same.
621func (tc *Context) generateKeys(userNames []string) {
622 for i := 1; i < len(userNames)+1; i++ {
623 if _, ok := tc.Users[userNames[i-1]]; ok {
624 // account already created
625 continue
626 }
627 // babyjubjub key
628 var sk babyjub.PrivateKey
629 copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
630
631 // eth address
632 var key ecdsa.PrivateKey
633 key.D = big.NewInt(int64(i)) // only for testing
634 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
635 key.Curve = ethCrypto.S256()
636 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
637
638 u := User{
639 Name: userNames[i-1],
640 BJJ: &sk,
641 Addr: addr,
642 Accounts: make(map[common.TokenID]*Account),
643 }
644 tc.Users[userNames[i-1]] = &u
645 }
646}
647
648// L1TxsToCommonL1Txs converts an array of []til.L1Tx to []common.L1Tx
649func L1TxsToCommonL1Txs(l1 []L1Tx) []common.L1Tx {
650 var r []common.L1Tx
651 for i := 0; i < len(l1); i++ {
652 r = append(r, l1[i].L1Tx)
653 }
654 return r
655}
656
657// ConfigExtra is the configuration used in FillBlocksExtra to extend the
658// blocks returned by til.
659type ConfigExtra struct {
660 // Address to set as forger for each batch
661 BootCoordAddr ethCommon.Address
662 // Coordinator user name used to select the corresponding accounts to
663 // collect coordinator fees
664 CoordUser string
665}
666
667// FillBlocksL1UserTxsBatchNum fills the BatchNum of forged L1UserTxs:
668// - blocks[].Rollup.L1UserTxs[].BatchNum
669func (tc *Context) FillBlocksL1UserTxsBatchNum(blocks []common.BlockData) {
670 for i := range blocks {
671 block := &blocks[i]
672 for j := range block.Rollup.Batches {
673 batch := &block.Rollup.Batches[j]
674 if batch.L1Batch {
675 // Set BatchNum for forged L1UserTxs to til blocks
676 bn := batch.Batch.BatchNum
677 for k := range blocks {
678 block := &blocks[k]
679 for l := range block.Rollup.L1UserTxs {
680 tx := &block.Rollup.L1UserTxs[l]
681 if *tx.ToForgeL1TxsNum == tc.extra.openToForge {
682 tx.BatchNum = &bn
683 }
684 }
685 }
686 tc.extra.openToForge++
687 }
688 }
689 }
690}
691
692// FillBlocksExtra fills extra fields not generated by til in each block, so
693// that the blockData is closer to what the HistoryDB stores. The filled fields are:
694// - blocks[].Rollup.Batch.EthBlockNum
695// - blocks[].Rollup.Batch.ForgerAddr
696// - blocks[].Rollup.Batch.ForgeL1TxsNum
697// - blocks[].Rollup.Batch.L1CoordinatorTxs[].TxID
698// - blocks[].Rollup.Batch.L1CoordinatorTxs[].BatchNum
699// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EthBlockNum
700// - blocks[].Rollup.Batch.L1CoordinatorTxs[].Position
701// - blocks[].Rollup.Batch.L2Txs[].TxID
702// - blocks[].Rollup.Batch.L2Txs[].Position
703// - blocks[].Rollup.Batch.L2Txs[].Nonce
704// - blocks[].Rollup.Batch.ExitTree
705// - blocks[].Rollup.Batch.CreatedAccounts
706// - blocks[].Rollup.Batch.FeeIdxCoordinator
707// - blocks[].Rollup.Batch.CollectedFees
708func (tc *Context) FillBlocksExtra(blocks []common.BlockData, cfg *ConfigExtra) error {
709 // Fill extra fields not generated by til in til block
710 for i := range blocks {
711 block := &blocks[i]
712 for j := range block.Rollup.Batches {
713 batch := &block.Rollup.Batches[j]
714 batch.Batch.EthBlockNum = block.Block.EthBlockNum
715 // til doesn't fill the batch forger addr
716 batch.Batch.ForgerAddr = cfg.BootCoordAddr
717 if batch.L1Batch {
718 toForgeL1TxsNumCpy := tc.extra.toForgeL1TxsNum
719 // til doesn't fill the ForgeL1TxsNum
720 batch.Batch.ForgeL1TxsNum = &toForgeL1TxsNumCpy
721 tc.extra.toForgeL1TxsNum++
722 }
723
724 batchNum := batch.Batch.BatchNum
725 for k := range batch.L1CoordinatorTxs {
726 tx := &batch.L1CoordinatorTxs[k]
727 tx.BatchNum = &batchNum
728 tx.EthBlockNum = batch.Batch.EthBlockNum
729 }
730 }
731 }
732
733 // Fill CreatedAccounts
734 for i := range blocks {
735 block := &blocks[i]
736 for j := range block.Rollup.Batches {
737 batch := &block.Rollup.Batches[j]
738 l1Txs := []common.L1Tx{}
739 if batch.L1Batch {
740 for _, tx := range tc.Queues[*batch.Batch.ForgeL1TxsNum] {
741 l1Txs = append(l1Txs, tx.L1Tx)
742 }
743 }
744 l1Txs = append(l1Txs, batch.L1CoordinatorTxs...)
745 for k := range l1Txs {
746 tx := &l1Txs[k]
747 if tx.Type == common.TxTypeCreateAccountDeposit ||
748 tx.Type == common.TxTypeCreateAccountDepositTransfer {
749 user, ok := tc.UsersByIdx[tc.extra.idx]
750 if !ok {
751 return fmt.Errorf("Created account with idx: %v not found", tc.extra.idx)
752 }
753 batch.CreatedAccounts = append(batch.CreatedAccounts,
754 common.Account{
755 Idx: common.Idx(tc.extra.idx),
756 TokenID: tx.TokenID,
757 BatchNum: batch.Batch.BatchNum,
758 PublicKey: user.BJJ.Public(),
759 EthAddr: user.Addr,
760 Nonce: 0,
761 Balance: big.NewInt(0),
762 })
763 tc.extra.idx++
764 }
765 }
766 }
767 }
768
769 // Fill expected positions in L1CoordinatorTxs and L2Txs
770 for i := range blocks {
771 block := &blocks[i]
772 for j := range block.Rollup.Batches {
773 batch := &block.Rollup.Batches[j]
774 position := 0
775 if batch.L1Batch {
776 position = len(tc.Queues[*batch.Batch.ForgeL1TxsNum])
777 }
778 for k := range batch.L1CoordinatorTxs {
779 tx := &batch.L1CoordinatorTxs[k]
780 tx.Position = position
781 position++
782 nTx, err := common.NewL1Tx(tx)
783 if err != nil {
784 return err
785 }
786 *tx = *nTx
787 }
788 for k := range batch.L2Txs {
789 tx := &batch.L2Txs[k]
790 tx.Position = position
791 position++
792 tc.extra.nonces[tx.FromIdx]++
793 tx.Nonce = tc.extra.nonces[tx.FromIdx]
794 nTx, err := common.NewL2Tx(tx)
795 if err != nil {
796 return err
797 }
798 *tx = *nTx
799 }
800 }
801 }
802
803 // Fill ExitTree (only AccountIdx and Balance)
804 for i := range blocks {
805 block := &blocks[i]
806 for j := range block.Rollup.Batches {
807 batch := &block.Rollup.Batches[j]
808 if batch.L1Batch {
809 for _, _tx := range tc.Queues[*batch.Batch.ForgeL1TxsNum] {
810 tx := _tx.L1Tx
811 if tx.Type == common.TxTypeForceExit {
812 batch.ExitTree =
813 append(batch.ExitTree,
814 common.ExitInfo{
815 BatchNum: batch.Batch.BatchNum,
816 AccountIdx: tx.FromIdx,
817 Balance: tx.Amount,
818 })
819 }
820 }
821 }
822 for k := range batch.L2Txs {
823 tx := &batch.L2Txs[k]
824 if tx.Type == common.TxTypeExit {
825 batch.ExitTree = append(batch.ExitTree, common.ExitInfo{
826 BatchNum: batch.Batch.BatchNum,
827 AccountIdx: tx.FromIdx,
828 Balance: tx.Amount,
829 })
830 }
831 fee, err := common.CalcFeeAmount(tx.Amount, tx.Fee)
832 if err != nil {
833 return err
834 }
835
836 // Find the TokenID of the tx
837 fromAcc, ok := tc.accountsByIdx[int(tx.FromIdx)]
838 if !ok {
839 return fmt.Errorf("L2tx.FromIdx idx: %v not found", tx.FromIdx)
840 }
841
842 // Find the idx of the CoordUser for the
843 // TokenID, and if it exists, add the fee to
844 // the collectedFees. Only consider the
845 // coordinator account to receive fee if it was
846 // created in this or a previous batch
847 if acc, ok := tc.l1CreatedAccounts[idxTokenIDToString(cfg.CoordUser, fromAcc.TokenID)]; ok &&
848 common.BatchNum(acc.BatchNum) <= batch.Batch.BatchNum {
849 found := false
850 for _, idx := range batch.Batch.FeeIdxsCoordinator {
851 if idx == common.Idx(acc.Idx) {
852 found = true
853 break
854 }
855 }
856 if !found {
857 batch.Batch.FeeIdxsCoordinator = append(batch.Batch.FeeIdxsCoordinator,
858 common.Idx(acc.Idx))
859 batch.Batch.CollectedFees[fromAcc.TokenID] = big.NewInt(0)
860 }
861 collected := batch.Batch.CollectedFees[fromAcc.TokenID]
862 collected.Add(collected, fee)
863 }
864 }
865 }
866 }
867 return nil
868}