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