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