diff --git a/common/account.go b/common/account.go index 3318db9..092ecc3 100644 --- a/common/account.go +++ b/common/account.go @@ -26,11 +26,11 @@ const ( // maxIdxValue is the maximum value that Idx can have (48 bits: maxIdxValue=2**48-1) maxIdxValue = 0xffffffffffff - // userThreshold determines the threshold from the User Idxs can be - userThreshold = 256 + // UserThreshold determines the threshold from the User Idxs can be + UserThreshold = 256 // IdxUserThreshold is a Idx type value that determines the threshold // from the User Idxs can be - IdxUserThreshold = Idx(userThreshold) + IdxUserThreshold = Idx(UserThreshold) ) var ( diff --git a/db/statedb/txprocessors_test.go b/db/statedb/txprocessors_test.go index 13c2d1f..620dea6 100644 --- a/db/statedb/txprocessors_test.go +++ b/db/statedb/txprocessors_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/eth" "github.com/hermeznetwork/hermez-node/test/transakcio" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,46 +22,54 @@ func TestProcessTxsSynchronizer(t *testing.T) { assert.Nil(t, err) // generate test transactions from test.SetTest0 code - tc := transakcio.NewTestContext() + tc := transakcio.NewTestContext(eth.RollupConstMaxL1UserTx) blocks, err := tc.GenerateBlocks(transakcio.SetBlockchain0) require.Nil(t, err) - assert.Equal(t, 29, len(blocks[0].Batches[0].L1UserTxs)) + assert.Equal(t, 29, len(blocks[0].L1UserTxs)) assert.Equal(t, 0, len(blocks[0].Batches[0].L1CoordinatorTxs)) - assert.Equal(t, 21, len(blocks[0].Batches[0].L2Txs)) - assert.Equal(t, 1, len(blocks[0].Batches[1].L1UserTxs)) - assert.Equal(t, 1, len(blocks[0].Batches[1].L1CoordinatorTxs)) - assert.Equal(t, 59, len(blocks[0].Batches[1].L2Txs)) - assert.Equal(t, 9, len(blocks[0].Batches[2].L1UserTxs)) - assert.Equal(t, 1, len(blocks[0].Batches[2].L1CoordinatorTxs)) - assert.Equal(t, 8, len(blocks[0].Batches[2].L2Txs)) + assert.Equal(t, 21, len(blocks[0].Batches[1].L2Txs)) + assert.Equal(t, 1, len(blocks[1].Batches[0].L1CoordinatorTxs)) + assert.Equal(t, 59, len(blocks[1].Batches[0].L2Txs)) + assert.Equal(t, 1, len(blocks[1].Batches[1].L1CoordinatorTxs)) + assert.Equal(t, 8, len(blocks[1].Batches[1].L2Txs)) // use first batch l2Txs := common.L2TxsToPoolL2Txs(blocks[0].Batches[0].L2Txs) - _, exitInfos, err := sdb.ProcessTxs(blocks[0].Batches[0].L1UserTxs, blocks[0].Batches[0].L1CoordinatorTxs, l2Txs) + _, exitInfos, err := sdb.ProcessTxs(blocks[0].L1UserTxs, blocks[0].Batches[0].L1CoordinatorTxs, l2Txs) + require.Nil(t, err) + assert.Equal(t, 0, len(exitInfos)) + acc, err := sdb.GetAccount(common.Idx(256)) + require.Nil(t, err) + assert.Equal(t, "50", acc.Balance.String()) + + // second batch of first block + l2Txs = common.L2TxsToPoolL2Txs(blocks[0].Batches[1].L2Txs) + _, exitInfos, err = sdb.ProcessTxs(nil, blocks[0].Batches[1].L1CoordinatorTxs, l2Txs) require.Nil(t, err) // TODO once TTGL is updated, add a check that a input poolL2Tx with // Nonce & TokenID =0, after ProcessTxs call has the expected value assert.Equal(t, 0, len(exitInfos)) - acc, err := sdb.GetAccount(common.Idx(256)) - assert.Nil(t, err) + acc, err = sdb.GetAccount(common.Idx(256)) + require.Nil(t, err) assert.Equal(t, "28", acc.Balance.String()) // use second batch - l2Txs = common.L2TxsToPoolL2Txs(blocks[0].Batches[1].L2Txs) - _, exitInfos, err = sdb.ProcessTxs(blocks[0].Batches[1].L1UserTxs, blocks[0].Batches[1].L1CoordinatorTxs, l2Txs) + l2Txs = common.L2TxsToPoolL2Txs(blocks[1].Batches[0].L2Txs) + _, exitInfos, err = sdb.ProcessTxs(nil, blocks[1].Batches[0].L1CoordinatorTxs, l2Txs) require.Nil(t, err) - assert.Equal(t, 5, len(exitInfos)) + assert.Equal(t, 4, len(exitInfos)) // the 'ForceExit(1)' is not computed yet, as the batch is without L1UserTxs acc, err = sdb.GetAccount(common.Idx(256)) require.Nil(t, err) - assert.Equal(t, "48", acc.Balance.String()) + assert.Equal(t, "53", acc.Balance.String()) // use third batch - l2Txs = common.L2TxsToPoolL2Txs(blocks[0].Batches[2].L2Txs) - _, exitInfos, err = sdb.ProcessTxs(blocks[0].Batches[2].L1UserTxs, blocks[0].Batches[2].L1CoordinatorTxs, l2Txs) + l2Txs = common.L2TxsToPoolL2Txs(blocks[1].Batches[1].L2Txs) + _, exitInfos, err = sdb.ProcessTxs(blocks[1].L1UserTxs, blocks[1].Batches[1].L1CoordinatorTxs, l2Txs) require.Nil(t, err) - assert.Equal(t, 1, len(exitInfos)) + + assert.Equal(t, 2, len(exitInfos)) // 2, as previous batch was without L1UserTxs, and has pending the 'ForceExit(1) A: 5' acc, err = sdb.GetAccount(common.Idx(256)) assert.Nil(t, err) assert.Equal(t, "73", acc.Balance.String()) diff --git a/test/transakcio/lang.go b/test/transakcio/lang.go index 5115374..9d75721 100644 --- a/test/transakcio/lang.go +++ b/test/transakcio/lang.go @@ -31,6 +31,10 @@ var setTypePoolL2 = setType("PoolL2") // common.TxType of a new batch var typeNewBatch common.TxType = "InstrTypeNewBatch" +// typeNewBatchL1 is used for testing purposes only, and represents the +// common.TxType of a new batch +var typeNewBatchL1 common.TxType = "InstrTypeNewBatchL1" + // typeNewBlock is used for testing purposes only, and represents the // common.TxType of a new ethereum block var typeNewBlock common.TxType = "InstrTypeNewBlock" @@ -53,6 +57,7 @@ const ( // instruction is the data structure that represents one line of code type instruction struct { + lineNum int literal string from string to string @@ -280,6 +285,9 @@ func (p *parser) parseLine(setType setType) (*instruction, error) { if lit == "batch" { _, _ = p.s.r.ReadString('\n') return &instruction{typ: typeNewBatch}, newEventLine + } else if lit == "batchL1" { + _, _ = p.s.r.ReadString('\n') + return &instruction{typ: typeNewBatchL1}, newEventLine } else if lit == "block" { _, _ = p.s.r.ReadString('\n') return &instruction{typ: typeNewBlock}, newEventLine @@ -484,52 +492,50 @@ func idxTokenIDToString(idx string, tid common.TokenID) string { // parse parses through reader func (p *parser) parse() (*parsedSet, error) { ps := &parsedSet{} - i := 0 + i := 0 // lines will start counting at line 1 accounts := make(map[string]bool) var setTypeOfSet setType for { + i++ instruction, err := p.parseLine(setTypeOfSet) if err == errof { break } if err == setTypeLine { if setTypeOfSet != "" { - return ps, fmt.Errorf("Instruction of 'Type: %s' when there is already a previous instruction 'Type: %s' defined", instruction.typ, setTypeOfSet) + return ps, fmt.Errorf("Line %d: Instruction of 'Type: %s' when there is already a previous instruction 'Type: %s' defined", i, instruction.typ, setTypeOfSet) } if instruction.typ == "PoolL2" { setTypeOfSet = setTypePoolL2 } else if instruction.typ == "Blockchain" { setTypeOfSet = setTypeBlockchain } else { - log.Fatalf("Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", instruction.typ) + log.Fatalf("Line %d: Invalid set type: '%s'. Valid set types: 'Blockchain', 'PoolL2'", i, instruction.typ) } - i++ continue } if err == commentLine { - i++ continue } + instruction.lineNum = i if err == newEventLine { if instruction.typ == typeRegisterToken && instruction.tokenID == common.TokenID(0) { - return ps, fmt.Errorf("RegisterToken can not register TokenID 0") + return ps, fmt.Errorf("Line %d: RegisterToken can not register TokenID 0", i) } - i++ ps.instructions = append(ps.instructions, *instruction) continue } if err != nil { - return ps, fmt.Errorf("error parsing line %d: %s, err: %s", i, instruction.literal, err.Error()) + return ps, fmt.Errorf("Line %d: %s, err: %s", i, instruction.literal, err.Error()) } if setTypeOfSet == "" { - return ps, fmt.Errorf("Set type not defined") + return ps, fmt.Errorf("Line %d: Set type not defined", i) } ps.instructions = append(ps.instructions, *instruction) accounts[instruction.from] = true if instruction.typ == common.TxTypeTransfer { // type: Transfer accounts[instruction.to] = true } - i++ } for a := range accounts { ps.accounts = append(ps.accounts, a) diff --git a/test/transakcio/lang_test.go b/test/transakcio/lang_test.go index 2e44ad3..b56f057 100644 --- a/test/transakcio/lang_test.go +++ b/test/transakcio/lang_test.go @@ -117,7 +117,7 @@ func TestParseErrors(t *testing.T) { ` parser := newParser(strings.NewReader(s)) _, err := parser.parse() - assert.Equal(t, "error parsing line 1: Deposit(1)A:: 10\n, err: strconv.Atoi: parsing \":\": invalid syntax", err.Error()) + assert.Equal(t, "Line 2: Deposit(1)A:: 10\n, err: strconv.Atoi: parsing \":\": invalid syntax", err.Error()) s = ` Type: Blockchain @@ -126,7 +126,7 @@ func TestParseErrors(t *testing.T) { ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 3: 20, err: Unexpected Blockchain tx type: 20", err.Error()) + assert.Equal(t, "Line 4: 20, err: Unexpected Blockchain tx type: 20", err.Error()) s = ` Type: Blockchain @@ -134,7 +134,7 @@ func TestParseErrors(t *testing.T) { ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 1: Transfer(1)A:, err: Expected '-', found ':'", err.Error()) + assert.Equal(t, "Line 2: Transfer(1)A:, err: Expected '-', found ':'", err.Error()) s = ` Type: Blockchain @@ -142,7 +142,7 @@ func TestParseErrors(t *testing.T) { ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 1: Transfer(1)AB, err: Expected '-', found 'B'", err.Error()) + assert.Equal(t, "Line 2: Transfer(1)AB, err: Expected '-', found 'B'", err.Error()) s = ` Type: Blockchain @@ -158,7 +158,7 @@ func TestParseErrors(t *testing.T) { ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 1: Transfer(1)A-B:10(256)\n, err: Fee 256 can not be bigger than 255", err.Error()) + assert.Equal(t, "Line 2: Transfer(1)A-B:10(256)\n, err: Fee 256 can not be bigger than 255", err.Error()) // check that the PoolTransfer & Transfer are only accepted in the // correct case case (PoolTxs/BlockchainTxs) @@ -168,14 +168,14 @@ func TestParseErrors(t *testing.T) { ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 1: Transfer, err: Unexpected PoolL2 tx type: Transfer", err.Error()) + assert.Equal(t, "Line 2: Transfer, err: Unexpected PoolL2 tx type: Transfer", err.Error()) s = ` Type: Blockchain PoolTransfer(1) A-B: 10 (1) ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 1: PoolTransfer, err: Unexpected Blockchain tx type: PoolTransfer", err.Error()) + assert.Equal(t, "Line 2: PoolTransfer, err: Unexpected Blockchain tx type: PoolTransfer", err.Error()) s = ` Type: Blockchain @@ -183,27 +183,27 @@ func TestParseErrors(t *testing.T) { ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 1: >, err: Unexpected '> btch', expected '> batch' or '> block'", err.Error()) + assert.Equal(t, "Line 2: >, err: Unexpected '> btch', expected '> batch' or '> block'", err.Error()) // check definition of set Type s = `PoolTransfer(1) A-B: 10 (1)` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 0: PoolTransfer, err: Set type not defined", err.Error()) + assert.Equal(t, "Line 1: PoolTransfer, err: Set type not defined", err.Error()) s = `Type: PoolL1` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 0: Type:, err: Invalid set type: 'PoolL1'. Valid set types: 'Blockchain', 'PoolL2'", err.Error()) + assert.Equal(t, "Line 1: Type:, err: Invalid set type: 'PoolL1'. Valid set types: 'Blockchain', 'PoolL2'", err.Error()) s = `Type: PoolL1 Type: Blockchain` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "error parsing line 0: Type:, err: Invalid set type: 'PoolL1'. Valid set types: 'Blockchain', 'PoolL2'", err.Error()) + assert.Equal(t, "Line 1: Type:, err: Invalid set type: 'PoolL1'. Valid set types: 'Blockchain', 'PoolL2'", err.Error()) s = `Type: PoolL2 Type: Blockchain` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "Instruction of 'Type: Blockchain' when there is already a previous instruction 'Type: PoolL2' defined", err.Error()) + assert.Equal(t, "Line 2: Instruction of 'Type: Blockchain' when there is already a previous instruction 'Type: PoolL2' defined", err.Error()) s = `Type: Blockchain RegisterToken(1) @@ -211,5 +211,5 @@ func TestParseErrors(t *testing.T) { ` parser = newParser(strings.NewReader(s)) _, err = parser.parse() - assert.Equal(t, "RegisterToken can not register TokenID 0", err.Error()) + assert.Equal(t, "Line 3: RegisterToken can not register TokenID 0", err.Error()) } diff --git a/test/transakcio/sets.go b/test/transakcio/sets.go index 5006c15..867bd20 100644 --- a/test/transakcio/sets.go +++ b/test/transakcio/sets.go @@ -42,6 +42,9 @@ CreateAccountDeposit(2) B: 5 CreateAccountDeposit(2) A: 20 // deposits TokenID: 3 CreateAccountDeposit(3) B: 100 + +> batchL1 + // transactions TokenID: 1 Transfer(1) A-B: 5 (1) Transfer(1) A-L: 10 (1) @@ -65,7 +68,8 @@ Transfer(1) H-K: 3 (2) Transfer(1) H-K: 3 (1) Transfer(1) H-K: 3 (1) -> batch +> batchL1 +> block // A (3) still does not exist, coordinator should create new L1Tx to create the account CreateAccountDepositCoordinator(3) A @@ -132,6 +136,7 @@ Exit(1) Y: 5 Exit(1) Z: 5 > batch + Deposit(1) A: 50 Deposit(1) B: 5 Deposit(1) C: 20 @@ -152,6 +157,10 @@ Exit(1) A: 5 // create CoordinatorTx CreateAccount for D, TokenId 2, used at SetPool0 for 'PoolTransfer(2) B-D: 3 (1)' CreateAccountDepositCoordinator(2) D + +> batchL1 +> batchL1 +> block ` // SetPool0 contains a set of transactions from the PoolL2 diff --git a/test/transakcio/sets_test.go b/test/transakcio/sets_test.go index 39f1cbf..deea003 100644 --- a/test/transakcio/sets_test.go +++ b/test/transakcio/sets_test.go @@ -4,6 +4,7 @@ import ( "strings" "testing" + "github.com/hermeznetwork/hermez-node/eth" "github.com/stretchr/testify/assert" ) @@ -15,7 +16,7 @@ func TestCompileSets(t *testing.T) { _, err = parser.parse() assert.Nil(t, err) - tc := NewTestContext() + tc := NewTestContext(eth.RollupConstMaxL1UserTx) _, err = tc.GenerateBlocks(SetBlockchain0) assert.Nil(t, err) _, err = tc.GenerateBlocks(SetPool0) diff --git a/test/transakcio/txs.go b/test/transakcio/txs.go index 3895b1a..511c9e5 100644 --- a/test/transakcio/txs.go +++ b/test/transakcio/txs.go @@ -11,6 +11,7 @@ import ( ethCommon "github.com/ethereum/go-ethereum/common" ethCrypto "github.com/ethereum/go-ethereum/crypto" "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/log" "github.com/iden3/go-iden3-crypto/babyjub" ) @@ -21,13 +22,33 @@ type TestContext struct { Users map[string]*User lastRegisteredTokenID common.TokenID l1CreatedAccounts map[string]*Account + + // rollupConstMaxL1UserTx Maximum L1-user transactions allowed to be queued in a batch + rollupConstMaxL1UserTx int + + idx int + currBlock BlockData + currBatch BatchData + currBatchNum int + queues [][]L1Tx + toForgeNum int + openToForge int } // NewTestContext returns a new TestContext -func NewTestContext() *TestContext { +func NewTestContext(rollupConstMaxL1UserTx int) *TestContext { return &TestContext{ - Users: make(map[string]*User), - l1CreatedAccounts: make(map[string]*Account), + Users: make(map[string]*User), + l1CreatedAccounts: make(map[string]*Account), + lastRegisteredTokenID: 0, + + rollupConstMaxL1UserTx: rollupConstMaxL1UserTx, + idx: common.UserThreshold, + currBatchNum: 0, + // start with 2 queues, one for toForge, and the other for openToForge + queues: make([][]L1Tx, 2), + toForgeNum: 0, + openToForge: 1, } } @@ -47,7 +68,7 @@ type User struct { // BlockData contains the information of a Block type BlockData struct { // block *common.Block // ethereum block - // L1UserTxs that were submitted in the block + // L1UserTxs that were accepted in the block L1UserTxs []common.L1Tx Batches []BatchData RegisteredTokens []common.Token @@ -55,14 +76,36 @@ type BlockData struct { // BatchData contains the information of a Batch type BatchData struct { - L1Batch bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer - // L1UserTxs that were forged in the batch - L1UserTxs []common.L1Tx - L1CoordinatorTxs []common.L1Tx - L2Txs []common.L2Tx - CreatedAccounts []common.Account - ExitTree []common.ExitInfo - Batch *common.Batch + L1Batch bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer + L1CoordinatorTxs []common.L1Tx + testL1CoordinatorTxs []L1Tx + L2Txs []common.L2Tx + // testL2Tx are L2Txs without the Idx&EthAddr&BJJ setted, but with the + // string that represents the account + testL2Txs []L2Tx + CreatedAccounts []common.Account +} + +// L1Tx is the data structure used internally for transaction test generation, +// which contains a common.L1Tx data plus some intermediate data for the +// transaction generation. +type L1Tx struct { + lineNum int + fromIdxName string + toIdxName string + + L1Tx common.L1Tx +} + +// L2Tx is the data structure used internally for transaction test generation, +// which contains a common.L2Tx data plus some intermediate data for the +// transaction generation. +type L2Tx struct { + lineNum int + fromIdxName string + toIdxName string + tokenID common.TokenID + L2Tx common.L2Tx } // GenerateBlocks returns an array of BlockData for a given set. It uses the @@ -80,167 +123,276 @@ func (tc *TestContext) GenerateBlocks(set string) ([]BlockData, error) { tc.generateKeys(tc.accountsNames) var blocks []BlockData - currBatchNum := 0 - var currBlock BlockData - var currBatch BatchData - idx := 256 for _, inst := range parsedSet.instructions { switch inst.typ { - case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer, txTypeCreateAccountDepositCoordinator: + case txTypeCreateAccountDepositCoordinator: if err := tc.checkIfTokenIsRegistered(inst); err != nil { - return nil, err + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx := common.L1Tx{ - // TxID FromEthAddr: tc.Users[inst.from].Addr, FromBJJ: tc.Users[inst.from].BJJ.Public(), TokenID: inst.tokenID, LoadAmount: big.NewInt(int64(inst.loadAmount)), - Type: inst.typ, + Type: common.TxTypeCreateAccountDeposit, // as txTypeCreateAccountDepositCoordinator is not valid oustide Transakcio package } - if tc.Users[inst.from].Accounts[inst.tokenID] == nil { // if account is not set yet, set it and increment idx - tc.Users[inst.from].Accounts[inst.tokenID] = &Account{ - Idx: common.Idx(idx), - Nonce: common.Nonce(0), - } - - tc.l1CreatedAccounts[idxTokenIDToString(inst.from, inst.tokenID)] = tc.Users[inst.from].Accounts[inst.tokenID] - idx++ + testTx := L1Tx{ + lineNum: inst.lineNum, + fromIdxName: inst.from, + L1Tx: tx, } - if inst.typ == common.TxTypeCreateAccountDepositTransfer { - tx.Amount = big.NewInt(int64(inst.amount)) - } - if inst.typ == txTypeCreateAccountDepositCoordinator { - tx.Type = common.TxTypeCreateAccountDeposit // as txTypeCreateAccountDepositCoordinator is not valid oustide Transakcio package - currBatch.L1CoordinatorTxs = append(currBatch.L1CoordinatorTxs, tx) - } else { - currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx) - } - case common.TxTypeDeposit, common.TxTypeDepositTransfer: + tc.currBatch.testL1CoordinatorTxs = append(tc.currBatch.testL1CoordinatorTxs, testTx) + case common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDepositTransfer: if err := tc.checkIfTokenIsRegistered(inst); err != nil { - return nil, err - } - if tc.Users[inst.from].Accounts[inst.tokenID] == nil { - return nil, fmt.Errorf("Deposit at User %s for TokenID %d while account not created yet", inst.from, inst.tokenID) + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx := common.L1Tx{ - // TxID - FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx, FromEthAddr: tc.Users[inst.from].Addr, FromBJJ: tc.Users[inst.from].BJJ.Public(), TokenID: inst.tokenID, LoadAmount: big.NewInt(int64(inst.loadAmount)), Type: inst.typ, } - if tc.Users[inst.from].Accounts[inst.tokenID].Idx == common.Idx(0) { - // if account.Idx is not set yet, set it and increment idx - tc.Users[inst.from].Accounts[inst.tokenID].Idx = common.Idx(idx) - - tc.l1CreatedAccounts[idxTokenIDToString(inst.from, inst.tokenID)] = tc.Users[inst.from].Accounts[inst.tokenID] - idx++ + if inst.typ == common.TxTypeCreateAccountDepositTransfer { + tx.Amount = big.NewInt(int64(inst.amount)) + } + testTx := L1Tx{ + lineNum: inst.lineNum, + fromIdxName: inst.from, + toIdxName: inst.to, + L1Tx: tx, + } + tc.addToL1Queue(testTx) + case common.TxTypeDeposit, common.TxTypeDepositTransfer: + if err := tc.checkIfTokenIsRegistered(inst); err != nil { + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) + } + if err := tc.checkIfAccountExists(inst.from, inst); err != nil { + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) + } + tx := common.L1Tx{ + TokenID: inst.tokenID, + LoadAmount: big.NewInt(int64(inst.loadAmount)), + Type: inst.typ, } if inst.typ == common.TxTypeDepositTransfer { tx.Amount = big.NewInt(int64(inst.amount)) - // if ToIdx is not set yet, set it and increment idx - if tc.Users[inst.to].Accounts[inst.tokenID].Idx == common.Idx(0) { - tc.Users[inst.to].Accounts[inst.tokenID].Idx = common.Idx(idx) - - tc.l1CreatedAccounts[idxTokenIDToString(inst.to, inst.tokenID)] = tc.Users[inst.to].Accounts[inst.tokenID] - tx.ToIdx = common.Idx(idx) - idx++ - } else { - // if Idx account of To already exist, use it for ToIdx - tx.ToIdx = tc.Users[inst.to].Accounts[inst.tokenID].Idx - } } - currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx) + testTx := L1Tx{ + lineNum: inst.lineNum, + fromIdxName: inst.from, + toIdxName: inst.to, + L1Tx: tx, + } + tc.addToL1Queue(testTx) case common.TxTypeTransfer: if err := tc.checkIfTokenIsRegistered(inst); err != nil { - return nil, err - } - if tc.Users[inst.from].Accounts[inst.tokenID] == nil { - return nil, fmt.Errorf("Transfer from User %s for TokenID %d while account not created yet", inst.from, inst.tokenID) + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } - // if account of receiver does not exist, create a new CoordinatorL1Tx creating the account - if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(inst.to, inst.tokenID)]; !ok { - return nil, fmt.Errorf("Can not create Transfer for a non existing account. Batch %d, Instruction: %s", currBatchNum, inst) - } - tc.Users[inst.from].Accounts[inst.tokenID].Nonce++ tx := common.L2Tx{ - FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx, - ToIdx: tc.Users[inst.to].Accounts[inst.tokenID].Idx, - Amount: big.NewInt(int64(inst.amount)), - Fee: common.FeeSelector(inst.fee), - Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce, - Type: common.TxTypeTransfer, + Amount: big.NewInt(int64(inst.amount)), + Fee: common.FeeSelector(inst.fee), + Type: common.TxTypeTransfer, } nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx()) if err != nil { - return nil, err + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx = nTx.L2Tx() - tx.BatchNum = common.BatchNum(currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost - - currBatch.L2Txs = append(currBatch.L2Txs, tx) + tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost + testTx := L2Tx{ + lineNum: inst.lineNum, + fromIdxName: inst.from, + toIdxName: inst.to, + tokenID: inst.tokenID, + L2Tx: tx, + } + tc.currBatch.testL2Txs = append(tc.currBatch.testL2Txs, testTx) case common.TxTypeExit: if err := tc.checkIfTokenIsRegistered(inst); err != nil { - return nil, err + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } - tc.Users[inst.from].Accounts[inst.tokenID].Nonce++ tx := common.L2Tx{ - FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx, - ToIdx: common.Idx(1), // as is an Exit - Amount: big.NewInt(int64(inst.amount)), - Nonce: tc.Users[inst.from].Accounts[inst.tokenID].Nonce, - Type: common.TxTypeExit, + ToIdx: common.Idx(1), // as is an Exit + Amount: big.NewInt(int64(inst.amount)), + Type: common.TxTypeExit, } - nTx, err := common.NewPoolL2Tx(tx.PoolL2Tx()) - if err != nil { - return nil, err + tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost + testTx := L2Tx{ + lineNum: inst.lineNum, + fromIdxName: inst.from, + toIdxName: inst.to, + tokenID: inst.tokenID, + L2Tx: tx, } - tx = nTx.L2Tx() - currBatch.L2Txs = append(currBatch.L2Txs, tx) + tc.currBatch.testL2Txs = append(tc.currBatch.testL2Txs, testTx) case common.TxTypeForceExit: if err := tc.checkIfTokenIsRegistered(inst); err != nil { - return nil, err + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx := common.L1Tx{ - FromIdx: tc.Users[inst.from].Accounts[inst.tokenID].Idx, ToIdx: common.Idx(1), // as is an Exit TokenID: inst.tokenID, Amount: big.NewInt(int64(inst.amount)), Type: common.TxTypeExit, } - currBatch.L1UserTxs = append(currBatch.L1UserTxs, tx) + testTx := L1Tx{ + lineNum: inst.lineNum, + fromIdxName: inst.from, + toIdxName: inst.to, + L1Tx: tx, + } + tc.addToL1Queue(testTx) case typeNewBatch: - currBlock.Batches = append(currBlock.Batches, currBatch) - currBatchNum++ - currBatch = BatchData{} + if err = tc.calculateIdxForL1Txs(true, tc.currBatch.testL1CoordinatorTxs); err != nil { + return nil, err + } + if err = tc.setIdxs(); err != nil { + log.Error(err) + return nil, err + } + case typeNewBatchL1: + // for each L1UserTx of the queues[ToForgeNum], calculate the Idx + if err = tc.calculateIdxForL1Txs(false, tc.queues[tc.toForgeNum]); err != nil { + return nil, err + } + if err = tc.calculateIdxForL1Txs(true, tc.currBatch.testL1CoordinatorTxs); err != nil { + return nil, err + } + + // once Idxs are calculated, update transactions to use the new Idxs + for i := 0; i < len(tc.queues[tc.toForgeNum]); i++ { + testTx := &tc.queues[tc.toForgeNum][i] + // set real Idx + testTx.L1Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.L1Tx.TokenID].Idx + testTx.L1Tx.FromEthAddr = tc.Users[testTx.fromIdxName].Addr + testTx.L1Tx.FromBJJ = tc.Users[testTx.fromIdxName].BJJ.Public() + if testTx.toIdxName == "" { + testTx.L1Tx.ToIdx = common.Idx(0) + } else { + testTx.L1Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.L1Tx.TokenID].Idx + } + tc.currBlock.L1UserTxs = append(tc.currBlock.L1UserTxs, testTx.L1Tx) + } + if err = tc.setIdxs(); err != nil { + log.Error(err) + return nil, err + } + // advance batch + tc.toForgeNum++ + if tc.toForgeNum == tc.openToForge { + tc.openToForge++ + newQueue := []L1Tx{} + tc.queues = append(tc.queues, newQueue) + } case typeNewBlock: - currBlock.Batches = append(currBlock.Batches, currBatch) - currBatchNum++ - currBatch = BatchData{} - blocks = append(blocks, currBlock) - currBlock = BlockData{} + blocks = append(blocks, tc.currBlock) + tc.currBlock = BlockData{} case typeRegisterToken: newToken := common.Token{ TokenID: inst.tokenID, EthBlockNum: int64(len(blocks)), } if inst.tokenID != tc.lastRegisteredTokenID+1 { - return nil, fmt.Errorf("RegisterToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", tc.lastRegisteredTokenID+1, inst.tokenID) + return nil, fmt.Errorf("Line %d: RegisterToken TokenID should be sequential, expected TokenID: %d, defined TokenID: %d", inst.lineNum, tc.lastRegisteredTokenID+1, inst.tokenID) } tc.lastRegisteredTokenID++ - currBlock.RegisteredTokens = append(currBlock.RegisteredTokens, newToken) + tc.currBlock.RegisteredTokens = append(tc.currBlock.RegisteredTokens, newToken) default: - return nil, fmt.Errorf("Unexpected type: %s", inst.typ) + return nil, fmt.Errorf("Line %d: Unexpected type: %s", inst.lineNum, inst.typ) } } - currBlock.Batches = append(currBlock.Batches, currBatch) - blocks = append(blocks, currBlock) return blocks, nil } + +// calculateIdxsForL1Txs calculates new Idx for new created accounts. If +// 'isCoordinatorTxs==true', adds the tx to tc.currBatch.L1CoordinatorTxs. +func (tc *TestContext) calculateIdxForL1Txs(isCoordinatorTxs bool, txs []L1Tx) error { + // for each batch.L1CoordinatorTxs of the queues[ToForgeNum], calculate the Idx + for i := 0; i < len(txs); i++ { + tx := txs[i] + if tx.L1Tx.Type == common.TxTypeCreateAccountDeposit || tx.L1Tx.Type == common.TxTypeCreateAccountDepositTransfer { + if tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] != nil { // if account already exists, return error + return fmt.Errorf("Can not create same account twice (same User & same TokenID) (this is a design property of Transakcio)") + } + tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] = &Account{ + Idx: common.Idx(tc.idx), + Nonce: common.Nonce(0), + } + tc.l1CreatedAccounts[idxTokenIDToString(tx.fromIdxName, tx.L1Tx.TokenID)] = tc.Users[tx.fromIdxName].Accounts[tx.L1Tx.TokenID] + tc.idx++ + } + if isCoordinatorTxs { + tc.currBatch.L1CoordinatorTxs = append(tc.currBatch.L1CoordinatorTxs, tx.L1Tx) + } + } + return nil +} + +// setIdxs sets the Idxs to the transactions of the tc.currBatch +func (tc *TestContext) setIdxs() error { + // once Idxs are calculated, update transactions to use the new Idxs + for i := 0; i < len(tc.currBatch.testL2Txs); i++ { + testTx := &tc.currBatch.testL2Txs[i] + + if tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID] == nil { + return 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) + } + if testTx.L2Tx.Type == common.TxTypeTransfer { + if _, ok := tc.l1CreatedAccounts[idxTokenIDToString(testTx.toIdxName, testTx.tokenID)]; !ok { + return 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) + } + } + tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce++ + testTx.L2Tx.Nonce = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Nonce + + // set real Idx + testTx.L2Tx.FromIdx = tc.Users[testTx.fromIdxName].Accounts[testTx.tokenID].Idx + if testTx.L2Tx.Type == common.TxTypeTransfer { + testTx.L2Tx.ToIdx = tc.Users[testTx.toIdxName].Accounts[testTx.tokenID].Idx + } + nTx, err := common.NewL2Tx(&testTx.L2Tx) + if err != nil { + return err + } + testTx.L2Tx = *nTx + + tc.currBatch.L2Txs = append(tc.currBatch.L2Txs, testTx.L2Tx) + } + + tc.currBlock.Batches = append(tc.currBlock.Batches, tc.currBatch) + tc.currBatchNum++ + tc.currBatch = BatchData{} + + return nil +} + +// addToL1Queue adds the L1Tx into the queue that is open and has space +func (tc *TestContext) addToL1Queue(tx L1Tx) { + if len(tc.queues[tc.openToForge]) >= tc.rollupConstMaxL1UserTx { + // if current OpenToForge queue reached its Max, move into a + // new queue + tc.openToForge++ + newQueue := []L1Tx{} + tc.queues = append(tc.queues, newQueue) + } + tc.queues[tc.openToForge] = append(tc.queues[tc.openToForge], tx) +} + +func (tc *TestContext) checkIfAccountExists(tf string, inst instruction) error { + if tc.Users[tf].Accounts[inst.tokenID] == nil { + return fmt.Errorf("%s at User: %s, for TokenID: %d, while account not created yet", inst.typ, tf, inst.tokenID) + } + return nil +} func (tc *TestContext) checkIfTokenIsRegistered(inst instruction) error { if inst.tokenID > tc.lastRegisteredTokenID { return fmt.Errorf("Can not process %s: TokenID %d not registered, last registered TokenID: %d", inst.typ, inst.tokenID, tc.lastRegisteredTokenID) @@ -266,11 +418,13 @@ func (tc *TestContext) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) for _, inst := range tc.Instructions { switch inst.typ { case common.TxTypeTransfer: - if tc.Users[inst.from].Accounts[inst.tokenID] == nil { - return nil, fmt.Errorf("Transfer from User %s for TokenID %d while account not created yet", inst.from, inst.tokenID) + if err := tc.checkIfAccountExists(inst.from, inst); err != nil { + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } - if tc.Users[inst.to].Accounts[inst.tokenID] == nil { - return nil, fmt.Errorf("Transfer to User %s for TokenID %d while account not created yet", inst.to, inst.tokenID) + if err := tc.checkIfAccountExists(inst.to, inst); err != nil { + log.Error(err) + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tc.Users[inst.from].Accounts[inst.tokenID].Nonce++ // if account of receiver does not exist, don't use @@ -292,13 +446,13 @@ func (tc *TestContext) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) } nTx, err := common.NewPoolL2Tx(&tx) if err != nil { - return nil, err + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx = *nTx // perform signature and set it to tx.Signature toSign, err := tx.HashToSign() if err != nil { - return nil, err + return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } sig := tc.Users[inst.to].BJJ.SignPoseidon(toSign) tx.Signature = sig @@ -316,7 +470,7 @@ func (tc *TestContext) GeneratePoolL2Txs(set string) ([]common.PoolL2Tx, error) } txs = append(txs, tx) default: - return nil, fmt.Errorf("instruction type unrecognized: %s", inst.typ) + return nil, fmt.Errorf("Line %d: instruction type unrecognized: %s", inst.lineNum, inst.typ) } } diff --git a/test/transakcio/txs_test.go b/test/transakcio/txs_test.go index 88f7748..ee7db83 100644 --- a/test/transakcio/txs_test.go +++ b/test/transakcio/txs_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/eth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,6 +23,9 @@ func TestGenerateBlocks(t *testing.T) { CreateAccountDeposit(1) C: 5 CreateAccountDepositTransfer(1) D-A: 15, 10 (3) + > batchL1 + > batchL1 + Transfer(1) A-B: 6 (1) Transfer(1) B-D: 3 (1) Transfer(1) A-D: 1 (1) @@ -40,11 +44,12 @@ func TestGenerateBlocks(t *testing.T) { CreateAccountDeposit(3) User1: 20 CreateAccountDepositCoordinator(1) User1 CreateAccountDepositCoordinator(3) User0 + > batchL1 Transfer(1) User0-User1: 15 (1) Transfer(3) User1-User0: 15 (1) Transfer(1) A-C: 1 (1) - > batch + > batchL1 Transfer(1) User1-User0: 1 (1) @@ -53,59 +58,64 @@ func TestGenerateBlocks(t *testing.T) { // Exits Transfer(1) A-B: 1 (1) Exit(1) A: 5 + + > batch + > block + + // this transaction should not be generated, as it's after last + // batch and last block + Transfer(1) User1-User0: 1 (1) ` - tc := NewTestContext() + tc := NewTestContext(eth.RollupConstMaxL1UserTx) blocks, err := tc.GenerateBlocks(set) require.Nil(t, err) assert.Equal(t, 2, len(blocks)) - assert.Equal(t, 3, len(blocks[0].Batches)) + assert.Equal(t, 5, len(blocks[0].Batches)) assert.Equal(t, 1, len(blocks[1].Batches)) - assert.Equal(t, 5, len(blocks[0].Batches[0].L1UserTxs)) - assert.Equal(t, 4, len(blocks[0].Batches[1].L1CoordinatorTxs)) - assert.Equal(t, 0, len(blocks[1].Batches[0].L1UserTxs)) + assert.Equal(t, 8, len(blocks[0].L1UserTxs)) + assert.Equal(t, 4, len(blocks[0].Batches[3].L1CoordinatorTxs)) + assert.Equal(t, 0, len(blocks[1].L1UserTxs)) // Check expected values generated by each line // #0: Deposit(1) A: 10 - tc.checkL1TxParams(t, blocks[0].Batches[0].L1UserTxs[0], common.TxTypeCreateAccountDeposit, 1, "A", "", big.NewInt(10), nil) + tc.checkL1TxParams(t, blocks[0].L1UserTxs[0], common.TxTypeCreateAccountDeposit, 1, "A", "", big.NewInt(10), nil) // #1: Deposit(2) A: 20 - tc.checkL1TxParams(t, blocks[0].Batches[0].L1UserTxs[1], common.TxTypeCreateAccountDeposit, 2, "A", "", big.NewInt(20), nil) - // #2: Deposit(1) A: 20 - tc.checkL1TxParams(t, blocks[0].Batches[0].L1UserTxs[2], common.TxTypeCreateAccountDeposit, 1, "B", "", big.NewInt(5), nil) - // #3: CreateAccountDeposit(1) C: 5 - tc.checkL1TxParams(t, blocks[0].Batches[0].L1UserTxs[3], common.TxTypeCreateAccountDeposit, 1, "C", "", big.NewInt(5), nil) - // #4: CreateAccountDepositTransfer(1) D-A: 15, 10 (3) - tc.checkL1TxParams(t, blocks[0].Batches[0].L1UserTxs[4], common.TxTypeCreateAccountDepositTransfer, 1, "D", "A", big.NewInt(15), big.NewInt(10)) + tc.checkL1TxParams(t, blocks[0].L1UserTxs[1], common.TxTypeCreateAccountDeposit, 2, "A", "", big.NewInt(20), nil) + // // #2: Deposit(1) A: 20 + tc.checkL1TxParams(t, blocks[0].L1UserTxs[2], common.TxTypeCreateAccountDeposit, 1, "B", "", big.NewInt(5), nil) + // // #3: CreateAccountDeposit(1) C: 5 + tc.checkL1TxParams(t, blocks[0].L1UserTxs[3], common.TxTypeCreateAccountDeposit, 1, "C", "", big.NewInt(5), nil) + // // #4: CreateAccountDepositTransfer(1) D-A: 15, 10 (3) + tc.checkL1TxParams(t, blocks[0].L1UserTxs[4], common.TxTypeCreateAccountDepositTransfer, 1, "D", "A", big.NewInt(15), big.NewInt(10)) // #5: Transfer(1) A-B: 6 (1) - tc.checkL2TxParams(t, blocks[0].Batches[0].L2Txs[0], common.TxTypeTransfer, 1, "A", "B", big.NewInt(6), common.BatchNum(0), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[0], common.TxTypeTransfer, 1, "A", "B", big.NewInt(6), common.BatchNum(2), common.Nonce(1)) // #6: Transfer(1) B-D: 3 (1) - tc.checkL2TxParams(t, blocks[0].Batches[0].L2Txs[1], common.TxTypeTransfer, 1, "B", "D", big.NewInt(3), common.BatchNum(0), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[1], common.TxTypeTransfer, 1, "B", "D", big.NewInt(3), common.BatchNum(2), common.Nonce(1)) // #7: Transfer(1) A-D: 1 (1) - tc.checkL2TxParams(t, blocks[0].Batches[0].L2Txs[2], common.TxTypeTransfer, 1, "A", "D", big.NewInt(1), common.BatchNum(0), common.Nonce(2)) + tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[2], common.TxTypeTransfer, 1, "A", "D", big.NewInt(1), common.BatchNum(2), common.Nonce(2)) // change of Batch // #8: DepositTransfer(1) A-B: 15, 10 (1) - tc.checkL1TxParams(t, blocks[0].Batches[1].L1UserTxs[0], common.TxTypeDepositTransfer, 1, "A", "B", big.NewInt(15), big.NewInt(10)) - // #9: Deposit(1) User0: 20 - tc.checkL1TxParams(t, blocks[0].Batches[1].L1UserTxs[0], common.TxTypeDepositTransfer, 1, "A", "B", big.NewInt(15), big.NewInt(10)) + tc.checkL1TxParams(t, blocks[0].L1UserTxs[5], common.TxTypeDepositTransfer, 1, "A", "B", big.NewInt(15), big.NewInt(10)) // #10: Transfer(1) C-A : 3 (1) - tc.checkL2TxParams(t, blocks[0].Batches[1].L2Txs[0], common.TxTypeTransfer, 1, "C", "A", big.NewInt(3), common.BatchNum(1), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[3].L2Txs[0], common.TxTypeTransfer, 1, "C", "A", big.NewInt(3), common.BatchNum(3), common.Nonce(1)) // #11: Transfer(2) A-B: 15 (1) - tc.checkL2TxParams(t, blocks[0].Batches[1].L2Txs[1], common.TxTypeTransfer, 2, "A", "B", big.NewInt(15), common.BatchNum(1), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[3].L2Txs[1], common.TxTypeTransfer, 2, "A", "B", big.NewInt(15), common.BatchNum(3), common.Nonce(1)) // #12: Deposit(1) User0: 20 - tc.checkL1TxParams(t, blocks[0].Batches[1].L1UserTxs[1], common.TxTypeCreateAccountDeposit, 1, "User0", "", big.NewInt(20), nil) - // #13: Deposit(3) User1: 20 - tc.checkL1TxParams(t, blocks[0].Batches[1].L1UserTxs[2], common.TxTypeCreateAccountDeposit, 3, "User1", "", big.NewInt(20), nil) + tc.checkL1TxParams(t, blocks[0].L1UserTxs[6], common.TxTypeCreateAccountDeposit, 1, "User0", "", big.NewInt(20), nil) + // // #13: Deposit(3) User1: 20 + tc.checkL1TxParams(t, blocks[0].L1UserTxs[7], common.TxTypeCreateAccountDeposit, 3, "User1", "", big.NewInt(20), nil) // #14: Transfer(1) User0-User1: 15 (1) - tc.checkL2TxParams(t, blocks[0].Batches[1].L2Txs[3], common.TxTypeTransfer, 1, "User0", "User1", big.NewInt(15), common.BatchNum(1), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[0], common.TxTypeTransfer, 1, "User0", "User1", big.NewInt(15), common.BatchNum(4), common.Nonce(1)) // #15: Transfer(3) User1-User0: 15 (1) - tc.checkL2TxParams(t, blocks[0].Batches[1].L2Txs[4], common.TxTypeTransfer, 3, "User1", "User0", big.NewInt(15), common.BatchNum(1), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[1], common.TxTypeTransfer, 3, "User1", "User0", big.NewInt(15), common.BatchNum(4), common.Nonce(1)) // #16: Transfer(1) A-C: 1 (1) - tc.checkL2TxParams(t, blocks[0].Batches[1].L2Txs[5], common.TxTypeTransfer, 1, "A", "C", big.NewInt(1), common.BatchNum(1), common.Nonce(4)) + tc.checkL2TxParams(t, blocks[0].Batches[4].L2Txs[2], common.TxTypeTransfer, 1, "A", "C", big.NewInt(1), common.BatchNum(4), common.Nonce(4)) // change of Batch // #17: Transfer(1) User1-User0: 1 (1) - tc.checkL2TxParams(t, blocks[0].Batches[2].L2Txs[0], common.TxTypeTransfer, 1, "User1", "User0", big.NewInt(1), common.BatchNum(2), common.Nonce(1)) + tc.checkL2TxParams(t, blocks[1].Batches[0].L2Txs[0], common.TxTypeTransfer, 1, "User1", "User0", big.NewInt(1), common.BatchNum(5), common.Nonce(1)) // change of Block (implies also a change of batch) // #18: Transfer(1) A-B: 1 (1) - tc.checkL2TxParams(t, blocks[1].Batches[0].L2Txs[0], common.TxTypeTransfer, 1, "A", "B", big.NewInt(1), common.BatchNum(3), common.Nonce(5)) + tc.checkL2TxParams(t, blocks[1].Batches[0].L2Txs[1], common.TxTypeTransfer, 1, "A", "B", big.NewInt(1), common.BatchNum(5), common.Nonce(5)) } func (tc *TestContext) checkL1TxParams(t *testing.T, tx common.L1Tx, typ common.TxType, tokenID common.TokenID, from, to string, loadAmount, amount *big.Int) { @@ -155,8 +165,10 @@ func TestGeneratePoolL2Txs(t *testing.T) { CreateAccountDeposit(3) User1: 5 CreateAccountDeposit(2) B: 5 CreateAccountDeposit(2) D: 0 + > batchL1 + > batchL1 ` - tc := NewTestContext() + tc := NewTestContext(eth.RollupConstMaxL1UserTx) _, err := tc.GenerateBlocks(set) require.Nil(t, err) set = ` @@ -203,27 +215,28 @@ func TestGenerateErrors(t *testing.T) { // unregistered token set := `Type: Blockchain CreateAccountDeposit(1) A: 5 + > batchL1 ` - tc := NewTestContext() + tc := NewTestContext(eth.RollupConstMaxL1UserTx) _, err := tc.GenerateBlocks(set) - assert.Equal(t, "Can not process CreateAccountDeposit: TokenID 1 not registered, last registered TokenID: 0", err.Error()) + assert.Equal(t, "Line 2: Can not process CreateAccountDeposit: TokenID 1 not registered, last registered TokenID: 0", err.Error()) // ensure RegisterToken sequentiality and not using 0 set = ` Type: Blockchain RegisterToken(0) ` - tc = NewTestContext() + tc = NewTestContext(eth.RollupConstMaxL1UserTx) _, err = tc.GenerateBlocks(set) - require.Equal(t, "RegisterToken can not register TokenID 0", err.Error()) + require.Equal(t, "Line 2: RegisterToken can not register TokenID 0", err.Error()) set = ` Type: Blockchain RegisterToken(2) ` - tc = NewTestContext() + tc = NewTestContext(eth.RollupConstMaxL1UserTx) _, err = tc.GenerateBlocks(set) - require.Equal(t, "RegisterToken TokenID should be sequential, expected TokenID: 1, defined TokenID: 2", err.Error()) + require.Equal(t, "Line 2: RegisterToken TokenID should be sequential, expected TokenID: 1, defined TokenID: 2", err.Error()) set = ` Type: Blockchain @@ -232,7 +245,57 @@ func TestGenerateErrors(t *testing.T) { RegisterToken(3) RegisterToken(5) ` - tc = NewTestContext() + tc = NewTestContext(eth.RollupConstMaxL1UserTx) + _, err = tc.GenerateBlocks(set) + require.Equal(t, "Line 5: RegisterToken TokenID should be sequential, expected TokenID: 4, defined TokenID: 5", err.Error()) + + // check transactions when account is not created yet + set = ` + Type: Blockchain + RegisterToken(1) + CreateAccountDeposit(1) A: 10 + > batchL1 + CreateAccountDeposit(1) B + Transfer(1) A-B: 6 (1) + > batch + ` + tc = NewTestContext(eth.RollupConstMaxL1UserTx) _, err = tc.GenerateBlocks(set) - require.Equal(t, "RegisterToken TokenID should be sequential, expected TokenID: 4, defined TokenID: 5", err.Error()) + require.Equal(t, "Line 5: CreateAccountDeposit(1)BTransfer(1) A-B: 6 (1)\n, err: Expected ':', found 'Transfer'", err.Error()) + set = ` + Type: Blockchain + RegisterToken(1) + CreateAccountDeposit(1) A: 10 + > batchL1 + CreateAccountDepositCoordinator(1) B + > batchL1 + > batch + Transfer(1) A-B: 6 (1) + > batch + ` + tc = NewTestContext(eth.RollupConstMaxL1UserTx) + _, err = tc.GenerateBlocks(set) + require.Nil(t, err) + + // check nonces + set = ` + Type: Blockchain + RegisterToken(1) + CreateAccountDeposit(1) A: 10 + > batchL1 + CreateAccountDepositCoordinator(1) B + > batchL1 + Transfer(1) A-B: 6 (1) + Transfer(1) A-B: 6 (1) // on purpose this is moving more money that what it has in the account, Transakcio should not fail + Transfer(1) B-A: 6 (1) + Exit(1) A: 3 + > batch + ` + tc = NewTestContext(eth.RollupConstMaxL1UserTx) + _, err = tc.GenerateBlocks(set) + require.Nil(t, err) + assert.Equal(t, common.Nonce(3), tc.Users["A"].Accounts[common.TokenID(1)].Nonce) + assert.Equal(t, common.Idx(256), tc.Users["A"].Accounts[common.TokenID(1)].Idx) + assert.Equal(t, common.Nonce(1), tc.Users["B"].Accounts[common.TokenID(1)].Nonce) + assert.Equal(t, common.Idx(257), tc.Users["B"].Accounts[common.TokenID(1)].Idx) }