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