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