Browse Source

Refactor api exits

feature/sql-semaphore1
Arnau B 3 years ago
parent
commit
233ecc4704
11 changed files with 452 additions and 407 deletions
  1. +4
    -4
      api/accountcreationauths.go
  2. +4
    -214
      api/api_test.go
  3. +0
    -85
      api/dbtoapistructs.go
  4. +72
    -0
      api/exits.go
  5. +276
    -0
      api/exits_test.go
  6. +0
    -61
      api/handlers.go
  7. +7
    -1
      api/txshistory_test.go
  8. +12
    -15
      apitypes/apitypes.go
  9. +23
    -13
      db/historydb/historydb.go
  10. +53
    -14
      db/historydb/views.go
  11. +1
    -0
      db/l2db/views.go

+ 4
- 4
api/accountcreationauths.go

@ -52,10 +52,10 @@ func getAccountCreationAuth(c *gin.Context) {
}
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 {

+ 4
- 214
api/api_test.go

@ -27,9 +27,7 @@ import (
"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 @@ type testCommon struct {
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 @@ func TestMain(m *testing.M) {
}
}
// 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 @@ func TestMain(m *testing.M) {
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 @@ func TestMain(m *testing.M) {
accs: accs,
usrTxs: usrTxs,
allTxs: allTxs,
exits: apiExits,
exits: allExits,
usrExits: usrExits,
poolTxsToSend: poolTxsToSend,
poolTxsToReceive: poolTxsToReceive,
@ -390,179 +353,6 @@ func TestMain(m *testing.M) {
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

+ 0
- 85
api/dbtoapistructs.go

@ -7,8 +7,6 @@ import (
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"
)
@ -40,89 +38,6 @@ func idxToHez(idx common.Idx, tokenSymbol string) string {
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
type rollupConstants struct {

+ 72
- 0
api/exits.go

@ -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)
}

+ 276
- 0
api/exits_test.go

@ -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])
}
}

+ 0
- 61
api/handlers.go

@ -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) {
}

+ 7
- 1
api/txshistory_test.go

@ -152,7 +152,13 @@ func (tx *wrappedL2) L2() *common.L2Tx {
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 {

+ 12
- 15
apitypes/apitypes.go

@ -156,20 +156,6 @@ func (s *StrHezEthAddr) UnmarshalText(text []byte) error {
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 @@ func (s *StrHezIdx) UnmarshalText(text []byte) error {
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
}

+ 23
- 13
db/historydb/historydb.go

@ -891,12 +891,17 @@ func (hdb *HistoryDB) GetAllExits() ([]common.ExitInfo, error) {
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 @@ func (hdb *HistoryDB) GetExit(batchNum *uint, idx *common.Idx) (*HistoryExit, er
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 @@ func (hdb *HistoryDB) GetExits(
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,

+ 53
- 14
db/historydb/views.go

@ -151,14 +151,14 @@ type TokenWithUSD struct {
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 @@ type HistoryExit struct {
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 @@ type HistoryExit struct {
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 @@ type BatchAPI struct {
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

+ 1
- 0
db/l2db/views.go

@ -115,6 +115,7 @@ func (tx PoolTxAPI) MarshalJSON() ([]byte, error) {
})
}
// 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" `

Loading…
Cancel
Save