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