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