From a8f6891aeaa94bf2d29461709c7ffcef7617a9e9 Mon Sep 17 00:00:00 2001 From: arnaucube Date: Tue, 2 Feb 2021 19:21:25 +0100 Subject: [PATCH] Add PoolL2Tx.Info about the status of the tx PoolL2Tx.Info contains information about the status & State of the transaction. As for example, if the Tx has not been selected in the last batch due not enough Balance at the Sender account, this reason would appear at this parameter. This will help the client (wallet, batchexplorer, etc) to reason why a L2Tx is not selected in the forged batches. --- api/swagger.yml | 7 +++ common/l2tx.go | 2 +- common/pooll2tx.go | 5 +++ coordinator/pipeline.go | 8 +++- db/l2db/l2db.go | 35 ++++++++++++++- db/l2db/l2db_test.go | 24 ++++++++++ db/l2db/views.go | 2 + db/migrations/0001.sql | 1 + test/zkproof/flows_test.go | 18 +++++--- txprocessor/txprocessor.go | 13 +++--- txselector/txselector.go | 83 +++++++++++++++++++++++++---------- txselector/txselector_test.go | 40 +++++++++++------ 12 files changed, 188 insertions(+), 50 deletions(-) diff --git a/api/swagger.yml b/api/swagger.yml index a462aee..ee5fb41 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -1314,6 +1314,12 @@ components: $ref: '#/components/schemas/Nonce' state: $ref: '#/components/schemas/PoolL2TransactionState' + info: + type: string + description: "Info contains information about the status & State of the transaction. As for example, if the Tx has not been selected in the last batch due not enough Balance at the Sender account, this reason would appear at this parameter." + pattern: ".*" + example: "Tx not selected due not enough Balance at the sender." + nullable: true signature: allOf: - $ref: '#/components/schemas/BJJSignature' @@ -1429,6 +1435,7 @@ components: - fee - nonce - state + - info - signature - timestamp - batchNum diff --git a/common/l2tx.go b/common/l2tx.go index 8ef60bc..bde0bea 100644 --- a/common/l2tx.go +++ b/common/l2tx.go @@ -23,7 +23,7 @@ type L2Tx struct { // Nonce is filled by the TxProcessor Nonce Nonce `meddler:"nonce"` Type TxType `meddler:"type"` - EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum Block Number in which this L2Tx was added to the queue + EthBlockNum int64 `meddler:"eth_block_num"` // EthereumBlockNumber in which this L2Tx was added to the queue } // NewL2Tx returns the given L2Tx with the TxId & Type parameters calculated diff --git a/common/pooll2tx.go b/common/pooll2tx.go index b71288b..96dc4f0 100644 --- a/common/pooll2tx.go +++ b/common/pooll2tx.go @@ -40,6 +40,11 @@ type PoolL2Tx struct { Fee FeeSelector `meddler:"fee"` Nonce Nonce `meddler:"nonce"` // effective 40 bits used State PoolL2TxState `meddler:"state"` + // Info contains information about the status & State of the + // transaction. As for example, if the Tx has not been selected in the + // last batch due not enough Balance at the Sender account, this reason + // would appear at this parameter. + Info string `meddler:"info,zeroisnull"` Signature babyjub.SignatureComp `meddler:"signature"` // tx signature Timestamp time.Time `meddler:"timestamp,utctime"` // time when added to the tx pool // Stored in DB: optional fileds, may be uninitialized diff --git a/coordinator/pipeline.go b/coordinator/pipeline.go index 97d8665..dda8fab 100644 --- a/coordinator/pipeline.go +++ b/coordinator/pipeline.go @@ -293,6 +293,7 @@ func (p *Pipeline) forgeBatch(batchNum common.BatchNum) (batchInfo *BatchInfo, e } var poolL2Txs []common.PoolL2Tx + var discardedL2Txs []common.PoolL2Tx var l1UserTxsExtra, l1CoordTxs []common.L1Tx var auths [][]byte var coordIdxs []common.Idx @@ -316,14 +317,14 @@ func (p *Pipeline) forgeBatch(batchNum common.BatchNum) (batchInfo *BatchInfo, e if err != nil { return nil, tracerr.Wrap(err) } - coordIdxs, auths, l1UserTxsExtra, l1CoordTxs, poolL2Txs, err = + coordIdxs, auths, l1UserTxsExtra, l1CoordTxs, poolL2Txs, discardedL2Txs, err = p.txSelector.GetL1L2TxSelection(selectionCfg, l1UserTxs) if err != nil { return nil, tracerr.Wrap(err) } } else { // 2b: only L2 txs - coordIdxs, auths, l1CoordTxs, poolL2Txs, err = + coordIdxs, auths, l1CoordTxs, poolL2Txs, discardedL2Txs, err = p.txSelector.GetL2TxSelection(selectionCfg) if err != nil { return nil, tracerr.Wrap(err) @@ -341,6 +342,9 @@ func (p *Pipeline) forgeBatch(batchNum common.BatchNum) (batchInfo *BatchInfo, e if err := p.l2DB.StartForging(common.TxIDsFromPoolL2Txs(poolL2Txs), batchInfo.BatchNum); err != nil { return nil, tracerr.Wrap(err) } + if err := p.l2DB.UpdateTxsInfo(discardedL2Txs); err != nil { + return nil, tracerr.Wrap(err) + } // Invalidate transactions that become invalid beause of // the poolL2Txs selected. Will mark as invalid the txs that have a diff --git a/db/l2db/l2db.go b/db/l2db/l2db.go index f8fe12e..0cd90df 100644 --- a/db/l2db/l2db.go +++ b/db/l2db/l2db.go @@ -94,6 +94,37 @@ func (l2db *L2DB) AddTx(tx *PoolL2TxWrite) error { return tracerr.Wrap(meddler.Insert(l2db.db, "tx_pool", tx)) } +// UpdateTxsInfo updates the parameter Info of the pool transactions +func (l2db *L2DB) UpdateTxsInfo(txs []common.PoolL2Tx) error { + if len(txs) == 0 { + return nil + } + type txUpdate struct { + ID common.TxID `db:"id"` + Info string `db:"info"` + } + txUpdates := make([]txUpdate, len(txs)) + for i := range txs { + txUpdates[i] = txUpdate{ID: txs[i].TxID, Info: txs[i].Info} + } + const query string = ` + UPDATE tx_pool SET + info = tx_update.info + FROM (VALUES + (NULL::::BYTEA, NULL::::VARCHAR), + (:id, :info) + ) as tx_update (id, info) + WHERE tx_pool.tx_id = tx_update.id; + ` + if len(txUpdates) > 0 { + if _, err := sqlx.NamedExec(l2db.db, query, txUpdates); err != nil { + return tracerr.Wrap(err) + } + } + + return nil +} + // AddTxTest inserts a tx into the L2DB. This is useful for test purposes, // but in production txs will only be inserted through the API func (l2db *L2DB) AddTxTest(tx *common.PoolL2Tx) error { @@ -146,7 +177,7 @@ func (l2db *L2DB) AddTxTest(tx *common.PoolL2Tx) error { const selectPoolTxAPI = `SELECT tx_pool.tx_id, hez_idx(tx_pool.from_idx, token.symbol) AS from_idx, tx_pool.effective_from_eth_addr, tx_pool.effective_from_bjj, hez_idx(tx_pool.to_idx, token.symbol) AS to_idx, tx_pool.effective_to_eth_addr, tx_pool.effective_to_bjj, tx_pool.token_id, tx_pool.amount, tx_pool.fee, tx_pool.nonce, -tx_pool.state, tx_pool.signature, tx_pool.timestamp, tx_pool.batch_num, hez_idx(tx_pool.rq_from_idx, token.symbol) AS rq_from_idx, +tx_pool.state, tx_pool.info, tx_pool.signature, tx_pool.timestamp, tx_pool.batch_num, hez_idx(tx_pool.rq_from_idx, token.symbol) AS rq_from_idx, hez_idx(tx_pool.rq_to_idx, token.symbol) AS rq_to_idx, tx_pool.rq_to_eth_addr, tx_pool.rq_to_bjj, tx_pool.rq_token_id, tx_pool.rq_amount, tx_pool.rq_fee, tx_pool.rq_nonce, tx_pool.tx_type, token.item_id AS token_item_id, token.eth_block_num, token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update @@ -155,7 +186,7 @@ FROM tx_pool INNER JOIN token ON tx_pool.token_id = token.token_id ` // selectPoolTxCommon select part of queries to get common.PoolL2Tx const selectPoolTxCommon = `SELECT tx_pool.tx_id, from_idx, to_idx, tx_pool.to_eth_addr, tx_pool.to_bjj, tx_pool.token_id, tx_pool.amount, tx_pool.fee, tx_pool.nonce, -tx_pool.state, tx_pool.signature, tx_pool.timestamp, rq_from_idx, +tx_pool.state, tx_pool.info, tx_pool.signature, tx_pool.timestamp, rq_from_idx, rq_to_idx, tx_pool.rq_to_eth_addr, tx_pool.rq_to_bjj, tx_pool.rq_token_id, tx_pool.rq_amount, tx_pool.rq_fee, tx_pool.rq_nonce, tx_pool.tx_type, fee_percentage(tx_pool.fee::NUMERIC) * token.usd * tx_pool.amount_f AS fee_usd, token.usd_update diff --git a/db/l2db/l2db_test.go b/db/l2db/l2db_test.go index bf49d11..7a7d811 100644 --- a/db/l2db/l2db_test.go +++ b/db/l2db/l2db_test.go @@ -162,6 +162,30 @@ func TestAddTxTest(t *testing.T) { assert.Equal(t, 0, offset) } } +func TestUpdateTxsInfo(t *testing.T) { + err := prepareHistoryDB(historyDB) + if err != nil { + log.Error("Error prepare historyDB", err) + } + poolL2Txs, err := generatePoolL2Txs() + assert.NoError(t, err) + for i := range poolL2Txs { + err := l2DB.AddTxTest(&poolL2Txs[i]) + require.NoError(t, err) + + // once added, change the Info parameter + poolL2Txs[i].Info = "test" + } + // update the txs + err = l2DB.UpdateTxsInfo(poolL2Txs) + require.NoError(t, err) + + for i := range poolL2Txs { + fetchedTx, err := l2DB.GetTx(poolL2Txs[i].TxID) + assert.NoError(t, err) + assert.Equal(t, "test", fetchedTx.Info) + } +} func assertTx(t *testing.T, expected, actual *common.PoolL2Tx) { // Check that timestamp has been set within the last 3 seconds diff --git a/db/l2db/views.go b/db/l2db/views.go index 8e4bd80..7850c0a 100644 --- a/db/l2db/views.go +++ b/db/l2db/views.go @@ -49,6 +49,7 @@ type PoolTxAPI struct { Fee common.FeeSelector `meddler:"fee"` Nonce common.Nonce `meddler:"nonce"` State common.PoolL2TxState `meddler:"state"` + Info *string `meddler:"info"` Signature babyjub.SignatureComp `meddler:"signature"` RqFromIdx *apitypes.HezIdx `meddler:"rq_from_idx"` RqToIdx *apitypes.HezIdx `meddler:"rq_to_idx"` @@ -90,6 +91,7 @@ func (tx PoolTxAPI) MarshalJSON() ([]byte, error) { "fee": tx.Fee, "nonce": tx.Nonce, "state": tx.State, + "info": tx.Info, "signature": tx.Signature, "timestamp": tx.Timestamp, "batchNum": tx.BatchNum, diff --git a/db/migrations/0001.sql b/db/migrations/0001.sql index afd8acb..1b2854f 100644 --- a/db/migrations/0001.sql +++ b/db/migrations/0001.sql @@ -606,6 +606,7 @@ CREATE TABLE tx_pool ( fee SMALLINT NOT NULL, nonce BIGINT NOT NULL, state CHAR(4) NOT NULL, + info VARCHAR, signature BYTEA NOT NULL, timestamp TIMESTAMP WITHOUT TIME ZONE DEFAULT timezone('utc', now()), batch_num BIGINT, diff --git a/test/zkproof/flows_test.go b/test/zkproof/flows_test.go index 17efd5c..5348cce 100644 --- a/test/zkproof/flows_test.go +++ b/test/zkproof/flows_test.go @@ -152,7 +152,7 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[i].Batch.ForgeL1TxsNum]) } // TxSelector select the transactions for the next Batch - coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) // BatchBuilder build Batch zki, err := bb.BuildBatch(coordIdxs, configBatch, oL1UserTxs, oL1CoordTxs, oL2Txs) @@ -175,7 +175,7 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { addL2Txs(t, l2DBTxSel, l2Txs) // Add L2s to TxSelector.L2DB l1UserTxs := til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[6].Batch.ForgeL1TxsNum]) // TxSelector select the transactions for the next Batch - coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) // BatchBuilder build Batch zki, err := bb.BuildBatch(coordIdxs, configBatch, oL1UserTxs, oL1CoordTxs, oL2Txs) @@ -184,6 +184,8 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { sendProofAndCheckResp(t, zki) err = l2DBTxSel.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), txsel.LocalAccountsDB().CurrentBatch()) require.NoError(t, err) + err = l2DBTxSel.UpdateTxsInfo(discardedL2Txs) + require.NoError(t, err) log.Debug("block:0 batch:8") // simulate the PoolL2Txs of the batch8 @@ -198,7 +200,7 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { addL2Txs(t, l2DBTxSel, l2Txs) // Add L2s to TxSelector.L2DB l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[7].Batch.ForgeL1TxsNum]) // TxSelector select the transactions for the next Batch - coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) // BatchBuilder build Batch zki, err = bb.BuildBatch(coordIdxs, configBatch, oL1UserTxs, oL1CoordTxs, oL2Txs) @@ -207,6 +209,8 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { sendProofAndCheckResp(t, zki) err = l2DBTxSel.StartForging(common.TxIDsFromPoolL2Txs(l2Txs), txsel.LocalAccountsDB().CurrentBatch()) require.NoError(t, err) + err = l2DBTxSel.UpdateTxsInfo(discardedL2Txs) + require.NoError(t, err) log.Debug("(batch9) block:1 batch:1") // simulate the PoolL2Txs of the batch9 @@ -219,7 +223,7 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { addL2Txs(t, l2DBTxSel, l2Txs) // Add L2s to TxSelector.L2DB l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[1].Rollup.Batches[0].Batch.ForgeL1TxsNum]) // TxSelector select the transactions for the next Batch - coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) // BatchBuilder build Batch zki, err = bb.BuildBatch(coordIdxs, configBatch, oL1UserTxs, oL1CoordTxs, oL2Txs) @@ -228,12 +232,14 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { sendProofAndCheckResp(t, zki) err = l2DBTxSel.StartForging(common.TxIDsFromPoolL2Txs(l2Txs), txsel.LocalAccountsDB().CurrentBatch()) require.NoError(t, err) + err = l2DBTxSel.UpdateTxsInfo(discardedL2Txs) + require.NoError(t, err) log.Debug("(batch10) block:1 batch:2") l2Txs = []common.PoolL2Tx{} l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[1].Rollup.Batches[1].Batch.ForgeL1TxsNum]) // TxSelector select the transactions for the next Batch - coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) // BatchBuilder build Batch zki, err = bb.BuildBatch(coordIdxs, configBatch, oL1UserTxs, oL1CoordTxs, oL2Txs) @@ -244,4 +250,6 @@ func TestTxSelectorBatchBuilderZKInputs(t *testing.T) { sendProofAndCheckResp(t, zki) err = l2DBTxSel.StartForging(common.TxIDsFromPoolL2Txs(l2Txs), txsel.LocalAccountsDB().CurrentBatch()) require.NoError(t, err) + err = l2DBTxSel.UpdateTxsInfo(discardedL2Txs) + require.NoError(t, err) } diff --git a/txprocessor/txprocessor.go b/txprocessor/txprocessor.go index 9b0d160..51337dc 100644 --- a/txprocessor/txprocessor.go +++ b/txprocessor/txprocessor.go @@ -1300,16 +1300,19 @@ func (tp *TxProcessor) computeEffectiveAmounts(tx *common.L1Tx) { } // CheckEnoughBalance returns true if the sender of the transaction has enough -// balance in the account to send the Amount+Fee -func (tp *TxProcessor) CheckEnoughBalance(tx common.PoolL2Tx) bool { +// balance in the account to send the Amount+Fee, and also returns the account +// Balance and the Fee+Amount (which is used to give information about why the +// transaction is not selected in case that this method returns false. +func (tp *TxProcessor) CheckEnoughBalance(tx common.PoolL2Tx) (bool, *big.Int, *big.Int) { acc, err := tp.s.GetAccount(tx.FromIdx) if err != nil { - return false + return false, nil, nil } fee, err := common.CalcFeeAmount(tx.Amount, tx.Fee) if err != nil { - return false + return false, nil, nil } feeAndAmount := new(big.Int).Add(tx.Amount, fee) - return acc.Balance.Cmp(feeAndAmount) != -1 // !=-1 balance= int(selectionConfig.MaxL1UserTxs)-len(l1UserTxs) { - // discard L2Tx + // discard L2Tx, and update Info parameter of + // the tx, and add it to the discardedTxs array + l2Txs0[i].Info = "Tx not selected due the L2Tx depends on a L1CoordinatorTx and there is not enough space for L1Coordinator" + discardedL2Txs = append(discardedL2Txs, l2Txs0[i]) continue } // increase positionL1 @@ -244,8 +249,13 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, // - keep used accAuths // - put the valid txs into validTxs array for i := 0; i < len(l2Txs); i++ { - if !tp.CheckEnoughBalance(l2Txs[i]) { - // not valid Amount with current Balance + enoughBalance, balance, feeAndAmount := tp.CheckEnoughBalance(l2Txs[i]) + if !enoughBalance { + // not valid Amount with current Balance. Discard L2Tx, + // and update Info parameter of the tx, and add it to + // the discardedTxs array + l2Txs[i].Info = fmt.Sprintf("Tx not selected due not enough Balance at the sender. Current sender account Balance: %s, Amount+Fee: %s", balance.String(), feeAndAmount.String()) + discardedL2Txs = append(discardedL2Txs, l2Txs[i]) continue } // check if Nonce is correct @@ -253,7 +263,11 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, if l2Txs[i].Nonce == nonce { noncesMap[l2Txs[i].FromIdx]++ } else { - // not valid Nonce at tx + // not valid Nonce at tx. Discard L2Tx, and update Info + // parameter of the tx, and add it to the discardedTxs + // array + l2Txs[i].Info = fmt.Sprintf("Tx not selected due not current Nonce. Tx.Nonce: %d, Account.Nonce: %d", l2Txs[i].Nonce, nonce) + discardedL2Txs = append(discardedL2Txs, l2Txs[i]) continue } @@ -271,6 +285,10 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, len(l1UserTxs), l1CoordinatorTxs, positionL1, l2Txs[i]) if err != nil { log.Debug(err) + // Discard L2Tx, and update Info parameter of + // the tx, and add it to the discardedTxs array + l2Txs[i].Info = fmt.Sprintf("Tx not selected (in processTxToEthAddrBJJ) due %s", err.Error()) + discardedL2Txs = append(discardedL2Txs, l2Txs[i]) continue } if accAuth != nil && l1CoordinatorTx != nil { @@ -287,13 +305,24 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, // tx not valid log.Debugw("invalid L2Tx: ToIdx not found in StateDB", "ToIdx", l2Txs[i].ToIdx) + // Discard L2Tx, and update Info parameter of + // the tx, and add it to the discardedTxs array + l2Txs[i].Info = fmt.Sprintf("Tx not selected due tx.ToIdx not found in StateDB. ToIdx: %d", + l2Txs[i].ToIdx) + discardedL2Txs = append(discardedL2Txs, l2Txs[i]) continue } if l2Txs[i].ToEthAddr != common.EmptyAddr { if l2Txs[i].ToEthAddr != receiverAcc.EthAddr { log.Debugw("invalid L2Tx: ToEthAddr does not correspond to the Account.EthAddr", - "ToIdx", l2Txs[i].ToIdx, "tx.ToEthAddr", l2Txs[i].ToEthAddr, - "account.EthAddr", receiverAcc.EthAddr) + "ToIdx", l2Txs[i].ToIdx, "tx.ToEthAddr", + l2Txs[i].ToEthAddr, "account.EthAddr", receiverAcc.EthAddr) + // Discard L2Tx, and update Info + // parameter of the tx, and add it to + // the discardedTxs array + l2Txs[i].Info = fmt.Sprintf("Tx not selected due ToEthAddr does not correspond to the Account.EthAddr. tx.ToIdx: %d, tx.ToEthAddr: %s, account.EthAddr: %s", + l2Txs[i].ToIdx, l2Txs[i].ToEthAddr, receiverAcc.EthAddr) + discardedL2Txs = append(discardedL2Txs, l2Txs[i]) continue } } @@ -302,6 +331,12 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, log.Debugw("invalid L2Tx: ToBJJ does not correspond to the Account.BJJ", "ToIdx", l2Txs[i].ToIdx, "tx.ToEthAddr", l2Txs[i].ToBJJ, "account.BJJ", receiverAcc.BJJ) + // Discard L2Tx, and update Info + // parameter of the tx, and add it to + // the discardedTxs array + l2Txs[i].Info = fmt.Sprintf("Tx not selected due tx.ToBJJ does not correspond to the Account.BJJ. tx.ToIdx: %d, tx.ToEthAddr: %s, tx.ToBJJ: %s, account.BJJ: %s", + l2Txs[i].ToIdx, l2Txs[i].ToEthAddr, l2Txs[i].ToBJJ, receiverAcc.BJJ) + discardedL2Txs = append(discardedL2Txs, l2Txs[i]) continue } } @@ -318,7 +353,7 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, for i := 0; i < len(l1CoordinatorTxs); i++ { _, _, _, _, err := tp.ProcessL1Tx(nil, &l1CoordinatorTxs[i]) if err != nil { - return nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) } } @@ -328,7 +363,7 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, // get TokenID from tx.Sender accSender, err := tp.StateDB().GetAccount(validTxs[i].FromIdx) if err != nil { - return nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) } tokenID := accSender.TokenID @@ -337,7 +372,7 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, // if err is db.ErrNotFound, should not happen, as all // the validTxs.TokenID should have a CoordinatorIdx // created in the DB at this point - return nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) } coordIdxsMap[tokenID] = coordIdx } @@ -371,6 +406,10 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, // log the error, assuming that this will be iterated // in a near future. log.Error(err) + // Discard L2Tx, and update Info parameter of the tx, + // and add it to the discardedTxs array + selectedL2Txs[i].Info = fmt.Sprintf("Tx not selected (in ProcessL2Tx) due %s", err.Error()) + discardedL2Txs = append(discardedL2Txs, selectedL2Txs[i]) continue } finalL2Txs = append(finalL2Txs, selectedL2Txs[i]) @@ -385,23 +424,23 @@ func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig, accCoord, err := txsel.localAccountsDB.GetAccount(idx) if err != nil { log.Errorw("Can not distribute accumulated fees to coordinator account: No coord Idx to receive fee", "idx", idx) - return nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) } accCoord.Balance = new(big.Int).Add(accCoord.Balance, accumulatedFee) _, err = txsel.localAccountsDB.UpdateAccount(idx, accCoord) if err != nil { log.Error(err) - return nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) } } } err = tp.StateDB().MakeCheckpoint() if err != nil { - return nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) } - return coordIdxs, accAuths, l1UserTxs, l1CoordinatorTxs, finalL2Txs, nil + return coordIdxs, accAuths, l1UserTxs, l1CoordinatorTxs, finalL2Txs, discardedL2Txs, nil } // processTxsToEthAddrBJJ process the common.PoolL2Tx in the case where diff --git a/txselector/txselector_test.go b/txselector/txselector_test.go index 723de8d..516ea35 100644 --- a/txselector/txselector_test.go +++ b/txselector/txselector_test.go @@ -177,7 +177,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { log.Debug("block:0 batch:1") l1UserTxs := []common.L1Tx{} - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 0, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) @@ -187,7 +187,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { log.Debug("block:0 batch:2") l1UserTxs = []common.L1Tx{} - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 0, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) @@ -197,7 +197,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { log.Debug("block:0 batch:3") l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[2].Batch.ForgeL1TxsNum]) - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 2, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) @@ -209,7 +209,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { log.Debug("block:0 batch:4") l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[3].Batch.ForgeL1TxsNum]) - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 1, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) @@ -222,7 +222,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { log.Debug("block:0 batch:5") l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[4].Batch.ForgeL1TxsNum]) - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 0, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) @@ -235,7 +235,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { log.Debug("block:0 batch:6") l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[5].Batch.ForgeL1TxsNum]) - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 1, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) @@ -268,7 +268,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { assert.True(t, l2TxsFromDB[0].VerifySignature(chainID, tc.Users["A"].BJJ.Public().Compress())) assert.True(t, l2TxsFromDB[1].VerifySignature(chainID, tc.Users["B"].BJJ.Public().Compress())) l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[6].Batch.ForgeL1TxsNum]) - coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, []common.Idx{261, 262}, coordIdxs) assert.Equal(t, txsel.coordAccount.AccountCreationAuth, accAuths[0]) @@ -314,7 +314,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { assert.True(t, l2TxsFromDB[2].VerifySignature(chainID, tc.Users["B"].BJJ.Public().Compress())) assert.True(t, l2TxsFromDB[3].VerifySignature(chainID, tc.Users["A"].BJJ.Public().Compress())) l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[7].Batch.ForgeL1TxsNum]) - coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, []common.Idx{261, 262}, coordIdxs) assert.Equal(t, 0, len(accAuths)) @@ -355,7 +355,7 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { assert.True(t, l2TxsFromDB[0].VerifySignature(chainID, tc.Users["D"].BJJ.Public().Compress())) assert.True(t, l2TxsFromDB[1].VerifySignature(chainID, tc.Users["B"].BJJ.Public().Compress())) l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[1].Rollup.Batches[0].Batch.ForgeL1TxsNum]) - coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + coordIdxs, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, []common.Idx{262}, coordIdxs) assert.Equal(t, 0, len(accAuths)) @@ -420,9 +420,13 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { } // batch1 l1UserTxs := []common.L1Tx{} - _, _, _, _, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, _, _, _, _, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) + expectedTxID0 := "0x0248bae02b5c8c3847d312bfac3a33ae790616e888f2f711f22aeaff007cde92c2" // 1st TransferToEthAddr + expectedTxID1 := "0x0249af018311a393c337ab9174ca2466cba489e49942b4ca4e5c530903671c4aef" // 1st Exit + expectedTxID2 := "0x0228b93a261a0cdc62f35588c03bd179d31a0807c28afffdb6a7aaf0c4f017e4cf" // 2nd TransferToEthAddr + // batch2 // prepare the PoolL2Txs batchPoolL2 := ` @@ -435,11 +439,14 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { addL2Txs(t, txsel, poolL2Txs) l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[1].Batch.ForgeL1TxsNum]) - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 3, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) assert.Equal(t, 0, len(oL2Txs)) // should be 0 as the 2 PoolL2Txs does not have enough funds + assert.Equal(t, 2, len(discardedL2Txs)) + assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String()) + assert.Equal(t, expectedTxID1, discardedL2Txs[1].TxID.String()) err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), txsel.localAccountsDB.CurrentBatch()) require.NoError(t, err) @@ -460,11 +467,15 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { addL2Txs(t, txsel, poolL2Txs) l1UserTxs = []common.L1Tx{} - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 0, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) assert.Equal(t, 1, len(oL2Txs)) // see 'NOTE' at the beginning of 'batch3' of this test + assert.Equal(t, 2, len(discardedL2Txs)) + assert.Equal(t, expectedTxID2, oL2Txs[0].TxID.String()) + assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String()) + assert.Equal(t, expectedTxID1, discardedL2Txs[1].TxID.String()) assert.Equal(t, common.TxTypeTransferToEthAddr, oL2Txs[0].Type) err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), txsel.localAccountsDB.CurrentBatch()) require.NoError(t, err) @@ -473,11 +484,14 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { // make the selection of another batch, which should include the // initial PoolExit, which now is valid as B has enough Balance l1UserTxs = []common.L1Tx{} - _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) + _, _, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err = txsel.GetL1L2TxSelection(selectionConfig, l1UserTxs) require.NoError(t, err) assert.Equal(t, 0, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) assert.Equal(t, 1, len(oL2Txs)) + assert.Equal(t, 1, len(discardedL2Txs)) + assert.Equal(t, expectedTxID1, oL2Txs[0].TxID.String()) // the Exit that was not accepted at the batch2 + assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String()) assert.Equal(t, common.TxTypeExit, oL2Txs[0].Type) err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), txsel.localAccountsDB.CurrentBatch()) require.NoError(t, err)