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