diff --git a/api/api_test.go b/api/api_test.go index 25046fc..88a869e 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -161,6 +161,8 @@ var SetBlockchain = ` > block > batch > block + ForceTransfer(0) D-B: 77777700000000000 + > block ` type testCommon struct { @@ -362,6 +364,12 @@ func TestMain(m *testing.M) { commonL1Txs = append(commonL1Txs, batch.L1CoordinatorTxs...) } } + // Add unforged L1 tx + unforgedTx := blocksData[len(blocksData)-1].Rollup.L1UserTxs[0] + if unforgedTx.BatchNum != nil { + panic("Unforged tx batch num should be nil") + } + commonL1Txs = append(commonL1Txs, unforgedTx) // Generate Coordinators and add them to HistoryDB const nCoords = 10 diff --git a/api/batch.go b/api/batch.go index 8d90ce0..721e6e0 100644 --- a/api/batch.go +++ b/api/batch.go @@ -109,7 +109,7 @@ func (a *API) getFullBatch(c *gin.Context) { // Fetch txs forged in the batch from historyDB maxTxsPerBatch := uint(2048) //nolint:gomnd txs, _, err := a.h.GetTxsAPI( - nil, nil, nil, nil, batchNum, nil, nil, &maxTxsPerBatch, historydb.OrderAsc, + nil, nil, nil, nil, batchNum, nil, nil, nil, &maxTxsPerBatch, historydb.OrderAsc, ) if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows { retSQLErr(err, c) diff --git a/api/swagger.yml b/api/swagger.yml index a1e70e8..17f32da 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -529,6 +529,15 @@ paths: type: integer minimum: 1 maximum: 2049 + - name: includePendingL1s + in: query + required: false + description: | + If set to true L1 transactions that have been added to the smart contract queue but haven't been forged yet are returned. + Warning: the correctness of the order is not guaranteed when using this filter, as the unforged transactions may change their position + once they are forged. + schema: + type: boolean responses: '200': description: Successful operation. diff --git a/api/txshistory.go b/api/txshistory.go index d42dca6..b74a37a 100644 --- a/api/txshistory.go +++ b/api/txshistory.go @@ -26,6 +26,14 @@ func (a *API) getHistoryTxs(c *gin.Context) { retBadReq(err, c) return } + // IncludePendingL1s + includePendingL1s := new(bool) + *includePendingL1s = false + includePendingL1s, err = parseQueryBool("includePendingL1s", includePendingL1s, c) + if err != nil { + retBadReq(err, c) + return + } // Pagination fromItem, order, limit, err := parsePagination(c) if err != nil { @@ -35,7 +43,7 @@ func (a *API) getHistoryTxs(c *gin.Context) { // Fetch txs from historyDB txs, pendingItems, err := a.h.GetTxsAPI( - addr, bjj, tokenID, idx, batchNum, txType, fromItem, limit, order, + addr, bjj, tokenID, idx, batchNum, txType, includePendingL1s, fromItem, limit, order, ) if err != nil { retSQLErr(err, c) diff --git a/api/txshistory_test.go b/api/txshistory_test.go index 752ce74..d9269f3 100644 --- a/api/txshistory_test.go +++ b/api/txshistory_test.go @@ -71,7 +71,7 @@ func (t txsSort) Less(i, j int) bool { } // i is forged if jsf.BatchNum == nil { - return false // j is not forged + return true // j is not forged } // Both are forged if *isf.BatchNum == *jsf.BatchNum { @@ -111,7 +111,7 @@ func genTestTxs( ) []testTx { txs := []testTx{} // common.L1Tx ==> testTx - for _, l1 := range l1s { + for i, l1 := range l1s { token := getTokenByID(l1.TokenID, tokens) // l1.FromEthAddr and l1.FromBJJ can't be nil fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr)) @@ -137,15 +137,26 @@ func genTestTxs( }, Token: token, } + // set BatchNum for user txs if tx.L1Info.ToForgeL1TxsNum != nil { - // WARNING: this is an asumption, and the test input data can brake it easily + // WARNING: this works just because the way "common" txs are generated using til + // any change on the test set could break this bn := common.BatchNum(*tx.L1Info.ToForgeL1TxsNum + 2) tx.BatchNum = &bn } // If FromIdx is not nil idxStr := idxToHez(l1.EffectiveFromIdx, token.Symbol) tx.FromIdx = &idxStr + if i == len(l1s)-1 { + // Last tx of the L1 set is supposed to be unforged as per the til set. + // Unforged txs have some special propperties + tx.L1Info.DepositAmountSuccess = false + tx.L1Info.AmountSuccess = false + tx.BatchNum = nil + idxStrUnforged := idxToHez(l1.FromIdx, token.Symbol) + tx.FromIdx = &idxStrUnforged + } // If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ if l1.ToIdx >= common.UserThreshold { // find account @@ -261,12 +272,26 @@ func TestGetHistoryTxs(t *testing.T) { fetchedTxs = append(fetchedTxs, tmp.(testTx)) } } - // Get all (no filters) + // Get all (no filters, excluding unforged txs) limit := 20 path := fmt.Sprintf("%s?limit=%d", endpoint, limit) err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) assert.NoError(t, err) + forgedTxs := []testTx{} + for i := 0; i < len(tc.txs); i++ { + if tc.txs[i].BatchNum != nil { + forgedTxs = append(forgedTxs, tc.txs[i]) + } + } + assertTxs(t, forgedTxs, fetchedTxs) + + // Get all, including unforged txs + fetchedTxs = []testTx{} + path = fmt.Sprintf("%s?limit=%d&includePendingL1s=true", endpoint, limit) + err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) + assert.NoError(t, err) assertTxs(t, tc.txs, fetchedTxs) + // Get by ethAddr account := tc.accounts[2] fetchedTxs = []testTx{} @@ -285,7 +310,7 @@ func TestGetHistoryTxs(t *testing.T) { (tx.FromEthAddr != nil && *tx.FromEthAddr == string(account.EthAddr)) || (tx.ToEthAddr != nil && *tx.ToEthAddr == string(account.EthAddr)) || (tx.FromBJJ != nil && *tx.FromBJJ == string(account.PublicKey)) || - (tx.ToBJJ != nil && *tx.ToBJJ == string(account.PublicKey)) { + (tx.ToBJJ != nil && *tx.ToBJJ == string(account.PublicKey)) && tx.BatchNum != nil { accountTxs = append(accountTxs, tx) } } @@ -312,7 +337,7 @@ func TestGetHistoryTxs(t *testing.T) { assert.NoError(t, err) tokenIDTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { - if tc.txs[i].Token.TokenID == tokenID { + if tc.txs[i].BatchNum != nil && tc.txs[i].Token.TokenID == tokenID { tokenIDTxs = append(tokenIDTxs, tc.txs[i]) } } @@ -331,6 +356,9 @@ func TestGetHistoryTxs(t *testing.T) { assert.NoError(t, err) idxTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { + if tc.txs[i].BatchNum == nil { + continue + } var fromIdx *common.Idx if tc.txs[i].FromIdx != nil { fromIdx, err = stringToIdx(*tc.txs[i].FromIdx, "") @@ -388,7 +416,7 @@ func TestGetHistoryTxs(t *testing.T) { assert.NoError(t, err) txTypeTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { - if tc.txs[i].Type == txType { + if tc.txs[i].Type == txType && tc.txs[i].BatchNum != nil { txTypeTxs = append(txTypeTxs, tc.txs[i]) } } @@ -420,7 +448,9 @@ func TestGetHistoryTxs(t *testing.T) { assert.NoError(t, err) flipedTxs := []testTx{} for i := 0; i < len(tc.txs); i++ { - flipedTxs = append(flipedTxs, tc.txs[len(tc.txs)-1-i]) + if tc.txs[len(tc.txs)-1-i].BatchNum != nil { + flipedTxs = append(flipedTxs, tc.txs[len(tc.txs)-1-i]) + } } assertTxs(t, flipedTxs, fetchedTxs) // Empty array diff --git a/db/historydb/apiqueries.go b/db/historydb/apiqueries.go index e3dfad1..b947125 100644 --- a/db/historydb/apiqueries.go +++ b/db/historydb/apiqueries.go @@ -457,6 +457,7 @@ func (hdb *HistoryDB) GetTxAPI(txID common.TxID) (*TxAPI, error) { func (hdb *HistoryDB) GetTxsAPI( ethAddr *ethCommon.Address, bjj *babyjub.PublicKeyComp, tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType, + includePendingL1s *bool, fromItem, limit *uint, order string, ) ([]TxAPI, uint64, error) { // Warning: amount_success and deposit_amount_success have true as default for @@ -554,12 +555,16 @@ func (hdb *HistoryDB) GetTxsAPI( args = append(args, fromItem) nextIsAnd = true } - if nextIsAnd { - queryStr += "AND " - } else { - queryStr += "WHERE " + + // Include pending L1 txs? (deafault false) + if includePendingL1s == nil || (includePendingL1s != nil && !*includePendingL1s) { + if nextIsAnd { + queryStr += "AND " + } else { + queryStr += "WHERE " + } + queryStr += "tx.batch_num IS NOT NULL " } - queryStr += "tx.batch_num IS NOT NULL " // pagination queryStr += "ORDER BY tx.item_id "