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}
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 var iBytes [8]byte
698 binary.LittleEndian.PutUint64(iBytes[:], uint64(i))
699 copy(sk[:], iBytes[:]) // only for testing
700
701 // eth address
702 var key ecdsa.PrivateKey
703 key.D = big.NewInt(int64(i)) // only for testing
704 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
705 key.Curve = ethCrypto.S256()
706 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
707
708 u := User{
709 Name: userNames[i-1],
710 BJJ: &sk,
711 EthSk: &key,
712 Addr: addr,
713 Accounts: make(map[common.TokenID]*Account),
714 }
715 tc.Users[userNames[i-1]] = &u
716 }
717}
718
719// L1TxsToCommonL1Txs converts an array of []til.L1Tx to []common.L1Tx
720func L1TxsToCommonL1Txs(l1 []L1Tx) []common.L1Tx {
721 var r []common.L1Tx
722 for i := 0; i < len(l1); i++ {
723 r = append(r, l1[i].L1Tx)
724 }
725 return r
726}
727
728// ConfigExtra is the configuration used in FillBlocksExtra to extend the
729// blocks returned by til.
730type ConfigExtra struct {
731 // Address to set as forger for each batch
732 BootCoordAddr ethCommon.Address
733 // Coordinator user name used to select the corresponding accounts to
734 // collect coordinator fees
735 CoordUser string
736}
737
738// FillBlocksL1UserTxsBatchNum fills the BatchNum of forged L1UserTxs:
739// - blocks[].Rollup.L1UserTxs[].BatchNum
740func (tc *Context) FillBlocksL1UserTxsBatchNum(blocks []common.BlockData) {
741 for i := range blocks {
742 block := &blocks[i]
743 for j := range block.Rollup.Batches {
744 batch := &block.Rollup.Batches[j]
745 if batch.L1Batch {
746 // Set BatchNum for forged L1UserTxs to til blocks
747 bn := batch.Batch.BatchNum
748 for k := range blocks {
749 block := &blocks[k]
750 for l := range block.Rollup.L1UserTxs {
751 tx := &block.Rollup.L1UserTxs[l]
752 if *tx.ToForgeL1TxsNum == tc.extra.openToForge {
753 tx.BatchNum = &bn
754 }
755 }
756 }
757 tc.extra.openToForge++
758 }
759 }
760 }
761}
762
763// FillBlocksForgedL1UserTxs fills the L1UserTxs of a batch with the L1UserTxs
764// that are forged in that batch. It always sets `EffectiveAmount` = `Amount`
765// and `EffectiveDepositAmount` = `DepositAmount`.
766func (tc *Context) FillBlocksForgedL1UserTxs(blocks []common.BlockData) error {
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 batchNum := batch.Batch.BatchNum
773 queue := tc.Queues[int(*batch.Batch.ForgeL1TxsNum)]
774 batch.L1UserTxs = make([]common.L1Tx, len(queue))
775 for k := range queue {
776 tx := &batch.L1UserTxs[k]
777 *tx = queue[k].L1Tx
778 tx.EffectiveAmount = tx.Amount
779 tx.EffectiveDepositAmount = tx.DepositAmount
780 tx.BatchNum = &batchNum
781 _tx, err := common.NewL1Tx(tx)
782 if err != nil {
783 return tracerr.Wrap(err)
784 }
785 *tx = *_tx
786 }
787 }
788 }
789 }
790 return nil
791}
792
793// FillBlocksExtra fills extra fields not generated by til in each block, so
794// that the blockData is closer to what the HistoryDB stores. The filled
795// fields are:
796// - blocks[].Rollup.Batch.EthBlockNum
797// - blocks[].Rollup.Batch.ForgerAddr
798// - blocks[].Rollup.Batch.ForgeL1TxsNum
799// - blocks[].Rollup.Batch.L1CoordinatorTxs[].TxID
800// - blocks[].Rollup.Batch.L1CoordinatorTxs[].BatchNum
801// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EthBlockNum
802// - blocks[].Rollup.Batch.L1CoordinatorTxs[].Position
803// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EffectiveAmount
804// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EffectiveDepositAmount
805// - blocks[].Rollup.Batch.L2Txs[].TxID
806// - blocks[].Rollup.Batch.L2Txs[].Position
807// - blocks[].Rollup.Batch.L2Txs[].Nonce
808// - blocks[].Rollup.Batch.ExitTree
809// - blocks[].Rollup.Batch.CreatedAccounts
810// - blocks[].Rollup.Batch.FeeIdxCoordinator
811// - blocks[].Rollup.Batch.CollectedFees
812func (tc *Context) FillBlocksExtra(blocks []common.BlockData, cfg *ConfigExtra) error {
813 // Fill extra fields not generated by til in til block
814 for i := range blocks {
815 block := &blocks[i]
816 for j := range block.Rollup.Batches {
817 batch := &block.Rollup.Batches[j]
818 batch.Batch.EthBlockNum = block.Block.Num
819 // til doesn't fill the batch forger addr
820 batch.Batch.ForgerAddr = cfg.BootCoordAddr
821 if batch.L1Batch {
822 toForgeL1TxsNumCpy := tc.extra.toForgeL1TxsNum
823 // til doesn't fill the ForgeL1TxsNum
824 batch.Batch.ForgeL1TxsNum = &toForgeL1TxsNumCpy
825 tc.extra.toForgeL1TxsNum++
826 }
827
828 batchNum := batch.Batch.BatchNum
829 for k := range batch.L1CoordinatorTxs {
830 tx := &batch.L1CoordinatorTxs[k]
831 tx.BatchNum = &batchNum
832 tx.EthBlockNum = batch.Batch.EthBlockNum
833 }
834 }
835 }
836
837 // Fill CreatedAccounts
838 for i := range blocks {
839 block := &blocks[i]
840 for j := range block.Rollup.Batches {
841 batch := &block.Rollup.Batches[j]
842 l1Txs := []common.L1Tx{}
843 if batch.L1Batch {
844 for _, tx := range tc.Queues[*batch.Batch.ForgeL1TxsNum] {
845 l1Txs = append(l1Txs, tx.L1Tx)
846 }
847 }
848 l1Txs = append(l1Txs, batch.L1CoordinatorTxs...)
849 for k := range l1Txs {
850 tx := &l1Txs[k]
851 if tx.Type == common.TxTypeCreateAccountDeposit ||
852 tx.Type == common.TxTypeCreateAccountDepositTransfer {
853 user, ok := tc.UsersByIdx[tc.extra.idx]
854 if !ok {
855 return tracerr.Wrap(fmt.Errorf("Created account with idx: %v not found", tc.extra.idx))
856 }
857 batch.CreatedAccounts = append(batch.CreatedAccounts,
858 common.Account{
859 Idx: common.Idx(tc.extra.idx),
860 TokenID: tx.TokenID,
861 BatchNum: batch.Batch.BatchNum,
862 BJJ: user.BJJ.Public().Compress(),
863 EthAddr: user.Addr,
864 Nonce: 0,
865 Balance: big.NewInt(0),
866 })
867 tc.extra.idx++
868 }
869 }
870 }
871 }
872
873 // Fill expected positions in L1CoordinatorTxs and L2Txs
874 for i := range blocks {
875 block := &blocks[i]
876 for j := range block.Rollup.Batches {
877 batch := &block.Rollup.Batches[j]
878 position := 0
879 if batch.L1Batch {
880 position = len(tc.Queues[*batch.Batch.ForgeL1TxsNum])
881 }
882 for k := range batch.L1CoordinatorTxs {
883 tx := &batch.L1CoordinatorTxs[k]
884 tx.Position = position
885 position++
886 tx.EffectiveAmount = big.NewInt(0)
887 tx.EffectiveDepositAmount = big.NewInt(0)
888 nTx, err := common.NewL1Tx(tx)
889 if err != nil {
890 return tracerr.Wrap(err)
891 }
892 *tx = *nTx
893 }
894 for k := range batch.L2Txs {
895 tx := &batch.L2Txs[k]
896 tx.Position = position
897 position++
898 tx.Nonce = tc.extra.nonces[tx.FromIdx]
899 tc.extra.nonces[tx.FromIdx]++
900 if err := tx.SetID(); err != nil {
901 return tracerr.Wrap(err)
902 }
903 nTx, err := common.NewL2Tx(tx)
904 if err != nil {
905 return tracerr.Wrap(err)
906 }
907 *tx = *nTx
908 }
909 }
910 }
911
912 // Fill ExitTree (only AccountIdx and Balance)
913 for i := range blocks {
914 block := &blocks[i]
915 for j := range block.Rollup.Batches {
916 batch := &block.Rollup.Batches[j]
917 if batch.L1Batch {
918 for _, _tx := range tc.Queues[*batch.Batch.ForgeL1TxsNum] {
919 tx := _tx.L1Tx
920 if tx.Type == common.TxTypeForceExit {
921 batch.ExitTree =
922 append(batch.ExitTree,
923 common.ExitInfo{
924 BatchNum: batch.Batch.BatchNum,
925 AccountIdx: tx.FromIdx,
926 Balance: tx.Amount,
927 })
928 }
929 }
930 }
931 for k := range batch.L2Txs {
932 tx := &batch.L2Txs[k]
933 if tx.Type == common.TxTypeExit {
934 batch.ExitTree = append(batch.ExitTree, common.ExitInfo{
935 BatchNum: batch.Batch.BatchNum,
936 AccountIdx: tx.FromIdx,
937 Balance: tx.Amount,
938 })
939 }
940 fee, err := common.CalcFeeAmount(tx.Amount, tx.Fee)
941 if err != nil {
942 return tracerr.Wrap(err)
943 }
944
945 // Find the TokenID of the tx
946 fromAcc, ok := tc.accountsByIdx[int(tx.FromIdx)]
947 if !ok {
948 return tracerr.Wrap(fmt.Errorf("L2tx.FromIdx idx: %v not found", tx.FromIdx))
949 }
950
951 // Find the idx of the CoordUser for the
952 // TokenID, and if it exists, add the fee to
953 // the collectedFees. Only consider the
954 // coordinator account to receive fee if it was
955 // created in this or a previous batch
956 if acc, ok := tc.l1CreatedAccounts[idxTokenIDToString(cfg.CoordUser, fromAcc.TokenID)]; ok &&
957 common.BatchNum(acc.BatchNum) <= batch.Batch.BatchNum {
958 found := false
959 for _, idx := range batch.Batch.FeeIdxsCoordinator {
960 if idx == common.Idx(acc.Idx) {
961 found = true
962 break
963 }
964 }
965 if !found {
966 batch.Batch.FeeIdxsCoordinator = append(batch.Batch.FeeIdxsCoordinator,
967 common.Idx(acc.Idx))
968 batch.Batch.CollectedFees[fromAcc.TokenID] = big.NewInt(0)
969 }
970 collected := batch.Batch.CollectedFees[fromAcc.TokenID]
971 collected.Add(collected, fee)
972 }
973 }
974 }
975 }
976 return nil
977}