diff --git a/api/api.go b/api/api.go index 926bc86..5575a0f 100644 --- a/api/api.go +++ b/api/api.go @@ -60,6 +60,7 @@ func NewAPI( // Transaction v1.POST("/transactions-pool", a.postPoolTx) v1.GET("/transactions-pool/:id", a.getPoolTx) + v1.GET("/transactions-pool", a.getPoolTxs) } // Add explorer endpoints diff --git a/api/parsers.go b/api/parsers.go index 259adc3..f606a87 100644 --- a/api/parsers.go +++ b/api/parsers.go @@ -96,6 +96,32 @@ func parseQueryBJJ(c querier) (*babyjub.PublicKeyComp, error) { return hezStringToBJJ(bjjStr, name) } +func parseQueryPoolL2TxState(c querier) (*common.PoolL2TxState, error) { + const name = "state" + stateStr := c.Query(name) + if stateStr == "" { + return nil, nil + } + switch common.PoolL2TxState(stateStr) { + case common.PoolL2TxStatePending: + ret := common.PoolL2TxStatePending + return &ret, nil + case common.PoolL2TxStateForged: + ret := common.PoolL2TxStateForged + return &ret, nil + case common.PoolL2TxStateForging: + ret := common.PoolL2TxStateForging + return &ret, nil + case common.PoolL2TxStateInvalid: + ret := common.PoolL2TxStateInvalid + return &ret, nil + } + return nil, tracerr.Wrap(fmt.Errorf( + "invalid %s, %s is not a valid option. Check the valid options in the docmentation", + name, stateStr, + )) +} + func parseQueryTxType(c querier) (*common.TxType, error) { const name = "type" typeStr := c.Query(name) diff --git a/api/swagger.yml b/api/swagger.yml index a1e70e8..3efdd74 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -415,6 +415,49 @@ paths: application/json: schema: $ref: '#/components/schemas/Error500' + get: + tags: + - Coordinator + summary: Get transactions that are in the pool. + operationId: getPoolTxs + parameters: + - name: state + in: query + required: false + description: State of the transactions, e.g. "pend" + schema: + $ref: '#/components/schemas/PoolL2TransactionState' + - name: accountIndex + in: query + required: false + description: Id of the account + schema: + $ref: '#/components/schemas/AccountIndex' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PoolL2Transactions' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' '/transactions-pool/{id}': get: tags: @@ -1439,6 +1482,14 @@ components: - requestFee - requestNonce - token + PoolL2Transactions: + type: object + properties: + transactions: + type: array + description: List of pool l2 transactions + items: + $ref: '#/components/schemas/PoolL2Transaction' TransactionId: type: string description: Identifier for transactions. Used for any kind of transaction (both L1 and L2). More info on how the identifiers are built [here](https://idocs.hermez.io/#/spec/architecture/db/README?id=txid) diff --git a/api/txspool.go b/api/txspool.go index 11cd878..6452b1f 100644 --- a/api/txspool.go +++ b/api/txspool.go @@ -55,6 +55,35 @@ func (a *API) getPoolTx(c *gin.Context) { c.JSON(http.StatusOK, tx) } +func (a *API) getPoolTxs(c *gin.Context) { + // Get idx + idx, err := parseIdx(c) + if err != nil { + retBadReq(err, c) + return + } + // Get state + state, err := parseQueryPoolL2TxState(c) + if err != nil { + retBadReq(err, c) + return + } + // Fetch txs from l2DB + txs, err := a.l2.GetPoolTxs(idx, state) + if err != nil { + retSQLErr(err, c) + return + } + + // Build successful response + type txsResponse struct { + Txs []*l2db.PoolTxAPI `json:"transactions"` + } + c.JSON(http.StatusOK, &txsResponse{ + Txs: txs, + }) +} + type receivedPoolTx struct { TxID common.TxID `json:"id" binding:"required"` Type common.TxType `json:"type" binding:"required"` diff --git a/api/txspool_test.go b/api/txspool_test.go index fdff1a4..b3d23c3 100644 --- a/api/txspool_test.go +++ b/api/txspool_test.go @@ -47,6 +47,10 @@ type testPoolTxReceive struct { Token historydb.TokenWithUSD `json:"token"` } +type testPoolTxsResponse struct { + Txs []testPoolTxReceive `json:"transactions"` +} + // testPoolTxSend is a struct to be used as a JSON body // when testing POST /transactions-pool type testPoolTxSend struct { @@ -224,6 +228,25 @@ func TestPoolTxs(t *testing.T) { jsonTxReader = bytes.NewReader(jsonTxBytes) err = doBadReq("POST", endpoint, jsonTxReader, 400) require.NoError(t, err) + // GET + // get by idx + fetchedTxs := testPoolTxsResponse{} + require.NoError(t, doGoodReq( + "GET", + endpoint+"?accountIndex=hez:ETH:263", + nil, &fetchedTxs)) + assert.Equal(t, 1, len(fetchedTxs.Txs)) + assert.Equal(t, "hez:ETH:263", fetchedTxs.Txs[0].FromIdx) + // get by state + require.NoError(t, doGoodReq( + "GET", + endpoint+"?state=pend", + nil, &fetchedTxs)) + assert.Equal(t, 4, len(fetchedTxs.Txs)) + for _, v := range fetchedTxs.Txs { + assert.Equal(t, common.PoolL2TxStatePending, v.State) + } + // GET endpoint += "/" for _, tx := range tc.poolTxsToReceive { diff --git a/db/l2db/apiqueries.go b/db/l2db/apiqueries.go index 46f4426..d86f403 100644 --- a/db/l2db/apiqueries.go +++ b/db/l2db/apiqueries.go @@ -127,3 +127,41 @@ func (l2db *L2DB) GetTxAPI(txID common.TxID) (*PoolTxAPI, error) { txID, )) } + +func (l2db *L2DB) GetPoolTxs(idx *common.Idx, state *common.PoolL2TxState) ([]*PoolTxAPI, error) { + cancel, err := l2db.apiConnCon.Acquire() + defer cancel() + if err != nil { + return nil, tracerr.Wrap(err) + } + defer l2db.apiConnCon.Release() + // Apply filters + nextIsAnd := false + queryStr := selectPoolTxAPI + var args []interface{} + if state != nil { + queryStr += "WHERE state = ? " + args = append(args, state) + nextIsAnd = true + } + if idx != nil { + if nextIsAnd { + queryStr += "AND (" + } else { + queryStr += "WHERE (" + } + queryStr += "tx_pool.from_idx = ? " + queryStr += "OR tx_pool.to_idx = ?) " + args = append(args, idx) + args = append(args, idx) + nextIsAnd = true + } + queryStr += "AND NOT external_delete;" + query := l2db.dbRead.Rebind(queryStr) + txs := []*PoolTxAPI{} + err = meddler.QueryAll( + l2db.dbRead, &txs, + query, + args...) + return txs, tracerr.Wrap(err) +} diff --git a/db/l2db/l2db_test.go b/db/l2db/l2db_test.go index 2a270d2..2a12107 100644 --- a/db/l2db/l2db_test.go +++ b/db/l2db/l2db_test.go @@ -311,6 +311,27 @@ func TestGetPending(t *testing.T) { } } +func TestL2DB_GetPoolTxs(t *testing.T) { + err := prepareHistoryDB(historyDB) + if err != nil { + log.Error("Error prepare historyDB", err) + } + poolL2Txs, err := generatePoolL2Txs() + state := common.PoolL2TxState("pend") + idx := common.Idx(256) + var pendingTxs []*common.PoolL2Tx + for i := range poolL2Txs { + if poolL2Txs[i].FromIdx == idx || poolL2Txs[i].ToIdx == idx { + err := l2DB.AddTxTest(&poolL2Txs[i]) + require.NoError(t, err) + pendingTxs = append(pendingTxs, &poolL2Txs[i]) + } + } + fetchedTxs, err := l2DBWithACC.GetPoolTxs(&idx, &state) + require.NoError(t, err) + assert.Equal(t, len(pendingTxs), len(fetchedTxs)) +} + func TestStartForging(t *testing.T) { // Generate txs var fakeBatchNum common.BatchNum = 33