Refactor api exits
@@ -52,10 +52,10 @@ c.JSON(http.StatusOK, auth)
} type receivedAuth struct { - EthAddr apitypes.StrHezEthAddr `json:"hezEthereumAddress" binding:"required"` - BJJ apitypes.StrHezBJJ `json:"bjj" binding:"required"` - Signature apitypes.StrEthSignature `json:"signature" binding:"required"` - Timestamp time.Time `json:"timestamp"` + EthAddr apitypes.StrHezEthAddr `json:"hezEthereumAddress" binding:"required"` + BJJ apitypes.StrHezBJJ `json:"bjj" binding:"required"` + Signature apitypes.EthSignature `json:"signature" binding:"required"` + Timestamp time.Time `json:"timestamp"` } func accountCreationAuthAPIToCommon(apiAuth *receivedAuth) *common.AccountCreationAuth {
@@ -27,9 +27,7 @@ "github.com/hermeznetwork/hermez-node/eth"
"github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/hermez-node/test" "github.com/iden3/go-iden3-crypto/babyjub" - "github.com/mitchellh/copystructure" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const apiPort = ":4010"@@ -46,8 +44,8 @@ usrBjj string
accs []common.Account usrTxs []testTx allTxs []testTx - exits []exitAPI - usrExits []exitAPI + exits []testExit + usrExits []testExit poolTxsToSend []testPoolTxSend poolTxsToReceive []testPoolTxReceive auths []testAuth@@ -286,42 +284,6 @@ panic("should be l1 or l2")
} } - // Transform exits to API - exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI { - historyExits := []historydb.HistoryExit{} - for _, exit := range exits { - token := getTokenByIdx(exit.AccountIdx, tokensUSD, accs) - historyExits = append(historyExits, historydb.HistoryExit{ - BatchNum: exit.BatchNum, - AccountIdx: exit.AccountIdx, - MerkleProof: exit.MerkleProof, - Balance: exit.Balance, - InstantWithdrawn: exit.InstantWithdrawn, - DelayedWithdrawRequest: exit.DelayedWithdrawRequest, - DelayedWithdrawn: exit.DelayedWithdrawn, - TokenID: token.TokenID, - TokenEthBlockNum: token.EthBlockNum, - TokenEthAddr: token.EthAddr, - TokenName: token.Name, - TokenSymbol: token.Symbol, - TokenDecimals: token.Decimals, - TokenUSD: token.USD, - TokenUSDUpdate: token.USDUpdate, - }) - } - return historyExitsToAPI(historyExits) - } - apiExits := exitsToAPIExits(exits, accs, tokens) - // sort.Sort(apiExits) - usrExits := []exitAPI{} - for _, exit := range apiExits { - for _, idx := range usrIdxs { - if idx == exit.AccountIdx { - usrExits = append(usrExits, exit) - } - } - } - // Coordinators const nCoords = 10 coords := test.GenCoordinators(nCoords, blocks)@@ -348,6 +310,7 @@ // Set testCommon
usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks) poolTxsToSend, poolTxsToReceive := genTestPoolTx(accs, []babyjub.PrivateKey{privK}, tokensUSD) // NOTE: pool txs are not inserted to the DB here. In the test they will be posted and getted. testBatches, fullBatches := genTestBatches(blocks, batches, allTxs) + usrExits, allExits := genTestExits(exits, tokensUSD, accs, usrIdxs) tc = testCommon{ blocks: blocks, tokens: tokensUSD,@@ -359,7 +322,7 @@ usrBjj: bjjToString(usrBjj),
accs: accs, usrTxs: usrTxs, allTxs: allTxs, - exits: apiExits, + exits: allExits, usrExits: usrExits, poolTxsToSend: poolTxsToSend, poolTxsToReceive: poolTxsToReceive,@@ -390,179 +353,6 @@ }
os.Exit(result) } -func TestGetExits(t *testing.T) { - endpoint := apiURL + "exits" - fetchedExits := []exitAPI{} - appendIter := func(intr interface{}) { - for i := 0; i < len(intr.(*exitsAPI).Exits); i++ { - tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i]) - if err != nil { - panic(err) - } - fetchedExits = append(fetchedExits, tmp.(exitAPI)) - } - } - // Get all (no filters) - limit := 8 - path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) - err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - assertExitAPIs(t, tc.exits, fetchedExits) - - // Get by ethAddr - fetchedExits = []exitAPI{} - limit = 7 - path = fmt.Sprintf( - "%s?hermezEthereumAddress=%s&limit=%d&fromItem=", - endpoint, tc.usrAddr, limit, - ) - err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - assertExitAPIs(t, tc.usrExits, fetchedExits) - // Get by bjj - fetchedExits = []exitAPI{} - limit = 6 - path = fmt.Sprintf( - "%s?BJJ=%s&limit=%d&fromItem=", - endpoint, tc.usrBjj, limit, - ) - err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - assertExitAPIs(t, tc.usrExits, fetchedExits) - // Get by tokenID - fetchedExits = []exitAPI{} - limit = 5 - tokenID := tc.exits[0].Token.TokenID - path = fmt.Sprintf( - "%s?tokenId=%d&limit=%d&fromItem=", - endpoint, tokenID, limit, - ) - err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - tokenIDExits := []exitAPI{} - for i := 0; i < len(tc.exits); i++ { - if tc.exits[i].Token.TokenID == tokenID { - tokenIDExits = append(tokenIDExits, tc.exits[i]) - } - } - assertExitAPIs(t, tokenIDExits, fetchedExits) - // idx - fetchedExits = []exitAPI{} - limit = 4 - idx := tc.exits[0].AccountIdx - path = fmt.Sprintf( - "%s?accountIndex=%s&limit=%d&fromItem=", - endpoint, idx, limit, - ) - err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - idxExits := []exitAPI{} - for i := 0; i < len(tc.exits); i++ { - if tc.exits[i].AccountIdx[6:] == idx[6:] { - idxExits = append(idxExits, tc.exits[i]) - } - } - assertExitAPIs(t, idxExits, fetchedExits) - // batchNum - fetchedExits = []exitAPI{} - limit = 3 - batchNum := tc.exits[0].BatchNum - path = fmt.Sprintf( - "%s?batchNum=%d&limit=%d&fromItem=", - endpoint, batchNum, limit, - ) - err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - batchNumExits := []exitAPI{} - for i := 0; i < len(tc.exits); i++ { - if tc.exits[i].BatchNum == batchNum { - batchNumExits = append(batchNumExits, tc.exits[i]) - } - } - assertExitAPIs(t, batchNumExits, fetchedExits) - // Multiple filters - fetchedExits = []exitAPI{} - limit = 1 - path = fmt.Sprintf( - "%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=", - endpoint, batchNum, tokenID, limit, - ) - err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - mixedExits := []exitAPI{} - flipedExits := []exitAPI{} - for i := 0; i < len(tc.exits); i++ { - if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID { - mixedExits = append(mixedExits, tc.exits[i]) - } - flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i]) - } - assertExitAPIs(t, mixedExits, fetchedExits) - // All, in reverse order - fetchedExits = []exitAPI{} - limit = 5 - path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) - err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter) - assert.NoError(t, err) - assertExitAPIs(t, flipedExits, fetchedExits) - // 400 - path = fmt.Sprintf( - "%s?accountIndex=%s&hermezEthereumAddress=%s", - endpoint, idx, tc.usrAddr, - ) - err = doBadReq("GET", path, nil, 400) - assert.NoError(t, err) - path = fmt.Sprintf("%s?tokenId=X", endpoint) - err = doBadReq("GET", path, nil, 400) - assert.NoError(t, err) - // 404 - path = fmt.Sprintf("%s?batchNum=999999", endpoint) - err = doBadReq("GET", path, nil, 404) - assert.NoError(t, err) - path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint) - err = doBadReq("GET", path, nil, 404) - assert.NoError(t, err) -} - -func TestGetExit(t *testing.T) { - // Get all txs by their ID - endpoint := apiURL + "exits/" - fetchedExits := []exitAPI{} - for _, exit := range tc.exits { - fetchedExit := exitAPI{} - assert.NoError( - t, doGoodReq( - "GET", - fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx), - nil, &fetchedExit, - ), - ) - fetchedExits = append(fetchedExits, fetchedExit) - } - assertExitAPIs(t, tc.exits, fetchedExits) - // 400 - err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400) - assert.NoError(t, err) - err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400) - assert.NoError(t, err) - // 404 - err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404) - assert.NoError(t, err) -} - -func assertExitAPIs(t *testing.T, expected, actual []exitAPI) { - require.Equal(t, len(expected), len(actual)) - for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop - actual[i].ItemID = 0 - if expected[i].Token.USDUpdate == nil { - assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate) - } else { - assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix()) - expected[i].Token.USDUpdate = actual[i].Token.USDUpdate - } - assert.Equal(t, expected[i], actual[i]) - } -} func TestGetConfig(t *testing.T) { endpoint := apiURL + "config" var configTest configAPI
@@ -7,8 +7,6 @@ "strconv"
ethCommon "github.com/ethereum/go-ethereum/common" "github.com/hermeznetwork/hermez-node/common" - "github.com/hermeznetwork/hermez-node/db" - "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/eth" "github.com/iden3/go-iden3-crypto/babyjub" )@@ -38,89 +36,6 @@ if idx == 1 {
return exitIdx } return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx)) -} - -// Exit - -type exitsAPI struct { - Exits []exitAPI `json:"exits"` - Pagination *db.Pagination `json:"pagination"` -} - -func (e *exitsAPI) GetPagination() *db.Pagination { - if e.Exits[0].ItemID < e.Exits[len(e.Exits)-1].ItemID { - e.Pagination.FirstReturnedItem = e.Exits[0].ItemID - e.Pagination.LastReturnedItem = e.Exits[len(e.Exits)-1].ItemID - } else { - e.Pagination.LastReturnedItem = e.Exits[0].ItemID - e.Pagination.FirstReturnedItem = e.Exits[len(e.Exits)-1].ItemID - } - return e.Pagination -} -func (e *exitsAPI) Len() int { return len(e.Exits) } - -type merkleProofAPI struct { - Root string - Siblings []string - OldKey string - OldValue string - IsOld0 bool - Key string - Value string - Fnc int -} - -type exitAPI struct { - ItemID int `json:"itemId"` - BatchNum common.BatchNum `json:"batchNum"` - AccountIdx string `json:"accountIndex"` - MerkleProof merkleProofAPI `json:"merkleProof"` - Balance string `json:"balance"` - InstantWithdrawn *int64 `json:"instantWithdrawn"` - DelayedWithdrawRequest *int64 `json:"delayedWithdrawRequest"` - DelayedWithdrawn *int64 `json:"delayedWithdrawn"` - Token historydb.TokenWithUSD `json:"token"` -} - -func historyExitsToAPI(dbExits []historydb.HistoryExit) []exitAPI { - apiExits := []exitAPI{} - for i := 0; i < len(dbExits); i++ { - exit := exitAPI{ - ItemID: dbExits[i].ItemID, - BatchNum: dbExits[i].BatchNum, - AccountIdx: idxToHez(dbExits[i].AccountIdx, dbExits[i].TokenSymbol), - MerkleProof: merkleProofAPI{ - Root: dbExits[i].MerkleProof.Root.String(), - OldKey: dbExits[i].MerkleProof.OldKey.String(), - OldValue: dbExits[i].MerkleProof.OldValue.String(), - IsOld0: dbExits[i].MerkleProof.IsOld0, - Key: dbExits[i].MerkleProof.Key.String(), - Value: dbExits[i].MerkleProof.Value.String(), - Fnc: dbExits[i].MerkleProof.Fnc, - }, - Balance: dbExits[i].Balance.String(), - InstantWithdrawn: dbExits[i].InstantWithdrawn, - DelayedWithdrawRequest: dbExits[i].DelayedWithdrawRequest, - DelayedWithdrawn: dbExits[i].DelayedWithdrawn, - Token: historydb.TokenWithUSD{ - TokenID: dbExits[i].TokenID, - EthBlockNum: dbExits[i].TokenEthBlockNum, - EthAddr: dbExits[i].TokenEthAddr, - Name: dbExits[i].TokenName, - Symbol: dbExits[i].TokenSymbol, - Decimals: dbExits[i].TokenDecimals, - USD: dbExits[i].TokenUSD, - USDUpdate: dbExits[i].TokenUSDUpdate, - }, - } - siblings := []string{} - for j := 0; j < len(dbExits[i].MerkleProof.Siblings); j++ { - siblings = append(siblings, dbExits[i].MerkleProof.Siblings[j].String()) - } - exit.MerkleProof.Siblings = siblings - apiExits = append(apiExits, exit) - } - return apiExits } // Config
@@ -0,0 +1,72 @@
+package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/hermeznetwork/hermez-node/db" + "github.com/hermeznetwork/hermez-node/db/historydb" +) + +func getExits(c *gin.Context) { + // Get query parameters + // Account filters + tokenID, addr, bjj, idx, err := parseAccountFilters(c) + if err != nil { + retBadReq(err, c) + return + } + // BatchNum + batchNum, err := parseQueryUint("batchNum", nil, 0, maxUint32, c) + if err != nil { + retBadReq(err, c) + return + } + // Pagination + fromItem, order, limit, err := parsePagination(c) + if err != nil { + retBadReq(err, c) + return + } + + // Fetch exits from historyDB + exits, pagination, err := h.GetExitsAPI( + addr, bjj, tokenID, idx, batchNum, fromItem, limit, order, + ) + if err != nil { + retSQLErr(err, c) + return + } + + // Build succesfull response + type exitsResponse struct { + Exits []historydb.ExitAPI `json:"exits"` + Pagination *db.Pagination `json:"pagination"` + } + c.JSON(http.StatusOK, &exitsResponse{ + Exits: exits, + Pagination: pagination, + }) +} + +func getExit(c *gin.Context) { + // Get batchNum and accountIndex + batchNum, err := parseParamUint("batchNum", nil, 0, maxUint32, c) + if err != nil { + retBadReq(err, c) + return + } + idx, err := parseParamIdx(c) + if err != nil { + retBadReq(err, c) + return + } + // Fetch tx from historyDB + exit, err := h.GetExitAPI(batchNum, idx) + if err != nil { + retSQLErr(err, c) + return + } + // Build succesfull response + c.JSON(http.StatusOK, exit) +}
@@ -0,0 +1,276 @@
+package api + +import ( + "fmt" + "testing" + + "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/db" + "github.com/hermeznetwork/hermez-node/db/historydb" + "github.com/mitchellh/copystructure" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testCVP struct { + Root string + Siblings []string + OldKey string + OldValue string + IsOld0 bool + Key string + Value string + Fnc int +} + +type testExit struct { + ItemID int `json:"itemId"` + BatchNum common.BatchNum `json:"batchNum"` + AccountIdx string `json:"accountIndex"` + MerkleProof testCVP `json:"merkleProof"` + Balance string `json:"balance"` + InstantWithdrawn *int64 `json:"instantWithdrawn"` + DelayedWithdrawRequest *int64 `json:"delayedWithdrawRequest"` + DelayedWithdrawn *int64 `json:"delayedWithdrawn"` + Token historydb.TokenWithUSD `json:"token"` +} + +type testExitsResponse struct { + Exits []testExit `json:"exits"` + Pagination *db.Pagination `json:"pagination"` +} + +func (t *testExitsResponse) GetPagination() *db.Pagination { + if t.Exits[0].ItemID < t.Exits[len(t.Exits)-1].ItemID { + t.Pagination.FirstReturnedItem = t.Exits[0].ItemID + t.Pagination.LastReturnedItem = t.Exits[len(t.Exits)-1].ItemID + } else { + t.Pagination.LastReturnedItem = t.Exits[0].ItemID + t.Pagination.FirstReturnedItem = t.Exits[len(t.Exits)-1].ItemID + } + return t.Pagination +} + +func (t *testExitsResponse) Len() int { + return len(t.Exits) +} + +func genTestExits( + commonExits []common.ExitInfo, + tokens []historydb.TokenWithUSD, + accs []common.Account, + usrIdxs []string, +) (usrExits, allExits []testExit) { + allExits = []testExit{} + for _, exit := range commonExits { + token := getTokenByIdx(exit.AccountIdx, tokens, accs) + siblings := []string{} + for i := 0; i < len(exit.MerkleProof.Siblings); i++ { + siblings = append(siblings, exit.MerkleProof.Siblings[i].String()) + } + allExits = append(allExits, testExit{ + BatchNum: exit.BatchNum, + AccountIdx: idxToHez(exit.AccountIdx, token.Symbol), + MerkleProof: testCVP{ + Root: exit.MerkleProof.Root.String(), + Siblings: siblings, + OldKey: exit.MerkleProof.OldKey.String(), + OldValue: exit.MerkleProof.OldValue.String(), + IsOld0: exit.MerkleProof.IsOld0, + Key: exit.MerkleProof.Key.String(), + Value: exit.MerkleProof.Value.String(), + Fnc: exit.MerkleProof.Fnc, + }, + Balance: exit.Balance.String(), + InstantWithdrawn: exit.InstantWithdrawn, + DelayedWithdrawRequest: exit.DelayedWithdrawRequest, + DelayedWithdrawn: exit.DelayedWithdrawn, + Token: token, + }) + } + usrExits = []testExit{} + for _, exit := range allExits { + for _, idx := range usrIdxs { + if idx == exit.AccountIdx { + usrExits = append(usrExits, exit) + break + } + } + } + return usrExits, allExits +} + +func TestGetExits(t *testing.T) { + endpoint := apiURL + "exits" + fetchedExits := []testExit{} + appendIter := func(intr interface{}) { + for i := 0; i < len(intr.(*testExitsResponse).Exits); i++ { + tmp, err := copystructure.Copy(intr.(*testExitsResponse).Exits[i]) + if err != nil { + panic(err) + } + fetchedExits = append(fetchedExits, tmp.(testExit)) + } + } + // Get all (no filters) + limit := 8 + path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) + err := doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + assertExitAPIs(t, tc.exits, fetchedExits) + + // Get by ethAddr + fetchedExits = []testExit{} + limit = 7 + path = fmt.Sprintf( + "%s?hermezEthereumAddress=%s&limit=%d&fromItem=", + endpoint, tc.usrAddr, limit, + ) + err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + assertExitAPIs(t, tc.usrExits, fetchedExits) + // Get by bjj + fetchedExits = []testExit{} + limit = 6 + path = fmt.Sprintf( + "%s?BJJ=%s&limit=%d&fromItem=", + endpoint, tc.usrBjj, limit, + ) + err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + assertExitAPIs(t, tc.usrExits, fetchedExits) + // Get by tokenID + fetchedExits = []testExit{} + limit = 5 + tokenID := tc.exits[0].Token.TokenID + path = fmt.Sprintf( + "%s?tokenId=%d&limit=%d&fromItem=", + endpoint, tokenID, limit, + ) + err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + tokenIDExits := []testExit{} + for i := 0; i < len(tc.exits); i++ { + if tc.exits[i].Token.TokenID == tokenID { + tokenIDExits = append(tokenIDExits, tc.exits[i]) + } + } + assertExitAPIs(t, tokenIDExits, fetchedExits) + // idx + fetchedExits = []testExit{} + limit = 4 + idx := tc.exits[0].AccountIdx + path = fmt.Sprintf( + "%s?accountIndex=%s&limit=%d&fromItem=", + endpoint, idx, limit, + ) + err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + idxExits := []testExit{} + for i := 0; i < len(tc.exits); i++ { + if tc.exits[i].AccountIdx[6:] == idx[6:] { + idxExits = append(idxExits, tc.exits[i]) + } + } + assertExitAPIs(t, idxExits, fetchedExits) + // batchNum + fetchedExits = []testExit{} + limit = 3 + batchNum := tc.exits[0].BatchNum + path = fmt.Sprintf( + "%s?batchNum=%d&limit=%d&fromItem=", + endpoint, batchNum, limit, + ) + err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + batchNumExits := []testExit{} + for i := 0; i < len(tc.exits); i++ { + if tc.exits[i].BatchNum == batchNum { + batchNumExits = append(batchNumExits, tc.exits[i]) + } + } + assertExitAPIs(t, batchNumExits, fetchedExits) + // Multiple filters + fetchedExits = []testExit{} + limit = 1 + path = fmt.Sprintf( + "%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=", + endpoint, batchNum, tokenID, limit, + ) + err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + mixedExits := []testExit{} + flipedExits := []testExit{} + for i := 0; i < len(tc.exits); i++ { + if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID { + mixedExits = append(mixedExits, tc.exits[i]) + } + flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i]) + } + assertExitAPIs(t, mixedExits, fetchedExits) + // All, in reverse order + fetchedExits = []testExit{} + limit = 5 + path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) + err = doGoodReqPaginated(path, historydb.OrderDesc, &testExitsResponse{}, appendIter) + assert.NoError(t, err) + assertExitAPIs(t, flipedExits, fetchedExits) + // 400 + path = fmt.Sprintf( + "%s?accountIndex=%s&hermezEthereumAddress=%s", + endpoint, idx, tc.usrAddr, + ) + err = doBadReq("GET", path, nil, 400) + assert.NoError(t, err) + path = fmt.Sprintf("%s?tokenId=X", endpoint) + err = doBadReq("GET", path, nil, 400) + assert.NoError(t, err) + // 404 + path = fmt.Sprintf("%s?batchNum=999999", endpoint) + err = doBadReq("GET", path, nil, 404) + assert.NoError(t, err) + path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint) + err = doBadReq("GET", path, nil, 404) + assert.NoError(t, err) +} + +func TestGetExit(t *testing.T) { + // Get all txs by their ID + endpoint := apiURL + "exits/" + fetchedExits := []testExit{} + for _, exit := range tc.exits { + fetchedExit := testExit{} + assert.NoError( + t, doGoodReq( + "GET", + fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx), + nil, &fetchedExit, + ), + ) + fetchedExits = append(fetchedExits, fetchedExit) + } + assertExitAPIs(t, tc.exits, fetchedExits) + // 400 + err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400) + assert.NoError(t, err) + err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400) + assert.NoError(t, err) + // 404 + err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404) + assert.NoError(t, err) +} + +func assertExitAPIs(t *testing.T, expected, actual []testExit) { + require.Equal(t, len(expected), len(actual)) + for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop + actual[i].ItemID = 0 + actual[i].Token.ItemID = 0 + if expected[i].Token.USDUpdate == nil { + assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate) + } else { + assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix()) + expected[i].Token.USDUpdate = actual[i].Token.USDUpdate + } + assert.Equal(t, expected[i], actual[i]) + } +}
@@ -36,67 +36,6 @@ func getAccount(c *gin.Context) {
} -func getExits(c *gin.Context) { - // Get query parameters - // Account filters - tokenID, addr, bjj, idx, err := parseAccountFilters(c) - if err != nil { - retBadReq(err, c) - return - } - // BatchNum - batchNum, err := parseQueryUint("batchNum", nil, 0, maxUint32, c) - if err != nil { - retBadReq(err, c) - return - } - // Pagination - fromItem, order, limit, err := parsePagination(c) - if err != nil { - retBadReq(err, c) - return - } - - // Fetch exits from historyDB - exits, pagination, err := h.GetExits( - addr, bjj, tokenID, idx, batchNum, fromItem, limit, order, - ) - if err != nil { - retSQLErr(err, c) - return - } - - // Build succesfull response - apiExits := historyExitsToAPI(exits) - c.JSON(http.StatusOK, &exitsAPI{ - Exits: apiExits, - Pagination: pagination, - }) -} - -func getExit(c *gin.Context) { - // Get batchNum and accountIndex - batchNum, err := parseParamUint("batchNum", nil, 0, maxUint32, c) - if err != nil { - retBadReq(err, c) - return - } - idx, err := parseParamIdx(c) - if err != nil { - retBadReq(err, c) - return - } - // Fetch tx from historyDB - exit, err := h.GetExit(batchNum, idx) - if err != nil { - retSQLErr(err, c) - return - } - apiExits := historyExitsToAPI([]historydb.HistoryExit{*exit}) - // Build succesfull response - c.JSON(http.StatusOK, apiExits[0]) -} - func getSlots(c *gin.Context) { }
@@ -152,7 +152,13 @@ l2tx := common.L2Tx(*tx)
return &l2tx } -func genTestTxs(genericTxs []txSortFielder, usrIdxs []string, accs []common.Account, tokens []historydb.TokenWithUSD, blocks []common.Block) (usrTxs []testTx, allTxs []testTx) { +func genTestTxs( + genericTxs []txSortFielder, + usrIdxs []string, + accs []common.Account, + tokens []historydb.TokenWithUSD, + blocks []common.Block, +) (usrTxs []testTx, allTxs []testTx) { usrTxs = []testTx{} allTxs = []testTx{} isUsrTx := func(tx testTx) bool {
@@ -156,20 +156,6 @@ *s = StrHezEthAddr(addr)
return nil } -// StrEthSignature is used to unmarshal EthSignature directly into an alias of []byte -type StrEthSignature []byte - -// UnmarshalText unmarshals a StrEthSignature -func (s *StrEthSignature) UnmarshalText(text []byte) error { - without0x := strings.TrimPrefix(string(text), "0x") - signature, err := hex.DecodeString(without0x) - if err != nil { - return err - } - *s = signature - return nil -} - // HezBJJ is used to scan/value *babyjub.PublicKey directly into strings that follow the BJJ public key hez fotmat (^hez:[A-Za-z0-9_-]{44}$) from/to sql DBs. // It assumes that *babyjub.PublicKey are inserted/fetched to/from the DB using the default Scan/Value interface type HezBJJ string@@ -277,7 +263,7 @@ *s = StrHezIdx(common.Idx(idxInt))
return nil } -// EthSignature is used to scan/value []byte representing an Ethereum signatue directly into strings from/to sql DBs. +// EthSignature is used to scan/value []byte representing an Ethereum signature directly into strings from/to sql DBs. type EthSignature string // NewEthSignature creates a *EthSignature from []byte@@ -311,3 +297,14 @@ func (e EthSignature) Value() (driver.Value, error) {
without0x := strings.TrimPrefix(string(e), "0x") return hex.DecodeString(without0x) } + +// UnmarshalText unmarshals a StrEthSignature +func (e *EthSignature) UnmarshalText(text []byte) error { + without0x := strings.TrimPrefix(string(text), "0x") + signature, err := hex.DecodeString(without0x) + if err != nil { + return err + } + *e = EthSignature([]byte(signature)) + return nil +}
@@ -891,12 +891,17 @@ )
return db.SlicePtrsToSlice(exits).([]common.ExitInfo), err } -// GetExit returns a exit from the DB -func (hdb *HistoryDB) GetExit(batchNum *uint, idx *common.Idx) (*HistoryExit, error) { - exit := &HistoryExit{} +// GetExitAPI returns a exit from the DB +func (hdb *HistoryDB) GetExitAPI(batchNum *uint, idx *common.Idx) (*ExitAPI, error) { + exit := &ExitAPI{} err := meddler.QueryRow( - hdb.db, exit, `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block, - token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update + hdb.db, exit, `SELECT exit_tree.item_id, exit_tree.batch_num, + hez_idx(exit_tree.account_idx, token.symbol) AS account_idx, + exit_tree.merkle_proof, exit_tree.balance, exit_tree.instant_withdrawn, + exit_tree.delayed_withdraw_request, exit_tree.delayed_withdrawn, + token.token_id, token.item_id AS token_item_id, + token.eth_block_num AS token_block, token.eth_addr, token.name, token.symbol, + token.decimals, token.usd, token.usd_update FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx INNER JOIN token ON account.token_id = token.token_id WHERE exit_tree.batch_num = $1 AND exit_tree.account_idx = $2;`, batchNum, idx,@@ -904,20 +909,25 @@ )
return exit, err } -// GetExits returns a list of exits from the DB and pagination info -func (hdb *HistoryDB) GetExits( +// GetExitsAPI returns a list of exits from the DB and pagination info +func (hdb *HistoryDB) GetExitsAPI( ethAddr *ethCommon.Address, bjj *babyjub.PublicKey, tokenID *common.TokenID, idx *common.Idx, batchNum *uint, fromItem, limit *uint, order string, -) ([]HistoryExit, *db.Pagination, error) { +) ([]ExitAPI, *db.Pagination, error) { if ethAddr != nil && bjj != nil { return nil, nil, errors.New("ethAddr and bjj are incompatible") } var query string var args []interface{} - queryStr := `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block, - token.eth_addr, token.name, token.symbol, token.decimals, token.usd, - token.usd_update, COUNT(*) OVER() AS total_items, MIN(exit_tree.item_id) OVER() AS first_item, MAX(exit_tree.item_id) OVER() AS last_item + queryStr := `SELECT exit_tree.item_id, exit_tree.batch_num, + hez_idx(exit_tree.account_idx, token.symbol) AS account_idx, + exit_tree.merkle_proof, exit_tree.balance, exit_tree.instant_withdrawn, + exit_tree.delayed_withdraw_request, exit_tree.delayed_withdrawn, + token.token_id, token.item_id AS token_item_id, + token.eth_block_num AS token_block, token.eth_addr, token.name, token.symbol, + token.decimals, token.usd, token.usd_update, COUNT(*) OVER() AS total_items, + MIN(exit_tree.item_id) OVER() AS first_item, MAX(exit_tree.item_id) OVER() AS last_item FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx INNER JOIN token ON account.token_id = token.token_id ` // Apply filters@@ -989,14 +999,14 @@ }
queryStr += fmt.Sprintf("LIMIT %d;", *limit) query = hdb.db.Rebind(queryStr) // log.Debug(query) - exits := []*HistoryExit{} + exits := []*ExitAPI{} if err := meddler.QueryAll(hdb.db, &exits, query, args...); err != nil { return nil, nil, err } if len(exits) == 0 { return nil, nil, sql.ErrNoRows } - return db.SlicePtrsToSlice(exits).([]HistoryExit), &db.Pagination{ + return db.SlicePtrsToSlice(exits).([]ExitAPI), &db.Pagination{ TotalItems: exits[0].TotalItems, FirstItem: exits[0].FirstItem, LastItem: exits[0].LastItem,
@@ -151,14 +151,14 @@ FirstItem int `json:"-" meddler:"first_item"`
LastItem int `json:"-" meddler:"last_item"` } -// HistoryExit is a representation of a exit with additional information +// ExitAPI is a representation of a exit with additional information // required by the API, and extracted by joining token table -type HistoryExit struct { +type ExitAPI struct { ItemID int `meddler:"item_id"` BatchNum common.BatchNum `meddler:"batch_num"` - AccountIdx common.Idx `meddler:"account_idx"` + AccountIdx apitypes.HezIdx `meddler:"account_idx"` MerkleProof *merkletree.CircomVerifierProof `meddler:"merkle_proof,json"` - Balance *big.Int `meddler:"balance,bigint"` + Balance apitypes.BigIntStr `meddler:"balance"` InstantWithdrawn *int64 `meddler:"instant_withdrawn"` DelayedWithdrawRequest *int64 `meddler:"delayed_withdraw_request"` DelayedWithdrawn *int64 `meddler:"delayed_withdrawn"`@@ -166,6 +166,7 @@ TotalItems int `meddler:"total_items"`
FirstItem int `meddler:"first_item"` LastItem int `meddler:"last_item"` TokenID common.TokenID `meddler:"token_id"` + TokenItemID int `meddler:"token_item_id"` TokenEthBlockNum int64 `meddler:"token_block"` TokenEthAddr ethCommon.Address `meddler:"eth_addr"` TokenName string `meddler:"name"`@@ -175,6 +176,45 @@ TokenUSD *float64 `meddler:"usd"`
TokenUSDUpdate *time.Time `meddler:"usd_update"` } +// MarshalJSON is used to neast some of the fields of ExitAPI +// without the need of auxiliar structs +func (e ExitAPI) MarshalJSON() ([]byte, error) { + siblings := []string{} + for i := 0; i < len(e.MerkleProof.Siblings); i++ { + siblings = append(siblings, e.MerkleProof.Siblings[i].String()) + } + return json.Marshal(map[string]interface{}{ + "itemId": e.ItemID, + "batchNum": e.BatchNum, + "accountIndex": e.AccountIdx, + "merkleProof": map[string]interface{}{ + "Root": e.MerkleProof.Root.String(), + "Siblings": siblings, + "OldKey": e.MerkleProof.OldKey.String(), + "OldValue": e.MerkleProof.OldValue.String(), + "IsOld0": e.MerkleProof.IsOld0, + "Key": e.MerkleProof.Key.String(), + "Value": e.MerkleProof.Value.String(), + "Fnc": e.MerkleProof.Fnc, + }, + "balance": e.Balance, + "instantWithdrawn": e.InstantWithdrawn, + "delayedWithdrawRequest": e.DelayedWithdrawRequest, + "delayedWithdrawn": e.DelayedWithdrawn, + "token": map[string]interface{}{ + "id": e.TokenID, + "itemId": e.TokenItemID, + "ethereumBlockNum": e.TokenEthBlockNum, + "ethereumAddress": e.TokenEthAddr, + "name": e.TokenName, + "symbol": e.TokenSymbol, + "decimals": e.TokenDecimals, + "USD": e.TokenUSD, + "fiatUpdate": e.TokenUSDUpdate, + }, + }) +} + // CoordinatorAPI is a representation of a coordinator with additional information // required by the API type CoordinatorAPI struct {@@ -198,16 +238,15 @@ EthBlockHash ethCommon.Hash `json:"ethereumBlockHash" meddler:"hash"`
Timestamp time.Time `json:"timestamp" meddler:"timestamp,utctime"` ForgerAddr ethCommon.Address `json:"forgerAddr" meddler:"forger_addr"` CollectedFees apitypes.CollectedFees `json:"collectedFees" meddler:"fees_collected,json"` - // CollectedFees map[common.TokenID]*big.Int `json:"collectedFees" meddler:"fees_collected,json"` - TotalFeesUSD *float64 `json:"historicTotalCollectedFeesUSD" meddler:"total_fees_usd"` - StateRoot apitypes.BigIntStr `json:"stateRoot" meddler:"state_root"` - NumAccounts int `json:"numAccounts" meddler:"num_accounts"` - ExitRoot apitypes.BigIntStr `json:"exitRoot" meddler:"exit_root"` - ForgeL1TxsNum *int64 `json:"forgeL1TransactionsNum" meddler:"forge_l1_txs_num"` - SlotNum int64 `json:"slotNum" meddler:"slot_num"` - TotalItems int `json:"-" meddler:"total_items"` - FirstItem int `json:"-" meddler:"first_item"` - LastItem int `json:"-" meddler:"last_item"` + TotalFeesUSD *float64 `json:"historicTotalCollectedFeesUSD" meddler:"total_fees_usd"` + StateRoot apitypes.BigIntStr `json:"stateRoot" meddler:"state_root"` + NumAccounts int `json:"numAccounts" meddler:"num_accounts"` + ExitRoot apitypes.BigIntStr `json:"exitRoot" meddler:"exit_root"` + ForgeL1TxsNum *int64 `json:"forgeL1TransactionsNum" meddler:"forge_l1_txs_num"` + SlotNum int64 `json:"slotNum" meddler:"slot_num"` + TotalItems int `json:"-" meddler:"total_items"` + FirstItem int `json:"-" meddler:"first_item"` + LastItem int `json:"-" meddler:"last_item"` } // Network define status of the network
@@ -115,6 +115,7 @@ },
}) } +// AccountCreationAuthAPI represents an account creation auth in the expected format by the API type AccountCreationAuthAPI struct { EthAddr apitypes.HezEthAddr `json:"hezEthereumAddress" meddler:"eth_addr" ` BJJ apitypes.HezBJJ `json:"bjj" meddler:"bjj" `