hermez-node/txselector/txselector_test.go
txselector/txselector_test.goRaw
1package txselector
2
3import (
4 "fmt"
5 "io/ioutil"
6 "os"
7 "strconv"
8 "testing"
9 "time"
10
11 ethCommon "github.com/ethereum/go-ethereum/common"
12 ethCrypto "github.com/ethereum/go-ethereum/crypto"
13 "github.com/hermeznetwork/hermez-node/common"
14 dbUtils "github.com/hermeznetwork/hermez-node/db"
15 "github.com/hermeznetwork/hermez-node/db/historydb"
16 "github.com/hermeznetwork/hermez-node/db/l2db"
17 "github.com/hermeznetwork/hermez-node/db/statedb"
18 "github.com/hermeznetwork/hermez-node/log"
19 "github.com/hermeznetwork/hermez-node/test"
20 "github.com/hermeznetwork/hermez-node/test/til"
21 "github.com/hermeznetwork/hermez-node/test/txsets"
22 "github.com/hermeznetwork/hermez-node/txprocessor"
23 "github.com/jmoiron/sqlx"
24 "github.com/stretchr/testify/assert"
25 "github.com/stretchr/testify/require"
26)
27
28func initTest(t *testing.T, chainID uint16, hermezContractAddr ethCommon.Address,
29 coordUser *til.User) *TxSelector {
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, db, 10, 100, 0.0, 24*time.Hour, nil)
34
35 dir, err := ioutil.TempDir("", "tmpdb")
36 require.NoError(t, err)
37 defer assert.NoError(t, os.RemoveAll(dir))
38 syncStateDB, err := statedb.NewStateDB(statedb.Config{Path: dir, Keep: 128,
39 Type: statedb.TypeTxSelector, NLevels: 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 // use Til Coord keys for tests compatibility
47 coordAccount := &CoordAccount{
48 Addr: coordUser.Addr,
49 BJJ: coordUser.BJJ.Public().Compress(),
50 AccountCreationAuth: nil,
51 }
52 // fmt.Printf("%v\n", coordAccount)
53 auth := common.AccountCreationAuth{
54 EthAddr: coordUser.Addr,
55 BJJ: coordUser.BJJ.Public().Compress(),
56 }
57 err = auth.Sign(func(hash []byte) ([]byte, error) {
58 return ethCrypto.Sign(hash, coordUser.EthSk)
59 }, chainID, hermezContractAddr)
60 assert.NoError(t, err)
61 coordAccount.AccountCreationAuth = auth.Signature
62
63 txsel, err := NewTxSelector(coordAccount, txselDir, syncStateDB, l2DB)
64 require.NoError(t, err)
65
66 test.WipeDB(txsel.l2db.DB())
67
68 return txsel
69}
70
71func addAccCreationAuth(t *testing.T, tc *til.Context, txsel *TxSelector, chainID uint16,
72 hermezContractAddr ethCommon.Address, username string) []byte {
73 user := tc.Users[username]
74 auth := &common.AccountCreationAuth{
75 EthAddr: user.Addr,
76 BJJ: user.BJJ.Public().Compress(),
77 }
78 err := auth.Sign(func(hash []byte) ([]byte, error) {
79 return ethCrypto.Sign(hash, user.EthSk)
80 }, chainID, hermezContractAddr)
81 assert.NoError(t, err)
82
83 err = txsel.l2db.AddAccountCreationAuth(auth)
84 assert.NoError(t, err)
85 return auth.Signature
86}
87
88func addL2Txs(t *testing.T, txsel *TxSelector, poolL2Txs []common.PoolL2Tx) {
89 for i := 0; i < len(poolL2Txs); i++ {
90 err := txsel.l2db.AddTxTest(&poolL2Txs[i])
91 if err != nil {
92 log.Error(err)
93 }
94 require.NoError(t, err)
95 }
96}
97
98func addTokens(t *testing.T, tc *til.Context, db *sqlx.DB) {
99 var tokens []common.Token
100 for i := 0; i < int(tc.LastRegisteredTokenID); i++ {
101 tokens = append(tokens, common.Token{
102 TokenID: common.TokenID(i + 1),
103 EthBlockNum: 1,
104 EthAddr: ethCommon.BytesToAddress([]byte{byte(i + 1)}),
105 Name: strconv.Itoa(i),
106 Symbol: strconv.Itoa(i),
107 Decimals: 18,
108 })
109 }
110
111 hdb := historydb.NewHistoryDB(db, db, nil)
112 assert.NoError(t, hdb.AddBlock(&common.Block{
113 Num: 1,
114 }))
115 assert.NoError(t, hdb.AddTokens(tokens))
116}
117
118func checkBalance(t *testing.T, tc *til.Context, txsel *TxSelector, username string,
119 tokenid int, expected string) {
120 // Accounts.Idx does not match with the TxSelector tests as we are not
121 // using the Til L1CoordinatorTxs (as are generated by the TxSelector
122 // itself when processing the txs, so the Idxs does not match the Til
123 // idxs). But the Idx is obtained through StateDB.GetIdxByEthAddrBJJ
124 user := tc.Users[username]
125 idx, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(user.Addr, user.BJJ.Public().Compress(),
126 common.TokenID(tokenid))
127 require.NoError(t, err)
128 checkBalanceByIdx(t, txsel, idx, expected)
129}
130
131func checkBalanceByIdx(t *testing.T, txsel *TxSelector, idx common.Idx, expected string) {
132 acc, err := txsel.localAccountsDB.GetAccount(idx)
133 require.NoError(t, err)
134 assert.Equal(t, expected, acc.Balance.String())
135}
136
137// checkSortedByNonce takes as input testAccNonces map, and the array of
138// common.PoolL2Txs, and checks if the nonces correspond to the accumulated
139// values of the map. Also increases the Nonces computed on the map.
140func checkSortedByNonce(t *testing.T, testAccNonces map[common.Idx]common.Nonce,
141 txs []common.PoolL2Tx) {
142 for _, tx := range txs {
143 assert.True(t, testAccNonces[tx.FromIdx] == tx.Nonce,
144 fmt.Sprintf("Idx: %d, expected: %d, tx.Nonce: %d",
145 tx.FromIdx, testAccNonces[tx.FromIdx], tx.Nonce))
146 testAccNonces[tx.FromIdx] = testAccNonces[tx.FromIdx] + 1
147 }
148}
149
150func TestGetL2TxSelectionMinimumFlow0(t *testing.T) {
151 chainID := uint16(0)
152 tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
153 // generate test transactions, the L1CoordinatorTxs generated by Til
154 // will be ignored at this test, as will be the TxSelector who
155 // generates them when needed
156 blocks, err := tc.GenerateBlocks(txsets.SetBlockchainMinimumFlow0)
157 assert.NoError(t, err)
158
159 hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
160 txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"])
161
162 // restart nonces of TilContext, as will be set by generating directly
163 // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs
164 tc.RestartNonces()
165 testAccNonces := make(map[common.Idx]common.Nonce)
166
167 // add tokens to HistoryDB to avoid breaking FK constrains
168 addTokens(t, tc, txsel.l2db.DB())
169
170 tpc := txprocessor.Config{
171 NLevels: 16,
172 MaxFeeTx: 10,
173 MaxTx: 20,
174 MaxL1Tx: 10,
175 ChainID: chainID,
176 }
177
178 // coordIdxs, accAuths, l1UserTxs, l1CoordTxs, l2Txs, err
179
180 log.Debug("block:0 batch:1")
181 l1UserTxs := []common.L1Tx{}
182 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err :=
183 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
184 require.NoError(t, err)
185 assert.Equal(t, 0, len(oL1UserTxs))
186 assert.Equal(t, 0, len(oL1CoordTxs))
187 assert.Equal(t, 0, len(oL2Txs))
188 assert.Equal(t, common.BatchNum(1), txsel.localAccountsDB.CurrentBatch())
189 assert.Equal(t, common.Idx(255), txsel.localAccountsDB.CurrentIdx())
190
191 log.Debug("block:0 batch:2")
192 l1UserTxs = []common.L1Tx{}
193 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err =
194 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
195 require.NoError(t, err)
196 assert.Equal(t, 0, len(oL1UserTxs))
197 assert.Equal(t, 0, len(oL1CoordTxs))
198 assert.Equal(t, 0, len(oL2Txs))
199 assert.Equal(t, common.BatchNum(2), txsel.localAccountsDB.CurrentBatch())
200 assert.Equal(t, common.Idx(255), txsel.localAccountsDB.CurrentIdx())
201
202 log.Debug("block:0 batch:3")
203 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[2].Batch.ForgeL1TxsNum])
204 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err =
205 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
206 require.NoError(t, err)
207 assert.Equal(t, 2, len(oL1UserTxs))
208 assert.Equal(t, 0, len(oL1CoordTxs))
209 assert.Equal(t, 0, len(oL2Txs))
210 assert.Equal(t, common.BatchNum(3), txsel.localAccountsDB.CurrentBatch())
211 assert.Equal(t, common.Idx(257), txsel.localAccountsDB.CurrentIdx())
212 checkBalance(t, tc, txsel, "A", 0, "500")
213 checkBalance(t, tc, txsel, "C", 1, "0")
214
215 log.Debug("block:0 batch:4")
216 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[3].Batch.ForgeL1TxsNum])
217 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err =
218 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
219 require.NoError(t, err)
220 assert.Equal(t, 1, len(oL1UserTxs))
221 assert.Equal(t, 0, len(oL1CoordTxs))
222 assert.Equal(t, 0, len(oL2Txs))
223 assert.Equal(t, common.BatchNum(4), txsel.localAccountsDB.CurrentBatch())
224 assert.Equal(t, common.Idx(258), txsel.localAccountsDB.CurrentIdx())
225 checkBalance(t, tc, txsel, "A", 0, "500")
226 checkBalance(t, tc, txsel, "A", 1, "500")
227 checkBalance(t, tc, txsel, "C", 1, "0")
228
229 log.Debug("block:0 batch:5")
230 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[4].Batch.ForgeL1TxsNum])
231 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err =
232 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
233 require.NoError(t, err)
234 assert.Equal(t, 0, len(oL1UserTxs))
235 assert.Equal(t, 0, len(oL1CoordTxs))
236 assert.Equal(t, 0, len(oL2Txs))
237 assert.Equal(t, common.BatchNum(5), txsel.localAccountsDB.CurrentBatch())
238 assert.Equal(t, common.Idx(258), txsel.localAccountsDB.CurrentIdx())
239 checkBalance(t, tc, txsel, "A", 0, "500")
240 checkBalance(t, tc, txsel, "A", 1, "500")
241 checkBalance(t, tc, txsel, "C", 1, "0")
242
243 log.Debug("block:0 batch:6")
244 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[5].Batch.ForgeL1TxsNum])
245 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err =
246 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
247 require.NoError(t, err)
248 assert.Equal(t, 1, len(oL1UserTxs))
249 assert.Equal(t, 0, len(oL1CoordTxs))
250 assert.Equal(t, 0, len(oL2Txs))
251 assert.Equal(t, common.BatchNum(6), txsel.localAccountsDB.CurrentBatch())
252 assert.Equal(t, common.Idx(259), txsel.localAccountsDB.CurrentIdx())
253 checkBalance(t, tc, txsel, "A", 0, "600")
254 checkBalance(t, tc, txsel, "A", 1, "500")
255 checkBalance(t, tc, txsel, "B", 0, "400")
256 checkBalance(t, tc, txsel, "C", 1, "0")
257
258 log.Debug("block:0 batch:7")
259 // simulate the PoolL2Txs of the batch7
260 batchPoolL2 := `
261 Type: PoolL2
262 PoolTransferToEthAddr(1) A-B: 200 (126)
263 PoolTransferToEthAddr(0) B-C: 100 (126)`
264 poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2)
265 require.NoError(t, err)
266 // add AccountCreationAuths that will be used at the next batch
267 accAuthSig0 := addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "B")
268 accAuthSig1 := addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "C")
269 // add the PoolL2Txs to the l2DB
270 addL2Txs(t, txsel, poolL2Txs)
271 // check signatures of L2Txs from the L2DB (to check that the
272 // parameters of the PoolL2Tx match the original parameters signed
273 // before inserting it to the L2DB)
274 l2TxsFromDB, err := txsel.l2db.GetPendingTxs()
275 require.NoError(t, err)
276 assert.True(t, l2TxsFromDB[0].VerifySignature(chainID, tc.Users["A"].BJJ.Public().Compress()))
277 assert.True(t, l2TxsFromDB[1].VerifySignature(chainID, tc.Users["B"].BJJ.Public().Compress()))
278 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[6].Batch.ForgeL1TxsNum])
279 coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err :=
280 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
281 require.NoError(t, err)
282 assert.Equal(t, []common.Idx{261, 263}, coordIdxs)
283 assert.Equal(t, txsel.coordAccount.AccountCreationAuth, accAuths[0])
284 assert.Equal(t, txsel.coordAccount.AccountCreationAuth, accAuths[2])
285 assert.Equal(t, accAuthSig0, accAuths[1])
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, 0, len(discardedL2Txs))
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 checkBalance(t, tc, txsel, "Coord", 1, "20") // CoordIdx for TokenID=1
295 checkBalance(t, tc, txsel, "Coord", 0, "10") // CoordIdx for TokenID=1
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),
304 txsel.localAccountsDB.CurrentBatch())
305 require.NoError(t, err)
306
307 log.Debug("block:0 batch:8")
308 // simulate the PoolL2Txs of the batch8
309 batchPoolL2 = `
310 Type: PoolL2
311 PoolTransfer(0) A-B: 100 (126)
312 PoolTransfer(0) C-A: 50 (126)
313 PoolTransfer(1) B-C: 100 (126)
314 PoolExit(0) A: 100 (126)`
315 poolL2Txs, err = tc.GeneratePoolL2Txs(batchPoolL2)
316 require.NoError(t, err)
317 addL2Txs(t, txsel, poolL2Txs)
318 // check signatures of L2Txs from the L2DB (to check that the
319 // parameters of the PoolL2Tx match the original parameters signed
320 // before inserting it to the L2DB)
321 l2TxsFromDB, err = txsel.l2db.GetPendingTxs()
322 require.NoError(t, err)
323 assert.True(t, l2TxsFromDB[0].VerifySignature(chainID, tc.Users["A"].BJJ.Public().Compress()))
324 assert.True(t, l2TxsFromDB[1].VerifySignature(chainID, tc.Users["C"].BJJ.Public().Compress()))
325 assert.True(t, l2TxsFromDB[2].VerifySignature(chainID, tc.Users["B"].BJJ.Public().Compress()))
326 assert.True(t, l2TxsFromDB[3].VerifySignature(chainID, tc.Users["A"].BJJ.Public().Compress()))
327 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[7].Batch.ForgeL1TxsNum])
328 coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err =
329 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
330 require.NoError(t, err)
331 assert.Equal(t, []common.Idx{261, 263}, coordIdxs)
332 assert.Equal(t, 0, len(accAuths))
333 assert.Equal(t, 0, len(oL1UserTxs))
334 assert.Equal(t, 0, len(oL1CoordTxs))
335 assert.Equal(t, 4, len(oL2Txs))
336 assert.Equal(t, 0, len(discardedL2Txs))
337 assert.Equal(t, len(oL1CoordTxs), len(accAuths))
338 assert.Equal(t, common.BatchNum(8), txsel.localAccountsDB.CurrentBatch())
339 assert.Equal(t, common.Idx(264), txsel.localAccountsDB.CurrentIdx())
340 checkBalance(t, tc, txsel, "Coord", 1, "30") // CoordIdx for TokenID=1
341 checkBalance(t, tc, txsel, "Coord", 0, "35") // CoordIdx for TokenID=1
342 checkBalance(t, tc, txsel, "A", 0, "430")
343 checkBalance(t, tc, txsel, "A", 1, "280")
344 checkBalance(t, tc, txsel, "B", 0, "390")
345 checkBalance(t, tc, txsel, "B", 1, "90")
346 checkBalance(t, tc, txsel, "C", 0, "45")
347 checkBalance(t, tc, txsel, "C", 1, "100")
348 checkBalance(t, tc, txsel, "D", 0, "800")
349 checkSortedByNonce(t, testAccNonces, oL2Txs)
350 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(poolL2Txs),
351 txsel.localAccountsDB.CurrentBatch())
352 require.NoError(t, err)
353
354 log.Debug("(batch9)block:1 batch:1")
355 // simulate the PoolL2Txs of the batch9
356 batchPoolL2 = `
357 Type: PoolL2
358 PoolTransfer(0) D-A: 300 (126)
359 PoolTransfer(0) B-D: 100 (126)
360 `
361 poolL2Txs, err = tc.GeneratePoolL2Txs(batchPoolL2)
362 require.NoError(t, err)
363 addL2Txs(t, txsel, poolL2Txs)
364 // check signatures of L2Txs from the L2DB (to check that the
365 // parameters of the PoolL2Tx match the original parameters signed
366 // before inserting it to the L2DB)
367 l2TxsFromDB, err = txsel.l2db.GetPendingTxs()
368 require.NoError(t, err)
369 assert.True(t, l2TxsFromDB[0].VerifySignature(chainID, tc.Users["D"].BJJ.Public().Compress()))
370 assert.True(t, l2TxsFromDB[1].VerifySignature(chainID, tc.Users["B"].BJJ.Public().Compress()))
371 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[1].Rollup.Batches[0].Batch.ForgeL1TxsNum])
372 coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err =
373 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
374 require.NoError(t, err)
375 assert.Equal(t, []common.Idx{263}, coordIdxs)
376 assert.Equal(t, 0, len(accAuths))
377 assert.Equal(t, 4, len(oL1UserTxs))
378 assert.Equal(t, 0, len(oL1CoordTxs))
379 assert.Equal(t, 2, len(oL2Txs))
380 assert.Equal(t, len(oL1CoordTxs), len(accAuths))
381 assert.Equal(t, common.BatchNum(9), txsel.localAccountsDB.CurrentBatch())
382 assert.Equal(t, common.Idx(264), txsel.localAccountsDB.CurrentIdx())
383 checkBalanceByIdx(t, txsel, 261, "30")
384 checkBalanceByIdx(t, txsel, 263, "75")
385 checkBalance(t, tc, txsel, "A", 0, "730")
386 checkBalance(t, tc, txsel, "A", 1, "280")
387 checkBalance(t, tc, txsel, "B", 0, "380")
388 checkBalance(t, tc, txsel, "B", 1, "90")
389 checkBalance(t, tc, txsel, "C", 0, "845")
390 checkBalance(t, tc, txsel, "C", 1, "100")
391 checkBalance(t, tc, txsel, "D", 0, "470")
392 checkSortedByNonce(t, testAccNonces, oL2Txs)
393 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(poolL2Txs),
394 txsel.localAccountsDB.CurrentBatch())
395 require.NoError(t, err)
396}
397
398func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) {
399 set := `
400 Type: Blockchain
401
402 CreateAccountDeposit(0) Coord: 0
403 CreateAccountDeposit(0) A: 100
404 CreateAccountDeposit(0) B: 100
405
406 > batchL1 // freeze L1User{3}
407 > batchL1 // forge L1User{3}
408 > block
409 `
410
411 chainID := uint16(0)
412 tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
413 // generate test transactions, the L1CoordinatorTxs generated by Til
414 // will be ignored at this test, as will be the TxSelector who
415 // generates them when needed
416 blocks, err := tc.GenerateBlocks(set)
417 assert.NoError(t, err)
418
419 hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
420 txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"])
421
422 // restart nonces of TilContext, as will be set by generating directly
423 // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs
424 tc.RestartNonces()
425
426 tpc := txprocessor.Config{
427 NLevels: 16,
428 MaxFeeTx: 10,
429 MaxTx: 20,
430 MaxL1Tx: 10,
431 ChainID: chainID,
432 }
433 // batch1
434 l1UserTxs := []common.L1Tx{}
435 _, _, _, _, _, _, err = txsel.GetL1L2TxSelection(tpc, l1UserTxs)
436 require.NoError(t, err)
437 // 1st TransferToEthAddr
438 expectedTxID0 := "0x028847b86613c0b70be18c8622119ed045b42e4e47d7938fa90bb3d1dc14928965"
439 // 1st Exit
440 expectedTxID1 := "0x0200b18773dcf56f770d65870fb02041cb59a088fd35b7c3f3df69f8a250b99a42"
441 // 2nd TransferToEthAddr
442 expectedTxID2 := "0x029720ff506153f970f120ac638cd7ee759eeff2c2012e7634a78e4fdc05c04a90"
443
444 // batch2
445 // prepare the PoolL2Txs
446 batchPoolL2 := `
447 Type: PoolL2
448 PoolTransferToEthAddr(0) A-B: 100 (126)
449 PoolExit(0) B: 100 (126)`
450 poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2)
451 require.NoError(t, err)
452 // add the PoolL2Txs to the l2DB
453 addL2Txs(t, txsel, poolL2Txs)
454
455 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[1].Batch.ForgeL1TxsNum])
456 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err :=
457 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
458 require.NoError(t, err)
459 assert.Equal(t, 3, len(oL1UserTxs))
460 assert.Equal(t, 0, len(oL1CoordTxs))
461 assert.Equal(t, 0, len(oL2Txs)) // should be 0 as the 2 PoolL2Txs does not have enough funds
462 assert.Equal(t, 2, len(discardedL2Txs))
463 assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String())
464 assert.Equal(t, expectedTxID1, discardedL2Txs[1].TxID.String())
465 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
466 txsel.localAccountsDB.CurrentBatch())
467 require.NoError(t, err)
468
469 // as the PoolL2Txs have not been really processed, restart nonces
470 tc.RestartNonces()
471
472 // batch3
473 // NOTE: this batch will result with 1 L2Tx, as the PoolExit tx is not
474 // possible, as the PoolTransferToEthAddr is not processed yet when
475 // checking availability of PoolExit. This, in a near-future iteration
476 // of the TxSelector will return the 2 transactions as valid and
477 // selected, as the TxSelector will handle this kind of combinations.
478 batchPoolL2 = `
479 Type: PoolL2
480 PoolTransferToEthAddr(0) A-B: 50 (126)`
481 poolL2Txs, err = tc.GeneratePoolL2Txs(batchPoolL2)
482 require.NoError(t, err)
483 addL2Txs(t, txsel, poolL2Txs)
484
485 l1UserTxs = []common.L1Tx{}
486 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err =
487 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
488 require.NoError(t, err)
489 assert.Equal(t, 0, len(oL1UserTxs))
490 assert.Equal(t, 0, len(oL1CoordTxs))
491 assert.Equal(t, 1, len(oL2Txs)) // see 'NOTE' at the beginning of 'batch3' of this test
492 assert.Equal(t, 2, len(discardedL2Txs))
493 assert.Equal(t, expectedTxID2, oL2Txs[0].TxID.String())
494 assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String())
495 assert.Equal(t, expectedTxID1, discardedL2Txs[1].TxID.String())
496 assert.Equal(t, common.TxTypeTransferToEthAddr, oL2Txs[0].Type)
497 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
498 txsel.localAccountsDB.CurrentBatch())
499 require.NoError(t, err)
500
501 // batch4
502 // make the selection of another batch, which should include the
503 // initial PoolExit, which now is valid as B has enough Balance
504 l1UserTxs = []common.L1Tx{}
505 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err =
506 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
507 require.NoError(t, err)
508 assert.Equal(t, 0, len(oL1UserTxs))
509 assert.Equal(t, 0, len(oL1CoordTxs))
510 assert.Equal(t, 1, len(oL2Txs))
511 assert.Equal(t, 1, len(discardedL2Txs))
512 // the Exit that was not accepted at the batch2
513 assert.Equal(t, expectedTxID1, oL2Txs[0].TxID.String())
514 assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String())
515 assert.Equal(t, common.TxTypeExit, oL2Txs[0].Type)
516 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
517 txsel.localAccountsDB.CurrentBatch())
518 require.NoError(t, err)
519}
520
521func TestTransferToBjj(t *testing.T) {
522 set := `
523 Type: Blockchain
524 AddToken(1)
525
526 CreateAccountDeposit(0) Coord: 0
527 CreateAccountDeposit(0) A: 1000
528 CreateAccountDeposit(0) B: 1000
529 CreateAccountDeposit(1) B: 1000
530
531 > batchL1 // freeze L1User{4}
532 > batchL1 // forge L1User{4}
533 > block
534 `
535
536 chainID := uint16(0)
537 tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
538 blocks, err := tc.GenerateBlocks(set)
539 assert.NoError(t, err)
540
541 hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
542 txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"])
543
544 // restart nonces of TilContext, as will be set by generating directly
545 // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs
546 tc.RestartNonces()
547
548 addTokens(t, tc, txsel.l2db.DB())
549
550 tpc := txprocessor.Config{
551 NLevels: 16,
552 MaxFeeTx: 10,
553 MaxTx: 20,
554 MaxL1Tx: 10,
555 ChainID: chainID,
556 }
557 // batch1 to freeze L1UserTxs that will create some accounts with
558 // positive balance
559 l1UserTxs := []common.L1Tx{}
560 _, _, _, _, _, _, err = txsel.GetL1L2TxSelection(tpc, l1UserTxs)
561 require.NoError(t, err)
562
563 // Transfer is ToBJJ to a BJJ-only account that doesn't exist
564 // and the coordinator will create it via L1CoordTx.
565
566 batchPoolL2 := `
567 Type: PoolL2
568 PoolTransferToBJJ(0) A-B: 50 (126)
569 `
570 poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2)
571 require.NoError(t, err)
572
573 // add the PoolL2Txs to the l2DB
574 addL2Txs(t, txsel, poolL2Txs)
575
576 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[1].Batch.ForgeL1TxsNum])
577 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err :=
578 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
579 require.NoError(t, err)
580 assert.Equal(t, 4, len(oL1UserTxs))
581 // We expect the coordinator to add an L1CoordTx to create an account for the recipient of the l2tx
582 require.Equal(t, 1, len(oL1CoordTxs))
583 assert.Equal(t, poolL2Txs[0].ToEthAddr, oL1CoordTxs[0].FromEthAddr)
584 assert.Equal(t, poolL2Txs[0].ToBJJ, oL1CoordTxs[0].FromBJJ)
585 assert.Equal(t, 1, len(oL2Txs))
586 assert.Equal(t, 0, len(discardedL2Txs))
587 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
588 txsel.localAccountsDB.CurrentBatch())
589 require.NoError(t, err)
590
591 // Now the BJJ-only account for B is already created, so the transfer
592 // happens without an L1CoordTx that creates the user account.
593
594 batchPoolL2 = `
595 Type: PoolL2
596 PoolTransferToBJJ(0) A-B: 50 (126)
597 `
598
599 poolL2Txs, err = tc.GeneratePoolL2Txs(batchPoolL2)
600 require.NoError(t, err)
601 addL2Txs(t, txsel, poolL2Txs)
602
603 l1UserTxs = []common.L1Tx{}
604 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err =
605 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
606 require.NoError(t, err)
607 assert.Equal(t, 0, len(oL1UserTxs))
608 // Since the BJJ-only account B already exists, the coordinator doesn't add any L1CoordTxs
609 assert.Equal(t, 0, len(oL1CoordTxs))
610 assert.Equal(t, 1, len(oL2Txs))
611 assert.Equal(t, 0, len(discardedL2Txs))
612 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
613 txsel.localAccountsDB.CurrentBatch())
614 require.NoError(t, err)
615
616 // The transfer now is ToBJJ to a BJJ-only account that doesn't exist
617 // and the coordinator will create it via L1CoordTx. Since it's a
618 // transfer of a token for which the coordinator doesn't have a fee
619 // account, another L1CoordTx will be created for the coordinator to
620 // receive the fees.
621
622 batchPoolL2 = `
623 Type: PoolL2
624 PoolTransferToBJJ(1) B-A: 50 (126)
625 `
626
627 poolL2Txs, err = tc.GeneratePoolL2Txs(batchPoolL2)
628 require.NoError(t, err)
629 addL2Txs(t, txsel, poolL2Txs)
630
631 l1UserTxs = []common.L1Tx{}
632 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err =
633 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
634 require.NoError(t, err)
635 assert.Equal(t, 0, len(oL1UserTxs))
636 // We expect the coordinator to add an L1CoordTx to create an account
637 // to receive the fees by the coordinator and another one for the
638 // recipient of the l2tx
639 assert.Equal(t, 2, len(oL1CoordTxs))
640 // [0] Coordinator account cration for token 1
641 assert.Equal(t, tc.Users["Coord"].Addr, oL1CoordTxs[0].FromEthAddr)
642 // [1] User A BJJ-only account creation for token 1
643 assert.Equal(t, poolL2Txs[0].ToEthAddr, oL1CoordTxs[1].FromEthAddr)
644 assert.Equal(t, poolL2Txs[0].ToBJJ, oL1CoordTxs[1].FromBJJ)
645 assert.Equal(t, common.TokenID(1), oL1CoordTxs[1].TokenID)
646
647 assert.Equal(t, 1, len(oL2Txs))
648 assert.Equal(t, 0, len(discardedL2Txs))
649 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
650 txsel.localAccountsDB.CurrentBatch())
651 require.NoError(t, err)
652}
653
654func TestTransferManyFromSameAccount(t *testing.T) {
655 set := `
656 Type: Blockchain
657
658 CreateAccountDeposit(0) Coord: 0
659 CreateAccountDeposit(0) A: 1000
660 CreateAccountDeposit(0) B: 1000
661
662 > batchL1 // freeze L1User{3}
663 > batchL1 // forge L1User{3}
664 > block
665 `
666
667 chainID := uint16(0)
668 tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
669 blocks, err := tc.GenerateBlocks(set)
670 assert.NoError(t, err)
671
672 hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
673 txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"])
674
675 // restart nonces of TilContext, as will be set by generating directly
676 // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs
677 tc.RestartNonces()
678
679 tpc := txprocessor.Config{
680 NLevels: 16,
681 MaxFeeTx: 10,
682 MaxTx: 10,
683 MaxL1Tx: 10,
684 ChainID: chainID,
685 }
686 // batch1 to freeze L1UserTxs
687 l1UserTxs := []common.L1Tx{}
688 _, _, _, _, _, _, err = txsel.GetL1L2TxSelection(tpc, l1UserTxs)
689 require.NoError(t, err)
690
691 // 8 transfers from the same account
692 batchPoolL2 := `
693 Type: PoolL2
694 PoolTransfer(0) A-B: 10 (126) // 1
695 PoolTransfer(0) A-B: 10 (126) // 2
696 PoolTransfer(0) A-B: 10 (126) // 3
697 PoolTransfer(0) A-B: 10 (126) // 4
698 PoolTransfer(0) A-B: 10 (126) // 5
699 PoolTransfer(0) A-B: 10 (126) // 6
700 PoolTransfer(0) A-B: 10 (126) // 7
701 PoolTransfer(0) A-B: 10 (126) // 8
702 PoolTransfer(0) A-B: 10 (126) // 9
703 PoolTransfer(0) A-B: 10 (126) // 10
704 PoolTransfer(0) A-B: 10 (126) // 11
705 `
706 poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2)
707 require.NoError(t, err)
708 require.Equal(t, 11, len(poolL2Txs))
709
710 // reorder poolL2Txs so that nonces are not sorted
711 poolL2Txs[0], poolL2Txs[7] = poolL2Txs[7], poolL2Txs[0]
712 poolL2Txs[1], poolL2Txs[10] = poolL2Txs[10], poolL2Txs[1]
713
714 // add the PoolL2Txs to the l2DB
715 addL2Txs(t, txsel, poolL2Txs)
716 // batch 2 to crate some accounts with positive balance, and do 8 L2Tx
717 // transfers from account A
718 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[1].Batch.ForgeL1TxsNum])
719 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err :=
720 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
721 require.NoError(t, err)
722 assert.Equal(t, 3, len(oL1UserTxs))
723 require.Equal(t, 0, len(oL1CoordTxs))
724 assert.Equal(t, 7, len(oL2Txs))
725 assert.Equal(t, 4, len(discardedL2Txs))
726
727 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
728 txsel.localAccountsDB.CurrentBatch())
729 require.NoError(t, err)
730}
731
732func TestPoolL2TxInvalidNonces(t *testing.T) {
733 // This test recreates the case where L2Txs of type TransferToEthAddr
734 // for non existing accounts are not selected due not enough space for
735 // the needed L1CoordinatorTxs (of account creation).
736
737 set := `
738 Type: Blockchain
739
740 CreateAccountDeposit(0) Coord: 0
741 CreateAccountDeposit(0) A: 100000
742 CreateAccountDeposit(0) B: 10000
743
744 > batchL1 // Batch1: freeze L1User{3}
745 > batchL1 // Batch2: forge L1User{3}
746 > block
747 `
748
749 chainID := uint16(0)
750 tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
751 blocks, err := tc.GenerateBlocks(set)
752 assert.NoError(t, err)
753
754 hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
755 txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"])
756
757 // restart nonces of TilContext, as will be set by generating directly
758 // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs
759 tc.RestartNonces()
760
761 tpc := txprocessor.Config{
762 NLevels: 16,
763 MaxFeeTx: 10,
764 MaxTx: 20,
765 MaxL1Tx: 3,
766 ChainID: chainID,
767 }
768 // batch1 to freeze L1UserTxs
769 l1UserTxs := []common.L1Tx{}
770 _, _, _, _, _, _, err = txsel.GetL1L2TxSelection(tpc, l1UserTxs)
771 require.NoError(t, err)
772
773 batchPoolL2 := `
774 Type: PoolL2
775 PoolTransferToEthAddr(0) B-C: 10 (126)
776 PoolTransfer(0) B-A: 10 (126)
777 PoolTransferToEthAddr(0) B-D: 10 (126)
778 PoolTransfer(0) B-A: 10 (126)
779 PoolTransferToEthAddr(0) B-E: 10 (126)
780 PoolTransfer(0) B-A: 10 (126)
781 PoolTransferToEthAddr(0) B-F: 10 (126)
782 PoolTransfer(0) B-A: 10 (126)
783 PoolTransferToEthAddr(0) B-G: 10 (126)
784 PoolTransfer(0) B-A: 10 (126)
785 `
786 poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2)
787 require.NoError(t, err)
788 require.Equal(t, 10, len(poolL2Txs))
789
790 // add AccountCreationAuths that will be used at the next batch
791 _ = addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "C")
792 _ = addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "D")
793 _ = addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "E")
794 _ = addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "F")
795 _ = addAccCreationAuth(t, tc, txsel, chainID, hermezContractAddr, "G")
796
797 // add the PoolL2Txs to the l2DB
798 addL2Txs(t, txsel, poolL2Txs)
799 // batch 2 to crate the accounts (from L1UserTxs)
800 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[1].Batch.ForgeL1TxsNum])
801
802 // select L1 & L2 txs
803 _, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err :=
804 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
805 require.NoError(t, err)
806 require.Equal(t, 3, len(oL1UserTxs))
807 require.Equal(t, 0, len(oL1CoordTxs))
808 require.Equal(t, 0, len(oL2Txs))
809 require.Equal(t, 10, len(discardedL2Txs))
810 require.Equal(t, 0, len(accAuths))
811
812 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
813 txsel.localAccountsDB.CurrentBatch())
814 require.NoError(t, err)
815
816 // batch 3
817 l1UserTxs = []common.L1Tx{}
818 _, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err =
819 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
820 require.NoError(t, err)
821
822 require.Equal(t, 0, len(oL1UserTxs))
823 require.Equal(t, 3, len(oL1CoordTxs))
824 require.Equal(t, 8, len(oL2Txs))
825 require.Equal(t, 2, len(discardedL2Txs))
826 require.Equal(t, 3, len(accAuths))
827
828 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
829 txsel.localAccountsDB.CurrentBatch())
830 require.NoError(t, err)
831
832 // batch 4
833 l1UserTxs = []common.L1Tx{}
834 _, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err =
835 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
836 require.NoError(t, err)
837
838 require.Equal(t, 0, len(oL1UserTxs))
839 require.Equal(t, 1, len(oL1CoordTxs))
840 require.Equal(t, 2, len(oL2Txs))
841 require.Equal(t, 0, len(discardedL2Txs))
842 require.Equal(t, 1, len(accAuths))
843
844 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
845 txsel.localAccountsDB.CurrentBatch())
846 require.NoError(t, err)
847}
848
849func TestProcessL2Selection(t *testing.T) {
850 set := `
851 Type: Blockchain
852
853 CreateAccountDeposit(0) Coord: 0
854 CreateAccountDeposit(0) A: 18
855 CreateAccountDeposit(0) B: 0
856
857 > batchL1 // freeze L1User{3}
858 > batchL1 // forge L1User{3}
859 > block
860 `
861
862 chainID := uint16(0)
863 tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx)
864 blocks, err := tc.GenerateBlocks(set)
865 assert.NoError(t, err)
866
867 hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6")
868 txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"])
869
870 // restart nonces of TilContext, as will be set by generating directly
871 // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs
872 tc.RestartNonces()
873
874 tpc := txprocessor.Config{
875 NLevels: 16,
876 MaxFeeTx: 10,
877 MaxTx: 10,
878 MaxL1Tx: 10,
879 ChainID: chainID,
880 }
881 // batch1 to freeze L1UserTxs
882 l1UserTxs := []common.L1Tx{}
883 _, _, _, _, _, _, err = txsel.GetL1L2TxSelection(tpc, l1UserTxs)
884 require.NoError(t, err)
885
886 // 8 transfers from the same account
887 batchPoolL2 := `
888 Type: PoolL2
889 PoolTransfer(0) A-B: 10 (126)
890 PoolTransfer(0) A-B: 10 (126) // not enough funds
891 PoolTransfer(0) A-B: 5 (126) // enough funds
892 `
893 poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2)
894 require.NoError(t, err)
895 require.Equal(t, 3, len(poolL2Txs))
896
897 // add the PoolL2Txs to the l2DB
898 addL2Txs(t, txsel, poolL2Txs)
899 // batch 2 to crate some accounts with positive balance, and do 8 L2Tx transfers from account A
900 l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[1].Batch.ForgeL1TxsNum])
901 _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err :=
902 txsel.GetL1L2TxSelection(tpc, l1UserTxs)
903 require.NoError(t, err)
904 assert.Equal(t, 3, len(oL1UserTxs))
905 require.Equal(t, 0, len(oL1CoordTxs))
906 // only 1st L2Tx should be accepted, as:
907 // - 2nd will not be selected as has not enough funds
908 // - 3rd will not be selected as has Nonce=2, and the account Nonce==1
909 // (due the 2nd txs not being accepted)
910 assert.Equal(t, 1, len(oL2Txs))
911 assert.Equal(t, 2, len(discardedL2Txs))
912 assert.Equal(t, common.Nonce(0), oL2Txs[0].Nonce)
913 assert.Equal(t, common.Nonce(1), discardedL2Txs[0].Nonce)
914 assert.Equal(t, common.Nonce(2), discardedL2Txs[1].Nonce)
915 assert.Equal(t, "Tx not selected due to not enough Balance at the sender. "+
916 "Current sender account Balance: 7, Amount+Fee: 11", discardedL2Txs[0].Info)
917 assert.Equal(t, "Tx not selected due to not current Nonce. Tx.Nonce: 2, "+
918 "Account.Nonce: 1", discardedL2Txs[1].Info)
919
920 err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
921 txsel.localAccountsDB.CurrentBatch())
922 require.NoError(t, err)
923}