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