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 return nil, tracerr.Wrap(err)
374 }
375 toForgeL1TxsNum := int64(tc.openToForge)
376 tc.currBatch.Batch.ForgeL1TxsNum = &toForgeL1TxsNum
377 // advance batch
378 tc.ToForgeNum++
379 if tc.ToForgeNum == tc.openToForge {
380 tc.openToForge++
381 newQueue := []L1Tx{}
382 tc.Queues = append(tc.Queues, newQueue)
383 }
384 case TypeNewBlock:
385 blocks = append(blocks, tc.currBlock)
386 tc.blockNum++
387 tc.currBlock = newBlock(tc.blockNum)
388 case TypeAddToken:
389 newToken := common.Token{
390 EthAddr: ethCommon.BigToAddress(big.NewInt(int64(inst.TokenID * 100))), //nolint:gomnd
391 // Name: fmt.Sprintf("Token %d", inst.TokenID),
392 // Symbol: fmt.Sprintf("TK%d", inst.TokenID),
393 // Decimals: 18,
394 TokenID: inst.TokenID,
395 EthBlockNum: tc.blockNum,
396 }
397 if inst.TokenID != tc.LastRegisteredTokenID+1 {
398 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))
399 }
400 tc.LastRegisteredTokenID++
401 tc.currBlock.Rollup.AddedTokens = append(tc.currBlock.Rollup.AddedTokens, newToken)
402 default:
403 return nil, tracerr.Wrap(fmt.Errorf("Line %d: Unexpected type: %s", inst.LineNum, inst.Typ))
404 }
405 }
406
407 return blocks, nil
408}
409
410// calculateIdxsForL1Txs calculates new Idx for new created accounts. If
411// 'isCoordinatorTxs==true', adds the tx to tc.currBatch.L1CoordinatorTxs.
412func (tc *Context) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error {
413 // for each batch.L1CoordinatorTxs of the Queues[ToForgeNum], calculate the Idx
414 for i := 0; i < len(txs); i++ {
415 tx := txs[i]
416 if tx.L1Tx.Type == common.TxTypeCreateAccountDeposit || tx.L1Tx.Type == common.TxTypeCreateAccountDepositTransfer {
417 if tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] != nil { // if account already exists, return error
418 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))
419 }
420 tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] = &Account{
421 Idx: common.Idx(tc.idx),
422 TokenID: tx.L1Tx.TokenID,
423 Nonce: common.Nonce(0),
424 BatchNum: tc.currBatchNum,
425 }
426 tc.l1CreatedAccounts[idxTokenIDToString(tx.fromIdxName, tx.L1Tx.TokenID)] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
427 tc.accountsByIdx[tc.idx] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID]
428 tc.UsersByIdx[tc.idx] = tc.Users[tx.fromIdxName]
429 tc.idx++
430 }
431 if isCoordinatorTxs {
432 tc.currBatch.L1CoordinatorTxs = append(tc.currBatch.L1CoordinatorTxs, tx.L1Tx)
433 }
434 }
435 return nil
436}
437
438// setIdxs sets the Idxs to the transactions of the tc.currBatch
439func (tc *Context) setIdxs() error {
440 // once Idxs are calculated, update transactions to use the new Idxs
441 for i := 0; i < len(tc.currBatchTest.l2Txs); i++ {
442 testTx := &tc.currBatchTest.l2Txs[i]
443
444 if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil {
445 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))
446 }
447 if testTx.L2Tx.Type == common.TxTypeTransfer {
448 if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(testTx.toIdxName, testTx.tokenID)]; !ok {
449 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))
450 }
451 }
452 tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce++
453 // next line is commented to avoid Blockchain L2Txs to have
454 // Nonce different from 0, as from Blockchain those
455 // transactions will come without Nonce
456 // testTx.L2Tx.Nonce = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce
457
458 // set real Idx
459 testTx.L2Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Idx
460 if testTx.L2Tx.Type == common.TxTypeTransfer {
461 testTx.L2Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.tokenID].Idx
462 }
463 // in case Type==Exit, ToIdx=1, already set at the
464 // GenerateBlocks main switch inside TxTypeExit case
465
466 nTx, err := common.NewL2Tx(&testTx.L2Tx)
467 if err != nil {
468 return tracerr.Wrap(fmt.Errorf("Line %d: %s", testTx.lineNum, err.Error()))
469 }
470 testTx.L2Tx = *nTx
471
472 tc.currBatch.L2Txs = append(tc.currBatch.L2Txs, testTx.L2Tx)
473 }
474
475 tc.currBatch.Batch.LastIdx = int64(tc.idx - 1) // `-1` because tc.idx is the next available idx
476 tc.currBlock.Rollup.Batches = append(tc.currBlock.Rollup.Batches, tc.currBatch)
477 tc.currBatchNum++
478 tc.currBatch = newBatchData(tc.currBatchNum)
479 tc.currBatchTest.l1CoordinatorTxs = nil
480 tc.currBatchTest.l2Txs = nil
481 return nil
482}
483
484// addToL1UserQueue adds the L1UserTx into the queue that is open and has space
485func (tc *Context) addToL1UserQueue(tx L1Tx) error {
486 if len(tc.Queues[tc.openToForge]) >= tc.rollupConstMaxL1UserTx {
487 // if current OpenToForge queue reached its Max, move into a
488 // new queue
489 tc.openToForge++
490 newQueue := []L1Tx{}
491 tc.Queues = append(tc.Queues, newQueue)
492 }
493 // Fill L1UserTx specific parameters
494 tx.L1Tx.UserOrigin = true
495 toForgeL1TxsNum := int64(tc.openToForge)
496 tx.L1Tx.ToForgeL1TxsNum = &toForgeL1TxsNum
497 tx.L1Tx.EthBlockNum = tc.blockNum
498 tx.L1Tx.Position = len(tc.Queues[tc.openToForge])
499
500 // When an L1UserTx is generated, all idxs must be available (except when idx == 0 or idx == 1)
501 if tx.L1Tx.Type != common.TxTypeCreateAccountDeposit && tx.L1Tx.Type != common.TxTypeCreateAccountDepositTransfer {
502 tx.L1Tx.FromIdx = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID].Idx
503 }
504 tx.L1Tx.FromEthAddr = tc.Users[tx.fromIdxName].Addr
505 tx.L1Tx.FromBJJ = tc.Users[tx.fromIdxName].BJJ.Public().Compress()
506 if tx.toIdxName == "" {
507 tx.L1Tx.ToIdx = common.Idx(0)
508 } else {
509 account, ok := tc.Users[tx.toIdxName].Accounts[tx.L1Tx.TokenID]
510 if !ok {
511 return tracerr.Wrap(fmt.Errorf("Line %d: Transfer to User: %s, for TokenID: %d, "+
512 "while account not created yet", tx.lineNum, tx.toIdxName, tx.L1Tx.TokenID))
513 }
514 tx.L1Tx.ToIdx = account.Idx
515 }
516 if tx.L1Tx.Type == common.TxTypeForceExit {
517 tx.L1Tx.ToIdx = common.Idx(1)
518 }
519 nTx, err := common.NewL1Tx(&tx.L1Tx)
520 if err != nil {
521 return tracerr.Wrap(fmt.Errorf("Line %d: %s", tx.lineNum, err.Error()))
522 }
523 tx.L1Tx = *nTx
524
525 tc.Queues[tc.openToForge] = append(tc.Queues[tc.openToForge], tx)
526 tc.currBlock.Rollup.L1UserTxs = append(tc.currBlock.Rollup.L1UserTxs, tx.L1Tx)
527
528 return nil
529}
530
531func (tc *Context) checkIfAccountExists(tf string, inst Instruction) error {
532 if tc.Users[tf].Accounts[inst.TokenID] == nil {
533 return tracerr.Wrap(fmt.Errorf("%s at User: %s, for TokenID: %d, while account not created yet", inst.Typ, tf, inst.TokenID))
534 }
535 return nil
536}
537
538func (tc *Context) checkIfTokenIsRegistered(inst Instruction) error {
539 if inst.TokenID > tc.LastRegisteredTokenID {
540 return tracerr.Wrap(fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.Typ, inst.TokenID, tc.LastRegisteredTokenID))
541 }
542 return nil
543}
544
545// GeneratePoolL2Txs returns an array of common.PoolL2Tx from a given set made
546// of a string. It uses the users (keys) of the Context.
547func (tc *Context) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) {
548 parser := newParser(strings.NewReader(set))
549 parsedSet, err := parser.parse()
550 if err != nil {
551 return nil, tracerr.Wrap(err)
552 }
553 if parsedSet.typ != SetTypePoolL2 {
554 return nil, tracerr.Wrap(fmt.Errorf("Expected set type: %s, found: %s", SetTypePoolL2, parsedSet.typ))
555 }
556
557 tc.instructions = parsedSet.instructions
558 tc.userNames = parsedSet.users
559
560 return tc.generatePoolL2Txs()
561}
562
563// GeneratePoolL2TxsFromInstructions returns an array of common.PoolL2Tx from a
564// given set made of instructions. It uses the users (keys) of the Context.
565func (tc *Context) GeneratePoolL2TxsFromInstructions(set []Instruction) ([]common.PoolL2Tx, error) {
566 userNames := []string{}
567 addedNames := make(map[string]bool)
568 for _, inst := range set {
569 if _, ok := addedNames[inst.From]; !ok {
570 // If the name wasn't already added
571 userNames = append(userNames, inst.From)
572 addedNames[inst.From] = true
573 }
574 if _, ok := addedNames[inst.To]; !ok {
575 // If the name wasn't already added
576 userNames = append(userNames, inst.To)
577 addedNames[inst.To] = true
578 }
579 }
580 tc.userNames = userNames
581 tc.instructions = set
582
583 return tc.generatePoolL2Txs()
584}
585
586func (tc *Context) generatePoolL2Txs() ([]common.PoolL2Tx, error) {
587 tc.generateKeys(tc.userNames)
588
589 txs := []common.PoolL2Tx{}
590 for _, inst := range tc.instructions {
591 switch inst.Typ {
592 case common.TxTypeTransfer, common.TxTypeTransferToEthAddr, common.TxTypeTransferToBJJ:
593 if err := tc.checkIfAccountExists(inst.From, inst); err != nil {
594 log.Error(err)
595 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
596 }
597 if inst.Typ == common.TxTypeTransfer {
598 // if TxTypeTransfer, need to exist the ToIdx account
599 if err := tc.checkIfAccountExists(inst.To, inst); err != nil {
600 log.Error(err)
601 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
602 }
603 }
604 // if account of receiver does not exist, don't use
605 // ToIdx, and use only ToEthAddr & ToBJJ
606 tx := common.PoolL2Tx{
607 FromIdx: tc.Users[inst.From].Accounts[inst.TokenID].Idx,
608 TokenID: inst.TokenID,
609 Amount: inst.Amount,
610 Fee: common.FeeSelector(inst.Fee),
611 Nonce: tc.Users[inst.From].Accounts[inst.TokenID].Nonce,
612 State: common.PoolL2TxStatePending,
613 Timestamp: time.Now(),
614 RqToEthAddr: common.EmptyAddr,
615 RqToBJJ: common.EmptyBJJComp,
616 Type: inst.Typ,
617 }
618 tc.Users[inst.From].Accounts[inst.TokenID].Nonce++
619 if tx.Type == common.TxTypeTransfer {
620 tx.ToIdx = tc.Users[inst.To].Accounts[inst.TokenID].Idx
621 tx.ToEthAddr = tc.Users[inst.To].Addr
622 tx.ToBJJ = tc.Users[inst.To].BJJ.Public().Compress()
623 } else if tx.Type == common.TxTypeTransferToEthAddr {
624 tx.ToIdx = common.Idx(0)
625 tx.ToEthAddr = tc.Users[inst.To].Addr
626 } else if tx.Type == common.TxTypeTransferToBJJ {
627 tx.ToIdx = common.Idx(0)
628 tx.ToEthAddr = common.FFAddr
629 tx.ToBJJ = tc.Users[inst.To].BJJ.Public().Compress()
630 }
631 nTx, err := common.NewPoolL2Tx(&tx)
632 if err != nil {
633 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
634 }
635 tx = *nTx
636 // perform signature and set it to tx.Signature
637 toSign, err := tx.HashToSign(tc.chainID)
638 if err != nil {
639 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
640 }
641 sig := tc.Users[inst.From].BJJ.SignPoseidon(toSign)
642 tx.Signature = sig.Compress()
643
644 txs = append(txs, tx)
645 case common.TxTypeExit:
646 tx := common.PoolL2Tx{
647 FromIdx: tc.Users[inst.From].Accounts[inst.TokenID].Idx,
648 ToIdx: common.Idx(1), // as is an Exit
649 Fee: common.FeeSelector(inst.Fee),
650 TokenID: inst.TokenID,
651 Amount: inst.Amount,
652 Nonce: tc.Users[inst.From].Accounts[inst.TokenID].Nonce,
653 State: common.PoolL2TxStatePending,
654 Type: common.TxTypeExit,
655 }
656 tc.Users[inst.From].Accounts[inst.TokenID].Nonce++
657 nTx, err := common.NewPoolL2Tx(&tx)
658 if err != nil {
659 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
660 }
661 tx = *nTx
662 // perform signature and set it to tx.Signature
663 toSign, err := tx.HashToSign(tc.chainID)
664 if err != nil {
665 return nil, tracerr.Wrap(fmt.Errorf("Line %d: %s", inst.LineNum, err.Error()))
666 }
667 sig := tc.Users[inst.From].BJJ.SignPoseidon(toSign)
668 tx.Signature = sig.Compress()
669 txs = append(txs, tx)
670 default:
671 return nil, tracerr.Wrap(fmt.Errorf("Line %d: instruction type unrecognized: %s", inst.LineNum, inst.Typ))
672 }
673 }
674
675 return txs, nil
676}
677
678// RestartNonces sets all the Users.Accounts.Nonces to 0
679func (tc *Context) RestartNonces() {
680 for name, user := range tc.Users {
681 for tokenID := range user.Accounts {
682 tc.Users[name].Accounts[tokenID].Nonce = common.Nonce(0)
683 }
684 }
685}
686
687// generateKeys generates BabyJubJub & Address keys for the given list of user
688// names in a deterministic way. This means, that for the same given
689// 'userNames' in a certain order, the keys will be always the same.
690func (tc *Context) generateKeys(userNames []string) {
691 for i := 1; i < len(userNames)+1; i++ {
692 if _, ok := tc.Users[userNames[i-1]]; ok {
693 // account already created
694 continue
695 }
696 // babyjubjub key
697 var sk babyjub.PrivateKey
698 var iBytes [8]byte
699 binary.LittleEndian.PutUint64(iBytes[:], uint64(i))
700 copy(sk[:], iBytes[:]) // only for testing
701
702 // eth address
703 var key ecdsa.PrivateKey
704 key.D = big.NewInt(int64(i)) // only for testing
705 key.PublicKey.X, key.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(key.D.Bytes())
706 key.Curve = ethCrypto.S256()
707 addr := ethCrypto.PubkeyToAddress(key.PublicKey)
708
709 u := User{
710 Name: userNames[i-1],
711 BJJ: &sk,
712 EthSk: &key,
713 Addr: addr,
714 Accounts: make(map[common.TokenID]*Account),
715 }
716 tc.Users[userNames[i-1]] = &u
717 }
718}
719
720// L1TxsToCommonL1Txs converts an array of []til.L1Tx to []common.L1Tx
721func L1TxsToCommonL1Txs(l1 []L1Tx) []common.L1Tx {
722 var r []common.L1Tx
723 for i := 0; i < len(l1); i++ {
724 r = append(r, l1[i].L1Tx)
725 }
726 return r
727}
728
729// ConfigExtra is the configuration used in FillBlocksExtra to extend the
730// blocks returned by til.
731type ConfigExtra struct {
732 // Address to set as forger for each batch
733 BootCoordAddr ethCommon.Address
734 // Coordinator user name used to select the corresponding accounts to
735 // collect coordinator fees
736 CoordUser string
737}
738
739// FillBlocksL1UserTxsBatchNum fills the BatchNum of forged L1UserTxs:
740// - blocks[].Rollup.L1UserTxs[].BatchNum
741func (tc *Context) FillBlocksL1UserTxsBatchNum(blocks []common.BlockData) {
742 for i := range blocks {
743 block := &blocks[i]
744 for j := range block.Rollup.Batches {
745 batch := &block.Rollup.Batches[j]
746 if batch.L1Batch {
747 // Set BatchNum for forged L1UserTxs to til blocks
748 bn := batch.Batch.BatchNum
749 for k := range blocks {
750 block := &blocks[k]
751 for l := range block.Rollup.L1UserTxs {
752 tx := &block.Rollup.L1UserTxs[l]
753 if *tx.ToForgeL1TxsNum == tc.extra.openToForge {
754 tx.BatchNum = &bn
755 }
756 }
757 }
758 tc.extra.openToForge++
759 }
760 }
761 }
762}
763
764// FillBlocksForgedL1UserTxs fills the L1UserTxs of a batch with the L1UserTxs
765// that are forged in that batch. It always sets `EffectiveAmount` = `Amount`
766// and `EffectiveDepositAmount` = `DepositAmount`. This function requires a
767// previous call to `FillBlocksExtra`.
768// - blocks[].Rollup.L1UserTxs[].BatchNum
769// - blocks[].Rollup.L1UserTxs[].EffectiveAmount
770// - blocks[].Rollup.L1UserTxs[].EffectiveDepositAmount
771// - blocks[].Rollup.L1UserTxs[].EffectiveFromIdx
772func (tc *Context) FillBlocksForgedL1UserTxs(blocks []common.BlockData) error {
773 for i := range blocks {
774 block := &blocks[i]
775 for j := range block.Rollup.Batches {
776 batch := &block.Rollup.Batches[j]
777 if batch.L1Batch {
778 batchNum := batch.Batch.BatchNum
779 queue := tc.Queues[int(*batch.Batch.ForgeL1TxsNum)]
780 batch.L1UserTxs = make([]common.L1Tx, len(queue))
781 for k := range queue {
782 tx := &batch.L1UserTxs[k]
783 *tx = queue[k].L1Tx
784 tx.EffectiveAmount = tx.Amount
785 tx.EffectiveDepositAmount = tx.DepositAmount
786 tx.BatchNum = &batchNum
787 _tx, err := common.NewL1Tx(tx)
788 if err != nil {
789 return tracerr.Wrap(err)
790 }
791 *tx = *_tx
792 if tx.FromIdx == 0 {
793 tx.EffectiveFromIdx = tc.extra.idxByTxID[tx.TxID]
794 } else {
795 tx.EffectiveFromIdx = tx.FromIdx
796 }
797 }
798 }
799 }
800 }
801 return nil
802}
803
804// FillBlocksExtra fills extra fields not generated by til in each block, so
805// that the blockData is closer to what the HistoryDB stores. The filled
806// fields are:
807// - blocks[].Rollup.Batch.EthBlockNum
808// - blocks[].Rollup.Batch.ForgerAddr
809// - blocks[].Rollup.Batch.ForgeL1TxsNum
810// - blocks[].Rollup.Batch.L1CoordinatorTxs[].TxID
811// - blocks[].Rollup.Batch.L1CoordinatorTxs[].BatchNum
812// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EthBlockNum
813// - blocks[].Rollup.Batch.L1CoordinatorTxs[].Position
814// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EffectiveAmount
815// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EffectiveDepositAmount
816// - blocks[].Rollup.Batch.L1CoordinatorTxs[].EffectiveFromIdx
817// - blocks[].Rollup.Batch.L2Txs[].TxID
818// - blocks[].Rollup.Batch.L2Txs[].Position
819// - blocks[].Rollup.Batch.L2Txs[].Nonce
820// - blocks[].Rollup.Batch.L2Txs[].TokenID
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 tx.TokenID = tc.accountsByIdx[int(tx.FromIdx)].TokenID
919 tc.extra.nonces[tx.FromIdx]++
920 if err := tx.SetID(); err != nil {
921 return tracerr.Wrap(err)
922 }
923 nTx, err := common.NewL2Tx(tx)
924 if err != nil {
925 return tracerr.Wrap(err)
926 }
927 *tx = *nTx
928 }
929 }
930 }
931
932 // Fill ExitTree (only AccountIdx and Balance)
933 for i := range blocks {
934 block := &blocks[i]
935 for j := range block.Rollup.Batches {
936 batch := &block.Rollup.Batches[j]
937 if batch.L1Batch {
938 for _, _tx := range tc.Queues[*batch.Batch.ForgeL1TxsNum] {
939 tx := _tx.L1Tx
940 if tx.Type == common.TxTypeForceExit {
941 batch.ExitTree =
942 append(batch.ExitTree,
943 common.ExitInfo{
944 BatchNum: batch.Batch.BatchNum,
945 AccountIdx: tx.FromIdx,
946 Balance: tx.Amount,
947 })
948 }
949 }
950 }
951 for k := range batch.L2Txs {
952 tx := &batch.L2Txs[k]
953 if tx.Type == common.TxTypeExit {
954 batch.ExitTree = append(batch.ExitTree, common.ExitInfo{
955 BatchNum: batch.Batch.BatchNum,
956 AccountIdx: tx.FromIdx,
957 Balance: tx.Amount,
958 })
959 }
960 fee, err := common.CalcFeeAmount(tx.Amount, tx.Fee)
961 if err != nil {
962 return tracerr.Wrap(err)
963 }
964
965 // Find the TokenID of the tx
966 fromAcc, ok := tc.accountsByIdx[int(tx.FromIdx)]
967 if !ok {
968 return tracerr.Wrap(fmt.Errorf("L2tx.FromIdx idx: %v not found", tx.FromIdx))
969 }
970
971 // Find the idx of the CoordUser for the
972 // TokenID, and if it exists, add the fee to
973 // the collectedFees. Only consider the
974 // coordinator account to receive fee if it was
975 // created in this or a previous batch
976 if acc, ok := tc.l1CreatedAccounts[idxTokenIDToString(cfg.CoordUser, fromAcc.TokenID)]; ok &&
977 common.BatchNum(acc.BatchNum) <= batch.Batch.BatchNum {
978 found := false
979 for _, idx := range batch.Batch.FeeIdxsCoordinator {
980 if idx == common.Idx(acc.Idx) {
981 found = true
982 break
983 }
984 }
985 if !found {
986 batch.Batch.FeeIdxsCoordinator = append(batch.Batch.FeeIdxsCoordinator,
987 common.Idx(acc.Idx))
988 batch.Batch.CollectedFees[fromAcc.TokenID] = big.NewInt(0)
989 }
990 collected := batch.Batch.CollectedFees[fromAcc.TokenID]
991 collected.Add(collected, fee)
992 }
993 }
994 }
995 }
996 return nil
997}