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