hermez-node/txselector/txselector_test.go
txselector/txselector_test.goRaw
1package txselector
2
3import (
4 "crypto/ecdsa"
5 "fmt"
6 "io/ioutil"
7 "math/big"
8 "os"
9 "strconv"
10 "testing"
11 "time"
12
13 ethCommon "github.com/ethereum/go-ethereum/common"
14 ethCrypto "github.com/ethereum/go-ethereum/crypto"
15 "github.com/hermeznetwork/hermez-node/common"
16 dbUtils "github.com/hermeznetwork/hermez-node/db"
17 "github.com/hermeznetwork/hermez-node/db/historydb"
18 "github.com/hermeznetwork/hermez-node/db/l2db"
19 "github.com/hermeznetwork/hermez-node/db/statedb"
20 "github.com/hermeznetwork/hermez-node/log"
21 "github.com/hermeznetwork/hermez-node/test"
22 "github.com/hermeznetwork/hermez-node/test/til"
23 "github.com/hermeznetwork/hermez-node/txprocessor"
24 "github.com/iden3/go-iden3-crypto/babyjub"
25 "github.com/jmoiron/sqlx"
26 "github.com/stretchr/testify/assert"
27 "github.com/stretchr/testify/require"
28)
29
30func initTest(t *testing.T, chainID uint16, hermezContractAddr ethCommon.Address, testSet string) (*TxSelector, *til.Context) {
31 pass := os.Getenv("POSTGRES_PASS")
32 db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
33 require.NoError(t, err)
34 l2DB := l2db.NewL2DB(db, 10, 100, 24*time.Hour)
35
36 dir, err := ioutil.TempDir("", "tmpdb")
37 require.NoError(t, err)
38 defer assert.NoError(t, os.RemoveAll(dir))
39 sdb, err := statedb.NewStateDB(dir, 128, statedb.TypeTxSelector, 0)
40 require.NoError(t, err)
41
42 txselDir, err := ioutil.TempDir("", "tmpTxSelDB")
43 require.NoError(t, err)
44 defer assert.NoError(t, os.RemoveAll(dir))
45
46 // coordinator keys
47 var ethSk ecdsa.PrivateKey
48 ethSk.D = big.NewInt(int64(1)) // only for testing
49 ethSk.PublicKey.X, ethSk.PublicKey.Y = ethCrypto.S256().ScalarBaseMult(ethSk.D.Bytes())
50 ethSk.Curve = ethCrypto.S256()
51 addr := ethCrypto.PubkeyToAddress(ethSk.PublicKey)
52 var bjj babyjub.PublicKeyComp
53 err = bjj.UnmarshalText([]byte("c433f7a696b7aa3a5224efb3993baf0ccd9e92eecee0c29a3f6c8208a9e81d9e"))
54 require.NoError(t, err)
55 coordAccount := &CoordAccount{
56 Addr: addr,
57 BJJ: bjj,
58 AccountCreationAuth: nil,
59 }
60 auth := common.AccountCreationAuth{
61 EthAddr: addr,
62 BJJ: bjj,
63 }
64 err = auth.Sign(func(hash []byte) ([]byte, error) {
65 return ethCrypto.Sign(hash, ðSk)
66 }, chainID, hermezContractAddr)
67 assert.NoError(t, err)
68 coordAccount.AccountCreationAuth = auth.Signature
69
70 txsel, err := NewTxSelector(coordAccount, txselDir, sdb, l2DB)
71 require.NoError(t, err)
72
73 test.WipeDB(txsel.l2db.DB())
74
75 tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
76
77 return txsel, tc
78}
79
80func addAccCreationAuth(t *testing.T, tc *til.Context, txsel *TxSelector, chainID uint16, hermezContractAddr ethCommon.Address, username string) []byte {
81 user := tc.Users[username]
82 auth := &common.AccountCreationAuth{
83 EthAddr: user.Addr,
84 BJJ: user.BJJ.Public().Compress(),
85 }
86 err := auth.Sign(func(hash []byte) ([]byte, error) {
87 return ethCrypto.Sign(hash, user.EthSk)
88 }, chainID, hermezContractAddr)
89 assert.NoError(t, err)
90
91 err = txsel.l2db.AddAccountCreationAuth(auth)
92 assert.NoError(t, err)
93 return auth.Signature
94}
95
96func addL2Txs(t *testing.T, txsel *TxSelector, poolL2Txs []common.PoolL2Tx) {
97 for i := 0; i < len(poolL2Txs); i++ {
98 err := txsel.l2db.AddTxTest(&poolL2Txs[i])
99 if err != nil {
100 log.Error(err)
101 }
102 require.NoError(t, err)
103 }
104}
105
106func addTokens(t *testing.T, tc *til.Context, db *sqlx.DB) {
107 var tokens []common.Token
108 for i := 0; i < int(tc.LastRegisteredTokenID); i++ {
109 tokens = append(tokens, common.Token{
110 TokenID: common.TokenID(i + 1),
111 EthBlockNum: 1,
112 EthAddr: ethCommon.BytesToAddress([]byte{byte(i + 1)}),
113 Name: strconv.Itoa(i),
114 Symbol: strconv.Itoa(i),
115 Decimals: 18,
116 })
117 }
118
119 hdb := historydb.NewHistoryDB(db)
120 assert.NoError(t, hdb.AddBlock(&common.Block{
121 Num: 1,
122 }))
123 assert.NoError(t, hdb.AddTokens(tokens))
124}
125
126func checkBalance(t *testing.T, tc *til.Context, txsel *TxSelector, username string, tokenid int, expected string) {
127 // Accounts.Idx does not match with the TxSelector tests as we are not
128 // using the Til L1CoordinatorTxs (as are generated by the TxSelector
129 // itself when processing the txs, so the Idxs does not match the Til
130 // idxs). But the Idx is obtained through StateDB.GetIdxByEthAddrBJJ
131 user := tc.Users[username]
132 idx, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(user.Addr, user.BJJ.Public().Compress(), common.TokenID(tokenid))
133 require.NoError(t, err)
134 checkBalanceByIdx(t, txsel, idx, expected)
135}
136
137func checkBalanceByIdx(t *testing.T, txsel *TxSelector, idx common.Idx, expected string) {
138 acc, err := txsel.localAccountsDB.GetAccount(idx)
139 require.NoError(t, err)
140 assert.Equal(t, expected, acc.Balance.String())
141}
142
143// checkSortedByNonce takes as input testAccNonces map, and the array of
144// common.PoolL2Txs, and checks if the nonces correspond to the accumulated
145// values of the map. Also increases the Nonces computed on the map.
146func checkSortedByNonce(t *testing.T, testAccNonces map[common.Idx]common.Nonce, txs []common.PoolL2Tx) {
147 for _, tx := range txs {
148 assert.True(t, testAccNonces[tx.FromIdx] == tx.Nonce,
149 fmt.Sprintf("Idx: %d, expected: %d, tx.Nonce: %d",
150 tx.FromIdx, testAccNonces[tx.FromIdx], tx.Nonce))
151 testAccNonces[tx.FromIdx] = testAccNonces[tx.FromIdx] + 1
152 }
153}
154
155func TestGetL2TxSelectionMinimumFlow0(t *testing.T) {
156 chainID := uint16(0)
157 hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
158 txsel, tc := initTest(t, chainID, hermezContractAddr, til.SetPool0)
159
160 // generate test transactions, the L1CoordinatorTxs generated by Til
161 // will be ignored at this test, as will be the TxSelector who
162 // generates them when needed
163 blocks, err := tc.GenerateBlocks(til.SetBlockchainMinimumFlow0)
164 assert.NoError(t, err)
165
166 // restart nonces of TilContext, as will be set by generating directly
167 // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs
168 tc.RestartNonces()
169 testAccNonces := make(map[common.Idx]common.Nonce)
170
171 // add tokens to HistoryDB to avoid breaking FK constrains
172 addTokens(t, tc, txsel.l2db.DB())
173
174 tpc := txprocessor.Config{
175 NLevels: 16,
176 MaxFeeTx: 10,
177 MaxTx: 20,
178 MaxL1Tx: 10,
179 ChainID: chainID,
180 }
181 selectionConfig := &SelectionConfig{
182 MaxL1UserTxs: 5,
183 TxProcessorConfig: tpc,
184 }
185
186 // coordIdxs, accAuths, l1UserTxs, l1CoordTxs, l2Txs, err
187
188 log.Debug("block:0 batch:0")
189 l1UserTxs := []common.L1Tx{}
190 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
191 require.NoError(t, err)
192 assert.Equal(t, 0, len(oL1UserTxs))
193 assert.Equal(t, 0, len(oL1CoordTxs))
194 assert.Equal(t, 0, len(oL2Txs))
195 assert.Equal(t, common.BatchNum(1), txsel.localAccountsDB.CurrentBatch())
196 assert.Equal(t, common.Idx(255), txsel.localAccountsDB.CurrentIdx())
197
198 log.Debug("block:0 batch:1")
199 l1UserTxs = []common.L1Tx{}
200 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
201 require.NoError(t, err)
202 assert.Equal(t, 0, len(oL1UserTxs))
203 assert.Equal(t, 0, len(oL1CoordTxs))
204 assert.Equal(t, 0, len(oL2Txs))
205 assert.Equal(t, common.BatchNum(2), txsel.localAccountsDB.CurrentBatch())
206 assert.Equal(t, common.Idx(255), txsel.localAccountsDB.CurrentIdx())
207
208 log.Debug("block:0 batch:2")
209 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[2].Batch.ForgeL1TxsNum])
210 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
211 require.NoError(t, err)
212 assert.Equal(t, 2, len(oL1UserTxs))
213 assert.Equal(t, 0, len(oL1CoordTxs))
214 assert.Equal(t, 0, len(oL2Txs))
215 assert.Equal(t, common.BatchNum(3), txsel.localAccountsDB.CurrentBatch())
216 assert.Equal(t, common.Idx(257), txsel.localAccountsDB.CurrentIdx())
217 checkBalance(t, tc, txsel, "A", 0, "500")
218 checkBalance(t, tc, txsel, "C", 1, "0")
219
220 log.Debug("block:0 batch:3")
221 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[3].Batch.ForgeL1TxsNum])
222 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
223 require.NoError(t, err)
224 assert.Equal(t, 1, len(oL1UserTxs))
225 assert.Equal(t, 0, len(oL1CoordTxs))
226 assert.Equal(t, 0, len(oL2Txs))
227 assert.Equal(t, common.BatchNum(4), txsel.localAccountsDB.CurrentBatch())
228 assert.Equal(t, common.Idx(258), txsel.localAccountsDB.CurrentIdx())
229 checkBalance(t, tc, txsel, "A", 0, "500")
230 checkBalance(t, tc, txsel, "A", 1, "500")
231 checkBalance(t, tc, txsel, "C", 1, "0")
232
233 log.Debug("block:0 batch:4")
234 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[4].Batch.ForgeL1TxsNum])
235 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
236 require.NoError(t, err)
237 assert.Equal(t, 0, len(oL1UserTxs))
238 assert.Equal(t, 0, len(oL1CoordTxs))
239 assert.Equal(t, 0, len(oL2Txs))
240 assert.Equal(t, common.BatchNum(5), txsel.localAccountsDB.CurrentBatch())
241 assert.Equal(t, common.Idx(258), txsel.localAccountsDB.CurrentIdx())
242 checkBalance(t, tc, txsel, "A", 0, "500")
243 checkBalance(t, tc, txsel, "A", 1, "500")
244 checkBalance(t, tc, txsel, "C", 1, "0")
245
246 log.Debug("block:0 batch:5")
247 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[5].Batch.ForgeL1TxsNum])
248 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
249 require.NoError(t, err)
250 assert.Equal(t, 1, len(oL1UserTxs))
251 assert.Equal(t, 0, len(oL1CoordTxs))
252 assert.Equal(t, 0, len(oL2Txs))
253 assert.Equal(t, common.BatchNum(6), txsel.localAccountsDB.CurrentBatch())
254 assert.Equal(t, common.Idx(259), txsel.localAccountsDB.CurrentIdx())
255 checkBalance(t, tc, txsel, "A", 0, "600")
256 checkBalance(t, tc, txsel, "A", 1, "500")
257 checkBalance(t, tc, txsel, "B", 0, "400")
258 checkBalance(t, tc, txsel, "C", 1, "0")
259
260 log.Debug("block:0 batch:6")
261 // simulate the PoolL2Txs of the batch6
262 batchPoolL2 := `
263 Type: PoolL2
264 PoolTransfer(1) A-B: 200 (126)
265 PoolTransfer(0) B-C: 100 (126)`
266 poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2)
267 require.NoError(t, err)
268 // add AccountCreationAuths that will be used at the next batch
269 accAuthSig0 := addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "B")
270 accAuthSig1 := addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "C")
271 // add ToEthAddr for the corresponent ToIdx, and remove ToIdx for Batches[6].L2Tx
272 poolL2Txs[0].ToEthAddr = tc.Users["B"].Addr
273 poolL2Txs[0].ToIdx = common.Idx(0)
274 poolL2Txs[1].ToEthAddr = tc.Users["C"].Addr
275 poolL2Txs[1].ToIdx = common.Idx(0)
276 // add the PoolL2Txs to the l2DB
277 addL2Txs(t, txsel, poolL2Txs)
278
279 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[6].Batch.ForgeL1TxsNum])
280 coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
281 require.NoError(t, err)
282 assert.Equal(t, []common.Idx{261, 262}, coordIdxs)
283 assert.Equal(t, txsel.coordAccount.AccountCreationAuth, accAuths[0])
284 assert.Equal(t, txsel.coordAccount.AccountCreationAuth, accAuths[1])
285 assert.Equal(t, accAuthSig0, accAuths[2])
286 assert.Equal(t, accAuthSig1, accAuths[3])
287 assert.Equal(t, 1, len(oL1UserTxs))
288 assert.Equal(t, 4, len(oL1CoordTxs))
289 assert.Equal(t, 2, len(oL2Txs))
290 assert.Equal(t, len(oL1CoordTxs), len(accAuths))
291 assert.Equal(t, common.BatchNum(7), txsel.localAccountsDB.CurrentBatch())
292 assert.Equal(t, common.Idx(264), txsel.localAccountsDB.CurrentIdx())
293 checkBalanceByIdx(t, txsel, 261, "20") // CoordIdx for TokenID=1
294 checkBalanceByIdx(t, txsel, 262, "10") // CoordIdx for TokenID=0
295 checkBalance(t, tc, txsel, "A", 0, "600")
296 checkBalance(t, tc, txsel, "A", 1, "280")
297 checkBalance(t, tc, txsel, "B", 0, "290")
298 checkBalance(t, tc, txsel, "B", 1, "200")
299 checkBalance(t, tc, txsel, "C", 0, "100")
300 checkBalance(t, tc, txsel, "D", 0, "800")
301 checkSortedByNonce(t, testAccNonces, oL2Txs)
302 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(poolL2Txs), txsel.localAccountsDB.CurrentBatch())
303 require.NoError(t, err)
304
305 log.Debug("block:0 batch:7")
306 // simulate the PoolL2Txs of the batch7
307 batchPoolL2 = `
308 Type: PoolL2
309 PoolTransfer(0) A-B: 100 (126)
310 PoolTransfer(0) C-A: 50 (126)
311 PoolTransfer(1) B-C: 100 (126)
312 PoolExit(0) A: 100 (126)`
313 poolL2Txs, err = tc.GeneratePoolL2Txs(batchPoolL2)
314 require.NoError(t, err)
315 addL2Txs(t, txsel, poolL2Txs)
316 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[7].Batch.ForgeL1TxsNum])
317 coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
318 require.NoError(t, err)
319 assert.Equal(t, []common.Idx{261, 262}, coordIdxs)
320 assert.Equal(t, 0, len(accAuths))
321 assert.Equal(t, 0, len(oL1UserTxs))
322 assert.Equal(t, 0, len(oL1CoordTxs))
323 assert.Equal(t, 4, len(oL2Txs))
324 assert.Equal(t, len(oL1CoordTxs), len(accAuths))
325 assert.Equal(t, common.BatchNum(8), txsel.localAccountsDB.CurrentBatch())
326 assert.Equal(t, common.Idx(264), txsel.localAccountsDB.CurrentIdx())
327 checkBalanceByIdx(t, txsel, 261, "30")
328 checkBalanceByIdx(t, txsel, 262, "35")
329 checkBalance(t, tc, txsel, "A", 0, "430")
330 checkBalance(t, tc, txsel, "A", 1, "280")
331 checkBalance(t, tc, txsel, "B", 0, "390")
332 checkBalance(t, tc, txsel, "B", 1, "90")
333 checkBalance(t, tc, txsel, "C", 0, "45")
334 checkBalance(t, tc, txsel, "C", 1, "100")
335 checkBalance(t, tc, txsel, "D", 0, "800")
336 checkSortedByNonce(t, testAccNonces, oL2Txs)
337 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(poolL2Txs), txsel.localAccountsDB.CurrentBatch())
338 require.NoError(t, err)
339
340 log.Debug("block:1 batch:0")
341 // simulate the PoolL2Txs of the batch6
342 batchPoolL2 = `
343 Type: PoolL2
344 PoolTransfer(0) D-A: 300 (126)
345 PoolTransfer(0) B-D: 100 (126)
346 `
347 poolL2Txs, err = tc.GeneratePoolL2Txs(batchPoolL2)
348 require.NoError(t, err)
349 addL2Txs(t, txsel, poolL2Txs)
350 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[1].Rollup.Batches[0].Batch.ForgeL1TxsNum])
351 coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs)
352 require.NoError(t, err)
353 assert.Equal(t, []common.Idx{262}, coordIdxs)
354 assert.Equal(t, 0, len(accAuths))
355 assert.Equal(t, 4, len(oL1UserTxs))
356 assert.Equal(t, 0, len(oL1CoordTxs))
357 assert.Equal(t, 2, len(oL2Txs))
358 assert.Equal(t, len(oL1CoordTxs), len(accAuths))
359 assert.Equal(t, common.BatchNum(9), txsel.localAccountsDB.CurrentBatch())
360 assert.Equal(t, common.Idx(264), txsel.localAccountsDB.CurrentIdx())
361 checkBalanceByIdx(t, txsel, 261, "30")
362 checkBalanceByIdx(t, txsel, 262, "75")
363 checkBalance(t, tc, txsel, "A", 0, "730")
364 checkBalance(t, tc, txsel, "A", 1, "280")
365 checkBalance(t, tc, txsel, "B", 0, "380")
366 checkBalance(t, tc, txsel, "B", 1, "90")
367 checkBalance(t, tc, txsel, "C", 0, "845")
368 checkBalance(t, tc, txsel, "C", 1, "100")
369 checkBalance(t, tc, txsel, "D", 0, "470")
370 checkSortedByNonce(t, testAccNonces, oL2Txs)
371 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(poolL2Txs), txsel.localAccountsDB.CurrentBatch())
372 require.NoError(t, err)
373}