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 made of a string. 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 return tc.generateBlocks()
165}
166
167// GenerateBlocksFromInstructions returns an array of BlockData for a given set made of instructions. It uses the
168// users (keys & nonces) of the Context.
169func (tc *Context) GenerateBlocksFromInstructions(set []Instruction) ([]common.BlockData, error) {
170 userNames := []string{}
171 addedNames := make(map[string]bool)
172 for _, inst := range set {
173 if _, ok := addedNames[inst.From]; !ok {
174 // If the name wasn't already added
175 userNames = append(userNames, inst.From)
176 addedNames[inst.From] = true
177 }
178 if _, ok := addedNames[inst.To]; !ok {
179 // If the name wasn't already added
180 userNames = append(userNames, inst.To)
181 addedNames[inst.To] = true
182 }
183 }
184 tc.userNames = userNames
185 tc.instructions = set
186 return tc.generateBlocks()
187}
188
189func (tc *Context) generateBlocks() ([]common.BlockData, error) {
190 tc.generateKeys(tc.userNames)
191
192 var blocks []common.BlockData
193 for _, inst := range tc.instructions {
194 switch inst.Typ {
195 case TxTypeCreateAccountDepositCoordinator: // tx source: L1CoordinatorTx
196 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
197 log.Error(err)
198 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
199 }
200 tx := common.L1Tx{
201 FromEthAddr: tc.Users[inst.From].Addr,
202 FromBJJ: tc.Users[inst.From].BJJ.Public(),
203 TokenID: inst.TokenID,
204 Amount: big.NewInt(0),
205 LoadAmount: big.NewInt(0),
206 Type: common.TxTypeCreateAccountDeposit, // as TxTypeCreateAccountDepositCoordinator is not valid oustide Til package
207 }
208 testTx := L1Tx{
209 lineNum: inst.LineNum,
210 fromIdxName: inst.From,
211 L1Tx: tx,
212 }
213
214 tc.currBatchTest.l1CoordinatorTxs = append(tc.currBatchTest.l1CoordinatorTxs, testTx)
215 case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer: // tx source: L1UserTx
216 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
217 log.Error(err)
218 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
219 }
220 tx := common.L1Tx{
221 FromEthAddr: tc.Users[inst.From].Addr,
222 FromBJJ: tc.Users[inst.From].BJJ.Public(),
223 TokenID: inst.TokenID,
224 Amount: big.NewInt(0),
225 LoadAmount: inst.LoadAmount,
226 Type: inst.Typ,
227 }
228 if inst.Typ == common.TxTypeCreateAccountDepositTransfer {
229 tx.Amount = 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.addToL1UserQueue(testTx); err != nil {
238 return nil, tracerr.Wrap(err)
239 }
240 case common.TxTypeDeposit, common.TxTypeDepositTransfer: // tx source: L1UserTx
241 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
242 log.Error(err)
243 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
244 }
245 if err := tc.checkIfAccountExists(inst.From, inst); err != nil {
246 log.Error(err)
247 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
248 }
249 tx := common.L1Tx{
250 TokenID: inst.TokenID,
251 Amount: big.NewInt(0),
252 LoadAmount: inst.LoadAmount,
253 Type: inst.Typ,
254 }
255 if inst.Typ == common.TxTypeDepositTransfer {
256 tx.Amount = inst.Amount
257 }
258 testTx := L1Tx{
259 lineNum: inst.LineNum,
260 fromIdxName: inst.From,
261 toIdxName: inst.To,
262 L1Tx: tx,
263 }
264 if err := tc.addToL1UserQueue(testTx); err != nil {
265 return nil, tracerr.Wrap(err)
266 }
267 case common.TxTypeTransfer: // L2Tx
268 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
269 log.Error(err)
270 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
271 }
272 tx := common.L2Tx{
273 Amount: inst.Amount,
274 Fee: common.FeeSelector(inst.Fee),
275 Type: common.TxTypeTransfer,
276 EthBlockNum: tc.blockNum,
277 }
278 tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
279 testTx := L2Tx{
280 lineNum: inst.LineNum,
281 fromIdxName: inst.From,
282 toIdxName: inst.To,
283 tokenID: inst.TokenID,
284 L2Tx: tx,
285 }
286 tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
287 case common.TxTypeForceTransfer: // tx source: L1UserTx
288 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
289 log.Error(err)
290 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
291 }
292 tx := common.L1Tx{
293 TokenID: inst.TokenID,
294 Amount: inst.Amount,
295 LoadAmount: big.NewInt(0),
296 Type: common.TxTypeForceTransfer,
297 }
298 testTx := L1Tx{
299 lineNum: inst.LineNum,
300 fromIdxName: inst.From,
301 toIdxName: inst.To,
302 L1Tx: tx,
303 }
304 if err := tc.addToL1UserQueue(testTx); err != nil {
305 return nil, tracerr.Wrap(err)
306 }
307 case common.TxTypeExit: // tx source: L2Tx
308 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
309 log.Error(err)
310 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
311 }
312 tx := common.L2Tx{
313 ToIdx: common.Idx(1), // as is an Exit
314 Fee: common.FeeSelector(inst.Fee),
315 Amount: inst.Amount,
316 Type: common.TxTypeExit,
317 EthBlockNum: tc.blockNum,
318 }
319 tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost
320 testTx := L2Tx{
321 lineNum: inst.LineNum,
322 fromIdxName: inst.From,
323 toIdxName: inst.To,
324 tokenID: inst.TokenID,
325 L2Tx: tx,
326 }
327 tc.currBatchTest.l2Txs = append(tc.currBatchTest.l2Txs, testTx)
328 case common.TxTypeForceExit: // tx source: L1UserTx
329 if err := tc.checkIfTokenIsRegistered(inst); err != nil {
330 log.Error(err)
331 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
332 }
333 tx := common.L1Tx{
334 ToIdx: common.Idx(1), // as is an Exit
335 TokenID: inst.TokenID,
336 Amount: inst.Amount,
337 LoadAmount: big.NewInt(0),
338 Type: common.TxTypeForceExit,
339 }
340 testTx := L1Tx{
341 lineNum: inst.LineNum,
342 fromIdxName: inst.From,
343 toIdxName: inst.To,
344 L1Tx: tx,
345 }
346 if err := tc.addToL1UserQueue(testTx); err != nil {
347 return nil, tracerr.Wrap(err)
348 }
349 case TypeNewBatch:
350 if err := tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
351 return nil, tracerr.Wrap(err)
352 }
353 if err := tc.setIdxs(); err != nil {
354 log.Error(err)
355 return nil, tracerr.Wrap(err)
356 }
357 case TypeNewBatchL1:
358 // for each L1UserTx of the Queues[ToForgeNum], calculate the Idx
359 if err := tc.calculateIdxForL1Txs(false, tc.Queues[tc.ToForgeNum]); err != nil {
360 return nil, tracerr.Wrap(err)
361 }
362 if err := tc.calculateIdxForL1Txs(true, tc.currBatchTest.l1CoordinatorTxs); err != nil {
363 return nil, tracerr.Wrap(err)
364 }
365 tc.currBatch.L1Batch = true
366 if err := tc.setIdxs(); err != nil {
367 log.Error(err)
368 return nil, tracerr.Wrap(err)
369 }
370 toForgeL1TxsNum := int64(tc.openToForge)
371 tc.currBatch.Batch.ForgeL1TxsNum = &toForgeL1TxsNum
372 // advance batch
373 tc.ToForgeNum++
374 if tc.ToForgeNum == tc.openToForge {
375 tc.openToForge++
376 newQueue := []L1Tx{}
377 tc.Queues = append(tc.Queues, newQueue)
378 }
379 case TypeNewBlock:
380 blocks = append(blocks, tc.currBlock)
381 tc.blockNum++
382 tc.currBlock = newBlock(tc.blockNum)
383 case TypeAddToken:
384 newToken := common.Token{
385 EthAddr: ethCommon.BigToAddress(big.NewInt(int64(inst.TokenID * 100))), //nolint:gomnd
386 // Name: fmt.Sprintf("Token %d", inst.TokenID),
387 // Symbol: fmt.Sprintf("TK%d", inst.TokenID),
388 // Decimals: 18,
389 TokenID: inst.TokenID,
390 EthBlockNum: tc.blockNum,
391 }
392 if inst.TokenID != tc.LastRegisteredTokenID+1 {
393 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))
394 }
395 tc.LastRegisteredTokenID++
396 tc.currBlock.Rollup.AddedTokens = append(tc.currBlock.Rollup.AddedTokens, newToken)
397 default:
398 return nil, tracerr.Wrap(fmt.Errorf("Line %d: Unexpected type: %s", inst.LineNum, inst.Typ))
399 }
400 }
401
402 return blocks, nil
403}
404
405// calculateIdxsForL1Txs calculates new Idx for new created accounts. If
406// 'isCoordinatorTxs==true', adds the tx to tc.currBatch.L1CoordinatorTxs.
407func (tc *Context) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error {
408 // for each batch.L1CoordinatorTxs of the Queues[ToForgeNum], calculate the Idx
409 for i := 0; i < len(txs); i++ {
410 tx := txs[i]
411 if tx.L1Tx.Type == common.TxTypeCreateAccountDeposit || tx.L1Tx.Type == common.TxTypeCreateAccountDepositTransfer {
412 if tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] != nil { // if account already exists, return error
413 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))
414 }
415 tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] = &Account{
416 Idx: common.Idx(tc.idx),
417 TokenID: tx.L1Tx.TokenID,
418 Nonce: common.Nonce(0),
419 BatchNum: tc.currBatchNum,
420 }
421 tc.l1CreatedAccounts[idxTokenIDToString(tx.fromIdxName, tx.L1Tx.TokenID)] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
422 tc.accountsByIdx[tc.idx] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
423 tc.UsersByIdx[tc.idx] = tc.Users[tx.fromIdxName]
424 tc.idx++
425 }
426 if isCoordinatorTxs {
427 tc.currBatch.L1CoordinatorTxs = append(tc.currBatch.L1CoordinatorTxs, tx.L1Tx)
428 }
429 }
430 return nil
431}
432
433// setIdxs sets the Idxs to the transactions of the tc.currBatch
434func (tc *Context) setIdxs() error {
435 // once Idxs are calculated, update transactions to use the new Idxs
436 for i := 0; i < len(tc.currBatchTest.l2Txs); i++ {
437 testTx := &tc.currBatchTest.l2Txs[i]
438
439 if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil {
440 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))
441 }
442 if testTx.L2Tx.Type == common.TxTypeTransfer {
443 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(testTx.toIdxName, testTx.tokenID)]; !ok {
444 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))
445 }
446 }
447 tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce++
448 // next line is commented to avoid Blockchain L2Txs to have
449 // Nonce different from 0, as from Blockchain those
450 // transactions will come without Nonce
451 // testTx.L2Tx.Nonce = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce
452
453 // set real Idx
454 testTx.L2Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Idx
455 if testTx.L2Tx.Type == common.TxTypeTransfer {
456 testTx.L2Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.tokenID].Idx
457 }
458 // in case Type==Exit, ToIdx=1, already set at the
459 // GenerateBlocks main switch inside TxTypeExit case
460
461 nTx, err := common.NewL2Tx(&testTx.L2Tx)
462 if err != nil {
463 return tracerr.Wrap(fmt.Errorf("Line %d: %s", testTx.lineNum, err.Error()))
464 }
465 testTx.L2Tx = *nTx
466
467 tc.currBatch.L2Txs = append(tc.currBatch.L2Txs, testTx.L2Tx)
468 }
469
470 tc.currBatch.Batch.LastIdx = int64(tc.idx - 1) // `-1` because tc.idx is the next available idx
471 tc.currBlock.Rollup.Batches = append(tc.currBlock.Rollup.Batches, tc.currBatch)
472 tc.currBatchNum++
473 tc.currBatch = newBatchData(tc.currBatchNum)
474 tc.currBatchTest.l1CoordinatorTxs = nil
475 tc.currBatchTest.l2Txs = nil
476 return nil
477}
478
479// addToL1UserQueue adds the L1UserTx into the queue that is open and has space
480func (tc *Context) addToL1UserQueue(tx L1Tx) error {
481 if len(tc.Queues[tc.openToForge]) >= tc.rollupConstMaxL1UserTx {
482 // if current OpenToForge queue reached its Max, move into a
483 // new queue
484 tc.openToForge++
485 newQueue := []L1Tx{}
486 tc.Queues = append(tc.Queues, newQueue)
487 }
488 // Fill L1UserTx specific parameters
489 tx.L1Tx.UserOrigin = true
490 toForgeL1TxsNum := int64(tc.openToForge)
491 tx.L1Tx.ToForgeL1TxsNum = &toForgeL1TxsNum
492 tx.L1Tx.EthBlockNum = tc.blockNum
493 tx.L1Tx.Position = len(tc.Queues[tc.openToForge])
494
495 // When an L1UserTx is generated, all idxs must be available (except when idx == 0 or idx == 1)
496 if tx.L1Tx.Type != common.TxTypeCreateAccountDeposit && tx.L1Tx.Type != common.TxTypeCreateAccountDepositTransfer {
497 tx.L1Tx.FromIdx = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID].Idx
498 }
499 tx.L1Tx.FromEthAddr = tc.Users[tx.fromIdxName].Addr
500 tx.L1Tx.FromBJJ = tc.Users[tx.fromIdxName].BJJ.Public()
501 if tx.toIdxName == "" {
502 tx.L1Tx.ToIdx = common.Idx(0)
503 } else {
504 account, ok := tc.Users[tx.toIdxName].Accounts[tx.L1Tx.TokenID]
505 if !ok {
506 return tracerr.Wrap(fmt.Errorf("Line %d: Transfer to User: %s, for TokenID: %d, "+
507 "while account not created yet", tx.lineNum, tx.toIdxName, tx.L1Tx.TokenID))
508 }
509 tx.L1Tx.ToIdx = account.Idx
510 }
511 if tx.L1Tx.Type == common.TxTypeForceExit {
512 tx.L1Tx.ToIdx = common.Idx(1)
513 }
514 nTx, err := common.NewL1Tx(&tx.L1Tx)
515 if err != nil {
516 return tracerr.Wrap(fmt.Errorf("Line %d: %s", tx.lineNum, err.Error()))
517 }
518 tx.L1Tx = *nTx
519
520 tc.Queues[tc.openToForge] = append(tc.Queues[tc.openToForge], tx)
521 tc.currBlock.Rollup.L1UserTxs = append(tc.currBlock.Rollup.L1UserTxs, tx.L1Tx)
522
523 return nil
524}
525
526func (tc *Context) checkIfAccountExists(tf string, inst Instruction) error {
527 if tc.Users[tf].Accounts[inst.TokenID] == nil {
528 return tracerr.Wrap(fmt.Errorf("%s at User: %s, for TokenID: %d, while account not created yet", inst.Typ, tf, inst.TokenID))
529 }
530 return nil
531}
532
533func (tc *Context) checkIfTokenIsRegistered(inst Instruction) error {
534 if inst.TokenID > tc.LastRegisteredTokenID {
535 return tracerr.Wrap(fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.Typ, inst.TokenID, tc.LastRegisteredTokenID))
536 }
537 return nil
538}
539
540// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set made of a string. It
541// uses the users (keys) of the Context.
542func (tc *Context) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) {
543 parser := newParser(strings.NewReader(set))
544 parsedSet, err := parser.parse()
545 if err != nil {
546 return nil, tracerr.Wrap(err)
547 }
548 if parsedSet.typ != SetTypePoolL2 {
549 return nil, tracerr.Wrap(fmt.Errorf("Expected set type: %s, found: %s", SetTypePoolL2, parsedSet.typ))
550 }
551
552 tc.instructions = parsedSet.instructions
553 tc.userNames = parsedSet.users
554
555 return tc.generatePoolL2Txs()
556}
557
558// GeneratePoolL2TxsFromInstructions returns an array of common.PoolL2Tx from a given set made of instructions. It
559// uses the users (keys) of the Context.
560func (tc *Context) GeneratePoolL2TxsFromInstructions(set []Instruction) ([]common.PoolL2Tx, error) {
561 userNames := []string{}
562 addedNames := make(map[string]bool)
563 for _, inst := range set {
564 if _, ok := addedNames[inst.From]; !ok {
565 // If the name wasn't already added
566 userNames = append(userNames, inst.From)
567 addedNames[inst.From] = true
568 }
569 if _, ok := addedNames[inst.To]; !ok {
570 // If the name wasn't already added
571 userNames = append(userNames, inst.To)
572 addedNames[inst.To] = true
573 }
574 }
575 tc.userNames = userNames
576 tc.instructions = set
577
578 return tc.generatePoolL2Txs()
579}
580
581func (tc *Context) generatePoolL2Txs() ([]common.PoolL2Tx, error) {
582 tc.generateKeys(tc.userNames)
583
584 txs := []common.PoolL2Tx{}
585 for _, inst := range tc.instructions {
586 switch inst.Typ {
587 case common.TxTypeTransfer, common.TxTypeTransferToEthAddr, common.TxTypeTransferToBJJ:
588 if err := tc.checkIfAccountExists(inst.From, inst); err != nil {
589 log.Error(err)
590 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
591 }
592 if inst.Typ == common.TxTypeTransfer {
593 // if TxTypeTransfer, need to exist the ToIdx account
594 if err := tc.checkIfAccountExists(inst.To, inst); err != nil {
595 log.Error(err)
596 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
597 }
598 }
599 tc.Users[inst.From].Accounts[inst.TokenID].Nonce++
600 // if account of receiver does not exist, don't use
601 // ToIdx, and use only ToEthAddr & ToBJJ
602 tx := common.PoolL2Tx{
603 FromIdx: tc.Users[inst.From].Accounts[inst.TokenID].Idx,
604 TokenID: inst.TokenID,
605 Amount: inst.Amount,
606 Fee: common.FeeSelector(inst.Fee),
607 Nonce: tc.Users[inst.From].Accounts[inst.TokenID].Nonce,
608 State: common.PoolL2TxStatePending,
609 Timestamp: time.Now(),
610 RqToEthAddr: common.EmptyAddr,
611 RqToBJJ: nil,
612 Type: inst.Typ,
613 }
614 if tx.Type == common.TxTypeTransfer {
615 tx.ToIdx = tc.Users[inst.To].Accounts[inst.TokenID].Idx
616 tx.ToEthAddr = tc.Users[inst.To].Addr
617 tx.ToBJJ = tc.Users[inst.To].BJJ.Public()
618 } else if tx.Type == common.TxTypeTransferToEthAddr {
619 tx.ToIdx = common.Idx(0)
620 tx.ToEthAddr = tc.Users[inst.To].Addr
621 } else if tx.Type == common.TxTypeTransferToBJJ {
622 tx.ToIdx = common.Idx(0)
623 tx.ToEthAddr = common.FFAddr
624 tx.ToBJJ = tc.Users[inst.To].BJJ.Public()
625 }
626 nTx, err := common.NewPoolL2Tx(&tx)
627 if err != nil {
628 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
629 }
630 tx = *nTx
631 // perform signature and set it to tx.Signature
632 toSign, err := tx.HashToSign()
633 if err != nil {
634 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
635 }
636 sig := tc.Users[inst.From].BJJ.SignPoseidon(toSign)
637 tx.Signature = sig.Compress()
638
639 txs = append(txs, tx)
640 case common.TxTypeExit:
641 tc.Users[inst.From].Accounts[inst.TokenID].Nonce++
642 tx := common.PoolL2Tx{
643 FromIdx: tc.Users[inst.From].Accounts[inst.TokenID].Idx,
644 ToIdx: common.Idx(1), // as is an Exit
645 Fee: common.FeeSelector(inst.Fee),
646 TokenID: inst.TokenID,
647 Amount: inst.Amount,
648 Nonce: tc.Users[inst.From].Accounts[inst.TokenID].Nonce,
649 State: common.PoolL2TxStatePending,
650 Type: common.TxTypeExit,
651 }
652 nTx, err := common.NewPoolL2Tx(&tx)
653 if err != nil {
654 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
655 }
656 tx = *nTx
657 // perform signature and set it to tx.Signature
658 toSign, err := tx.HashToSign()
659 if err != nil {
660 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
661 }
662 sig := tc.Users[inst.From].BJJ.SignPoseidon(toSign)
663 tx.Signature = sig.Compress()
664 txs = append(txs, tx)
665 default:
666 return nil, tracerr.Wrap(fmt.Errorf("Line %d: instruction type unrecognized: %s", inst.LineNum, inst.Typ))
667 }
668 }
669
670 return txs, nil
671}
672
673// generateKeys generates BabyJubJub & Address keys for the given list of user
674// names in a deterministic way. This means, that for the same given
675// 'userNames' in a certain order, the keys will be always the same.
676func (tc *Context) generateKeys(userNames []string) {
677 for i := 1; i < len(userNames)+1; i++ {
678 if _, ok := tc.Users[userNames[i-1]]; ok {
679 // account already created
680 continue
681 }
682 // babyjubjub key
683 var sk babyjub.PrivateKey
684 copy(sk[:], []byte(strconv.Itoa(i))) // only for testing
685
686 // eth address
687 var key ecdsa.PrivateKey
688 key.D = big.NewInt(int64(i)) // only for testing
689 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
690 key.Curve = ethCrypto.S256()
691 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
692
693 u := User{
694 Name: userNames[i-1],
695 BJJ: &sk,
696 Addr: addr,
697 Accounts: make(map[common.TokenID]*Account),
698 }
699 tc.Users[userNames[i-1]] = &u
700 }
701}
702
703// L1TxsToCommonL1Txs converts an array of []til.L1Tx to []common.L1Tx
704func L1TxsToCommonL1Txs(l1 []L1Tx) []common.L1Tx {
705 var r []common.L1Tx
706 for i := 0; i < len(l1); i++ {
707 r = append(r, l1[i].L1Tx)
708 }
709 return r
710}
711
712// ConfigExtra is the configuration used in FillBlocksExtra to extend the
713// blocks returned by til.
714type ConfigExtra struct {
715 // Address to set as forger for each batch
716 BootCoordAddr ethCommon.Address
717 // Coordinator user name used to select the corresponding accounts to
718 // collect coordinator fees
719 CoordUser string
720}
721
722// FillBlocksL1UserTxsBatchNum fills the BatchNum of forged L1UserTxs:
723// - blocks[].Rollup.L1UserTxs[].BatchNum
724func (tc *Context) FillBlocksL1UserTxsBatchNum(blocks []common.BlockData) {
725 for i := range blocks {
726 block := &blocks[i]
727 for j := range block.Rollup.Batches {
728 batch := &block.Rollup.Batches[j]
729 if batch.L1Batch {
730 // Set BatchNum for forged L1UserTxs to til blocks
731 bn := batch.Batch.BatchNum
732 for k := range blocks {
733 block := &blocks[k]
734 for l := range block.Rollup.L1UserTxs {
735 tx := &block.Rollup.L1UserTxs[l]
736 if *tx.ToForgeL1TxsNum == tc.extra.openToForge {
737 tx.BatchNum = &bn
738 }
739 }
740 }
741 tc.extra.openToForge++
742 }
743 }
744 }
745}
746
747// FillBlocksForgedL1UserTxs fills the L1UserTxs of a batch with the L1UserTxs
748// that are forged in that batch. It always sets `EffectiveAmount` = `Amount`
749// and `EffectiveLoadAmount` = `LoadAmount`.
750func (tc *Context) FillBlocksForgedL1UserTxs(blocks []common.BlockData) error {
751 for i := range blocks {
752 block := &blocks[i]
753 for j := range block.Rollup.Batches {
754 batch := &block.Rollup.Batches[j]
755 if batch.L1Batch {
756 batchNum := batch.Batch.BatchNum
757 queue := tc.Queues[int(*batch.Batch.ForgeL1TxsNum)]
758 batch.L1UserTxs = make([]common.L1Tx, len(queue))
759 for k := range queue {
760 tx := &batch.L1UserTxs[k]
761 *tx = queue[k].L1Tx
762 tx.EffectiveAmount = tx.Amount
763 tx.EffectiveLoadAmount = tx.LoadAmount
764 tx.BatchNum = &batchNum
765 _tx, err := common.NewL1Tx(tx)
766 if err != nil {
767 return tracerr.Wrap(err)
768 }
769 *tx = *_tx
770 }
771 }
772 }
773 }
774 return nil
775}
776
777// FillBlocksExtra fills extra fields not generated by til in each block, so
778// that the blockData is closer to what the HistoryDB stores. The filled fields are:
779// - blocks[].Rollup.Batch.EthBlockNum
780// - blocks[].Rollup.Batch.ForgerAddr
781// - blocks[].Rollup.Batch.ForgeL1TxsNum
782// - blocks[].Rollup.Batch.L1CoordinatorTxs[].TxID
783// - blocks[].Rollup.Batch.L1CoordinatorTxs[].BatchNum
784// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EthBlockNum
785// - blocks[].Rollup.Batch.L1CoordinatorTxs[].Position
786// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EffectiveAmount
787// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EffectiveLoadAmount
788// - blocks[].Rollup.Batch.L2Txs[].TxID
789// - blocks[].Rollup.Batch.L2Txs[].Position
790// - blocks[].Rollup.Batch.L2Txs[].Nonce
791// - blocks[].Rollup.Batch.ExitTree
792// - blocks[].Rollup.Batch.CreatedAccounts
793// - blocks[].Rollup.Batch.FeeIdxCoordinator
794// - blocks[].Rollup.Batch.CollectedFees
795func (tc *Context) FillBlocksExtra(blocks []common.BlockData, cfg *ConfigExtra) error {
796 // Fill extra fields not generated by til in til block
797 for i := range blocks {
798 block := &blocks[i]
799 for j := range block.Rollup.Batches {
800 batch := &block.Rollup.Batches[j]
801 batch.Batch.EthBlockNum = block.Block.Num
802 // til doesn't fill the batch forger addr
803 batch.Batch.ForgerAddr = cfg.BootCoordAddr
804 if batch.L1Batch {
805 toForgeL1TxsNumCpy := tc.extra.toForgeL1TxsNum
806 // til doesn't fill the ForgeL1TxsNum
807 batch.Batch.ForgeL1TxsNum = &toForgeL1TxsNumCpy
808 tc.extra.toForgeL1TxsNum++
809 }
810
811 batchNum := batch.Batch.BatchNum
812 for k := range batch.L1CoordinatorTxs {
813 tx := &batch.L1CoordinatorTxs[k]
814 tx.BatchNum = &batchNum
815 tx.EthBlockNum = batch.Batch.EthBlockNum
816 }
817 }
818 }
819
820 // Fill CreatedAccounts
821 for i := range blocks {
822 block := &blocks[i]
823 for j := range block.Rollup.Batches {
824 batch := &block.Rollup.Batches[j]
825 l1Txs := []common.L1Tx{}
826 if batch.L1Batch {
827 for _, tx := range tc.Queues[*batch.Batch.ForgeL1TxsNum] {
828 l1Txs = append(l1Txs, tx.L1Tx)
829 }
830 }
831 l1Txs = append(l1Txs, batch.L1CoordinatorTxs...)
832 for k := range l1Txs {
833 tx := &l1Txs[k]
834 if tx.Type == common.TxTypeCreateAccountDeposit ||
835 tx.Type == common.TxTypeCreateAccountDepositTransfer {
836 user, ok := tc.UsersByIdx[tc.extra.idx]
837 if !ok {
838 return tracerr.Wrap(fmt.Errorf("Created account with idx: %v not found", tc.extra.idx))
839 }
840 batch.CreatedAccounts = append(batch.CreatedAccounts,
841 common.Account{
842 Idx: common.Idx(tc.extra.idx),
843 TokenID: tx.TokenID,
844 BatchNum: batch.Batch.BatchNum,
845 PublicKey: user.BJJ.Public(),
846 EthAddr: user.Addr,
847 Nonce: 0,
848 Balance: big.NewInt(0),
849 })
850 tc.extra.idx++
851 }
852 }
853 }
854 }
855
856 // Fill expected positions in L1CoordinatorTxs and L2Txs
857 for i := range blocks {
858 block := &blocks[i]
859 for j := range block.Rollup.Batches {
860 batch := &block.Rollup.Batches[j]
861 position := 0
862 if batch.L1Batch {
863 position = len(tc.Queues[*batch.Batch.ForgeL1TxsNum])
864 }
865 for k := range batch.L1CoordinatorTxs {
866 tx := &batch.L1CoordinatorTxs[k]
867 tx.Position = position
868 position++
869 tx.EffectiveAmount = big.NewInt(0)
870 tx.EffectiveLoadAmount = big.NewInt(0)
871 nTx, err := common.NewL1Tx(tx)
872 if err != nil {
873 return tracerr.Wrap(err)
874 }
875 *tx = *nTx
876 }
877 for k := range batch.L2Txs {
878 tx := &batch.L2Txs[k]
879 tx.Position = position
880 position++
881 tc.extra.nonces[tx.FromIdx]++
882 tx.Nonce = tc.extra.nonces[tx.FromIdx]
883 nTx, err := common.NewL2Tx(tx)
884 if err != nil {
885 return tracerr.Wrap(err)
886 }
887 *tx = *nTx
888 }
889 }
890 }
891
892 // Fill ExitTree (only AccountIdx and Balance)
893 for i := range blocks {
894 block := &blocks[i]
895 for j := range block.Rollup.Batches {
896 batch := &block.Rollup.Batches[j]
897 if batch.L1Batch {
898 for _, _tx := range tc.Queues[*batch.Batch.ForgeL1TxsNum] {
899 tx := _tx.L1Tx
900 if tx.Type == common.TxTypeForceExit {
901 batch.ExitTree =
902 append(batch.ExitTree,
903 common.ExitInfo{
904 BatchNum: batch.Batch.BatchNum,
905 AccountIdx: tx.FromIdx,
906 Balance: tx.Amount,
907 })
908 }
909 }
910 }
911 for k := range batch.L2Txs {
912 tx := &batch.L2Txs[k]
913 if tx.Type == common.TxTypeExit {
914 batch.ExitTree = append(batch.ExitTree, common.ExitInfo{
915 BatchNum: batch.Batch.BatchNum,
916 AccountIdx: tx.FromIdx,
917 Balance: tx.Amount,
918 })
919 }
920 fee, err := common.CalcFeeAmount(tx.Amount, tx.Fee)
921 if err != nil {
922 return tracerr.Wrap(err)
923 }
924
925 // Find the TokenID of the tx
926 fromAcc, ok := tc.accountsByIdx[int(tx.FromIdx)]
927 if !ok {
928 return tracerr.Wrap(fmt.Errorf("L2tx.FromIdx idx: %v not found", tx.FromIdx))
929 }
930
931 // Find the idx of the CoordUser for the
932 // TokenID, and if it exists, add the fee to
933 // the collectedFees. Only consider the
934 // coordinator account to receive fee if it was
935 // created in this or a previous batch
936 if acc, ok := tc.l1CreatedAccounts[idxTokenIDToString(cfg.CoordUser, fromAcc.TokenID)]; ok &&
937 common.BatchNum(acc.BatchNum) <= batch.Batch.BatchNum {
938 found := false
939 for _, idx := range batch.Batch.FeeIdxsCoordinator {
940 if idx == common.Idx(acc.Idx) {
941 found = true
942 break
943 }
944 }
945 if !found {
946 batch.Batch.FeeIdxsCoordinator = append(batch.Batch.FeeIdxsCoordinator,
947 common.Idx(acc.Idx))
948 batch.Batch.CollectedFees[fromAcc.TokenID] = big.NewInt(0)
949 }
950 collected := batch.Batch.CollectedFees[fromAcc.TokenID]
951 collected.Add(collected, fee)
952 }
953 }
954 }
955 }
956 return nil
957}