mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-07 11:26:44 +01:00
Merge pull request #245 from hermeznetwork/refactor/api-exits-and-auth
Refactor/api exits and auth
This commit is contained in:
68
api/accountcreationauths.go
Normal file
68
api/accountcreationauths.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/hermeznetwork/hermez-node/apitypes"
|
||||||
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
|
)
|
||||||
|
|
||||||
|
func postAccountCreationAuth(c *gin.Context) {
|
||||||
|
// Parse body
|
||||||
|
var apiAuth receivedAuth
|
||||||
|
if err := c.ShouldBindJSON(&apiAuth); err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// API to common + verify signature
|
||||||
|
commonAuth := accountCreationAuthAPIToCommon(&apiAuth)
|
||||||
|
if !commonAuth.VerifySignature() {
|
||||||
|
retBadReq(errors.New("invalid signature"), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Insert to DB
|
||||||
|
if err := l2.AddAccountCreationAuth(commonAuth); err != nil {
|
||||||
|
retSQLErr(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Return OK
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccountCreationAuth(c *gin.Context) {
|
||||||
|
// Get hezEthereumAddress
|
||||||
|
addr, err := parseParamHezEthAddr(c)
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Fetch auth from l2DB
|
||||||
|
auth, err := l2.GetAccountCreationAuthAPI(*addr)
|
||||||
|
if err != nil {
|
||||||
|
retSQLErr(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Build succesfull response
|
||||||
|
c.JSON(http.StatusOK, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
type receivedAuth struct {
|
||||||
|
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 {
|
||||||
|
return &common.AccountCreationAuth{
|
||||||
|
EthAddr: ethCommon.Address(apiAuth.EthAddr),
|
||||||
|
BJJ: (*babyjub.PublicKey)(&apiAuth.BJJ),
|
||||||
|
Signature: []byte(apiAuth.Signature),
|
||||||
|
Timestamp: apiAuth.Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
99
api/accountcreationauths_test.go
Normal file
99
api/accountcreationauths_test.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testAuth struct {
|
||||||
|
EthAddr string `json:"hezEthereumAddress" binding:"required"`
|
||||||
|
BJJ string `json:"bjj" binding:"required"`
|
||||||
|
Signature string `json:"signature" binding:"required"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func genTestAuths(auths []*common.AccountCreationAuth) []testAuth {
|
||||||
|
testAuths := []testAuth{}
|
||||||
|
for _, auth := range auths {
|
||||||
|
testAuths = append(testAuths, testAuth{
|
||||||
|
EthAddr: ethAddrToHez(auth.EthAddr),
|
||||||
|
BJJ: bjjToString(auth.BJJ),
|
||||||
|
Signature: "0x" + hex.EncodeToString(auth.Signature),
|
||||||
|
Timestamp: auth.Timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return testAuths
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccountCreationAuth(t *testing.T) {
|
||||||
|
// POST
|
||||||
|
endpoint := apiURL + "account-creation-authorization"
|
||||||
|
for _, auth := range tc.auths {
|
||||||
|
jsonAuthBytes, err := json.Marshal(auth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
jsonAuthReader := bytes.NewReader(jsonAuthBytes)
|
||||||
|
fmt.Println(string(jsonAuthBytes))
|
||||||
|
assert.NoError(
|
||||||
|
t, doGoodReq(
|
||||||
|
"POST",
|
||||||
|
endpoint,
|
||||||
|
jsonAuthReader, nil,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// GET
|
||||||
|
endpoint += "/"
|
||||||
|
for _, auth := range tc.auths {
|
||||||
|
fetchedAuth := testAuth{}
|
||||||
|
assert.NoError(
|
||||||
|
t, doGoodReq(
|
||||||
|
"GET",
|
||||||
|
endpoint+auth.EthAddr,
|
||||||
|
nil, &fetchedAuth,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assertAuth(t, auth, fetchedAuth)
|
||||||
|
}
|
||||||
|
// POST
|
||||||
|
// 400
|
||||||
|
// Wrong addr
|
||||||
|
badAuth := tc.auths[0]
|
||||||
|
badAuth.EthAddr = ethAddrToHez(ethCommon.BigToAddress(big.NewInt(1)))
|
||||||
|
jsonAuthBytes, err := json.Marshal(badAuth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
jsonAuthReader := bytes.NewReader(jsonAuthBytes)
|
||||||
|
err = doBadReq("POST", endpoint, jsonAuthReader, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Wrong signature
|
||||||
|
badAuth = tc.auths[0]
|
||||||
|
badAuth.Signature = badAuth.Signature[:len(badAuth.Signature)-1]
|
||||||
|
badAuth.Signature += "F"
|
||||||
|
jsonAuthBytes, err = json.Marshal(badAuth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
jsonAuthReader = bytes.NewReader(jsonAuthBytes)
|
||||||
|
err = doBadReq("POST", endpoint, jsonAuthReader, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// GET
|
||||||
|
// 400
|
||||||
|
err = doBadReq("GET", endpoint+"hez:0xFooBar", nil, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 404
|
||||||
|
err = doBadReq("GET", endpoint+"hez:0x0000000000000000000000000000000000000001", nil, 404)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertAuth(t *testing.T, expected, actual testAuth) {
|
||||||
|
// timestamp should be very close to now
|
||||||
|
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
||||||
|
expected.Timestamp = actual.Timestamp
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
296
api/api_test.go
296
api/api_test.go
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -28,9 +27,7 @@ import (
|
|||||||
"github.com/hermeznetwork/hermez-node/log"
|
"github.com/hermeznetwork/hermez-node/log"
|
||||||
"github.com/hermeznetwork/hermez-node/test"
|
"github.com/hermeznetwork/hermez-node/test"
|
||||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
"github.com/mitchellh/copystructure"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiPort = ":4010"
|
const apiPort = ":4010"
|
||||||
@@ -47,11 +44,11 @@ type testCommon struct {
|
|||||||
accs []common.Account
|
accs []common.Account
|
||||||
usrTxs []testTx
|
usrTxs []testTx
|
||||||
allTxs []testTx
|
allTxs []testTx
|
||||||
exits []exitAPI
|
exits []testExit
|
||||||
usrExits []exitAPI
|
usrExits []testExit
|
||||||
poolTxsToSend []testPoolTxSend
|
poolTxsToSend []testPoolTxSend
|
||||||
poolTxsToReceive []testPoolTxReceive
|
poolTxsToReceive []testPoolTxReceive
|
||||||
auths []accountCreationAuthAPI
|
auths []testAuth
|
||||||
router *swagger.Router
|
router *swagger.Router
|
||||||
bids []testBid
|
bids []testBid
|
||||||
}
|
}
|
||||||
@@ -287,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
|
// Coordinators
|
||||||
const nCoords = 10
|
const nCoords = 10
|
||||||
coords := test.GenCoordinators(nCoords, blocks)
|
coords := test.GenCoordinators(nCoords, blocks)
|
||||||
@@ -336,15 +297,6 @@ func TestMain(m *testing.M) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
// Account creation auth
|
|
||||||
const nAuths = 5
|
|
||||||
auths := test.GenAuths(nAuths)
|
|
||||||
// Transform auths to API format
|
|
||||||
apiAuths := []accountCreationAuthAPI{}
|
|
||||||
for _, auth := range auths {
|
|
||||||
apiAuth := accountCreationAuthToAPI(auth)
|
|
||||||
apiAuths = append(apiAuths, *apiAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bids
|
// Bids
|
||||||
const nBids = 10
|
const nBids = 10
|
||||||
@@ -358,6 +310,7 @@ func TestMain(m *testing.M) {
|
|||||||
usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
|
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.
|
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)
|
testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
|
||||||
|
usrExits, allExits := genTestExits(exits, tokensUSD, accs, usrIdxs)
|
||||||
tc = testCommon{
|
tc = testCommon{
|
||||||
blocks: blocks,
|
blocks: blocks,
|
||||||
tokens: tokensUSD,
|
tokens: tokensUSD,
|
||||||
@@ -369,11 +322,11 @@ func TestMain(m *testing.M) {
|
|||||||
accs: accs,
|
accs: accs,
|
||||||
usrTxs: usrTxs,
|
usrTxs: usrTxs,
|
||||||
allTxs: allTxs,
|
allTxs: allTxs,
|
||||||
exits: apiExits,
|
exits: allExits,
|
||||||
usrExits: usrExits,
|
usrExits: usrExits,
|
||||||
poolTxsToSend: poolTxsToSend,
|
poolTxsToSend: poolTxsToSend,
|
||||||
poolTxsToReceive: poolTxsToReceive,
|
poolTxsToReceive: poolTxsToReceive,
|
||||||
auths: apiAuths,
|
auths: genTestAuths(test.GenAuths(5)),
|
||||||
router: router,
|
router: router,
|
||||||
bids: genTestBids(blocks, coordinators, bids),
|
bids: genTestBids(blocks, coordinators, bids),
|
||||||
}
|
}
|
||||||
@@ -400,179 +353,6 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(result)
|
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) {
|
func TestGetConfig(t *testing.T) {
|
||||||
endpoint := apiURL + "config"
|
endpoint := apiURL + "config"
|
||||||
var configTest configAPI
|
var configTest configAPI
|
||||||
@@ -581,70 +361,6 @@ func TestGetConfig(t *testing.T) {
|
|||||||
assert.Equal(t, cg, &configTest)
|
assert.Equal(t, cg, &configTest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountCreationAuth(t *testing.T) {
|
|
||||||
// POST
|
|
||||||
endpoint := apiURL + "account-creation-authorization"
|
|
||||||
for _, auth := range tc.auths {
|
|
||||||
jsonAuthBytes, err := json.Marshal(auth)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
jsonAuthReader := bytes.NewReader(jsonAuthBytes)
|
|
||||||
fmt.Println(string(jsonAuthBytes))
|
|
||||||
assert.NoError(
|
|
||||||
t, doGoodReq(
|
|
||||||
"POST",
|
|
||||||
endpoint,
|
|
||||||
jsonAuthReader, nil,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// GET
|
|
||||||
endpoint += "/"
|
|
||||||
for _, auth := range tc.auths {
|
|
||||||
fetchedAuth := accountCreationAuthAPI{}
|
|
||||||
assert.NoError(
|
|
||||||
t, doGoodReq(
|
|
||||||
"GET",
|
|
||||||
endpoint+auth.EthAddr,
|
|
||||||
nil, &fetchedAuth,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
assertAuth(t, auth, fetchedAuth)
|
|
||||||
}
|
|
||||||
// POST
|
|
||||||
// 400
|
|
||||||
// Wrong addr
|
|
||||||
badAuth := tc.auths[0]
|
|
||||||
badAuth.EthAddr = ethAddrToHez(ethCommon.BigToAddress(big.NewInt(1)))
|
|
||||||
jsonAuthBytes, err := json.Marshal(badAuth)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
jsonAuthReader := bytes.NewReader(jsonAuthBytes)
|
|
||||||
err = doBadReq("POST", endpoint, jsonAuthReader, 400)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
// Wrong signature
|
|
||||||
badAuth = tc.auths[0]
|
|
||||||
badAuth.Signature = badAuth.Signature[:len(badAuth.Signature)-1]
|
|
||||||
badAuth.Signature += "F"
|
|
||||||
jsonAuthBytes, err = json.Marshal(badAuth)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
jsonAuthReader = bytes.NewReader(jsonAuthBytes)
|
|
||||||
err = doBadReq("POST", endpoint, jsonAuthReader, 400)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
// GET
|
|
||||||
// 400
|
|
||||||
err = doBadReq("GET", endpoint+"hez:0xFooBar", nil, 400)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
// 404
|
|
||||||
err = doBadReq("GET", endpoint+"hez:0x0000000000000000000000000000000000000001", nil, 404)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertAuth(t *testing.T, expected, actual accountCreationAuthAPI) {
|
|
||||||
// timestamp should be very close to now
|
|
||||||
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
|
||||||
expected.Timestamp = actual.Timestamp
|
|
||||||
assert.Equal(t, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doGoodReqPaginated(
|
func doGoodReqPaginated(
|
||||||
path, order string,
|
path, order string,
|
||||||
iterStruct db.Paginationer,
|
iterStruct db.Paginationer,
|
||||||
|
|||||||
@@ -2,17 +2,11 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/hermeznetwork/hermez-node/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/hermeznetwork/hermez-node/eth"
|
||||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
)
|
)
|
||||||
@@ -44,89 +38,6 @@ func idxToHez(idx common.Idx, tokenSymbol string) string {
|
|||||||
return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx))
|
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
|
// Config
|
||||||
|
|
||||||
type rollupConstants struct {
|
type rollupConstants struct {
|
||||||
@@ -152,47 +63,3 @@ type configAPI struct {
|
|||||||
AuctionConstants eth.AuctionConstants `json:"auction"`
|
AuctionConstants eth.AuctionConstants `json:"auction"`
|
||||||
WDelayerConstants eth.WDelayerConstants `json:"withdrawalDelayer"`
|
WDelayerConstants eth.WDelayerConstants `json:"withdrawalDelayer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountCreationAuth
|
|
||||||
|
|
||||||
type accountCreationAuthAPI struct {
|
|
||||||
EthAddr string `json:"hezEthereumAddress" binding:"required"`
|
|
||||||
BJJ string `json:"bjj" binding:"required"`
|
|
||||||
Signature string `json:"signature" binding:"required"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func accountCreationAuthToAPI(dbAuth *common.AccountCreationAuth) *accountCreationAuthAPI {
|
|
||||||
return &accountCreationAuthAPI{
|
|
||||||
EthAddr: ethAddrToHez(dbAuth.EthAddr),
|
|
||||||
BJJ: bjjToString(dbAuth.BJJ),
|
|
||||||
Signature: "0x" + hex.EncodeToString(dbAuth.Signature),
|
|
||||||
Timestamp: dbAuth.Timestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func accountCreationAuthAPIToCommon(apiAuth *accountCreationAuthAPI) (*common.AccountCreationAuth, error) {
|
|
||||||
ethAddr, err := hezStringToEthAddr(apiAuth.EthAddr, "hezEthereumAddress")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bjj, err := hezStringToBJJ(apiAuth.BJJ, "bjj")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
without0x := strings.TrimPrefix(apiAuth.Signature, "0x")
|
|
||||||
s, err := hex.DecodeString(without0x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
auth := &common.AccountCreationAuth{
|
|
||||||
EthAddr: *ethAddr,
|
|
||||||
BJJ: bjj,
|
|
||||||
Signature: s,
|
|
||||||
Timestamp: apiAuth.Timestamp,
|
|
||||||
}
|
|
||||||
if !auth.VerifySignature() {
|
|
||||||
return nil, errors.New("invalid signature")
|
|
||||||
}
|
|
||||||
return auth, nil
|
|
||||||
}
|
|
||||||
|
|||||||
72
api/exits.go
Normal file
72
api/exits.go
Normal file
@@ -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
api/exits_test.go
Normal file
276
api/exits_test.go
Normal file
@@ -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])
|
||||||
|
}
|
||||||
|
}
|
||||||
101
api/handlers.go
101
api/handlers.go
@@ -28,46 +28,6 @@ var (
|
|||||||
ErrNillBidderAddr = errors.New("biderAddr can not be nil")
|
ErrNillBidderAddr = errors.New("biderAddr can not be nil")
|
||||||
)
|
)
|
||||||
|
|
||||||
func postAccountCreationAuth(c *gin.Context) {
|
|
||||||
// Parse body
|
|
||||||
var apiAuth accountCreationAuthAPI
|
|
||||||
if err := c.ShouldBindJSON(&apiAuth); err != nil {
|
|
||||||
retBadReq(err, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// API to common + verify signature
|
|
||||||
dbAuth, err := accountCreationAuthAPIToCommon(&apiAuth)
|
|
||||||
if err != nil {
|
|
||||||
retBadReq(err, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Insert to DB
|
|
||||||
if err := l2.AddAccountCreationAuth(dbAuth); err != nil {
|
|
||||||
retSQLErr(err, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Return OK
|
|
||||||
c.Status(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAccountCreationAuth(c *gin.Context) {
|
|
||||||
// Get hezEthereumAddress
|
|
||||||
addr, err := parseParamHezEthAddr(c)
|
|
||||||
if err != nil {
|
|
||||||
retBadReq(err, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Fetch auth from l2DB
|
|
||||||
dbAuth, err := l2.GetAccountCreationAuth(*addr)
|
|
||||||
if err != nil {
|
|
||||||
retSQLErr(err, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apiAuth := accountCreationAuthToAPI(dbAuth)
|
|
||||||
// Build succesfull response
|
|
||||||
c.JSON(http.StatusOK, apiAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAccounts(c *gin.Context) {
|
func getAccounts(c *gin.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -76,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) {
|
func getSlots(c *gin.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,13 @@ func (tx *wrappedL2) L2() *common.L2Tx {
|
|||||||
return &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{}
|
usrTxs = []testTx{}
|
||||||
allTxs = []testTx{}
|
allTxs = []testTx{}
|
||||||
isUsrTx := func(tx testTx) bool {
|
isUsrTx := func(tx testTx) bool {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package apitypes
|
|||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -261,3 +262,49 @@ func (s *StrHezIdx) UnmarshalText(text []byte) error {
|
|||||||
*s = StrHezIdx(common.Idx(idxInt))
|
*s = StrHezIdx(common.Idx(idxInt))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// If the provided signature is nil the returned *EthSignature will also be nil
|
||||||
|
func NewEthSignature(signature []byte) *EthSignature {
|
||||||
|
if signature == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ethSignature := EthSignature("0x" + hex.EncodeToString(signature))
|
||||||
|
return ðSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements Scanner for database/sql
|
||||||
|
func (e *EthSignature) Scan(src interface{}) error {
|
||||||
|
if srcStr, ok := src.(string); ok {
|
||||||
|
// src is a string
|
||||||
|
*e = *(NewEthSignature([]byte(srcStr)))
|
||||||
|
return nil
|
||||||
|
} else if srcBytes, ok := src.([]byte); ok {
|
||||||
|
// src is []byte
|
||||||
|
*e = *(NewEthSignature(srcBytes))
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
// unexpected src
|
||||||
|
return fmt.Errorf("can't scan %T into apitypes.EthSignature", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements valuer for database/sql
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -332,3 +332,82 @@ func TestHezBJJ(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, toBJJNil.I)
|
assert.Nil(t, toBJJNil.I)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEthSignature(t *testing.T) {
|
||||||
|
// Clean DB
|
||||||
|
_, err := db.Exec("delete from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Example structs
|
||||||
|
type ethSignStruct struct {
|
||||||
|
I []byte `meddler:"i"`
|
||||||
|
}
|
||||||
|
type hezEthSignStruct struct {
|
||||||
|
I EthSignature `meddler:"i"`
|
||||||
|
}
|
||||||
|
type hezEthSignStructNil struct {
|
||||||
|
I *EthSignature `meddler:"i"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not nil case
|
||||||
|
// Insert into DB using []byte Scan/Value
|
||||||
|
s := "someRandomFooForYou"
|
||||||
|
fromEth := ethSignStruct{
|
||||||
|
I: []byte(s),
|
||||||
|
}
|
||||||
|
err = meddler.Insert(db, "test", &fromEth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Read from DB using EthSignature Scan/Value
|
||||||
|
toHezEth := hezEthSignStruct{}
|
||||||
|
err = meddler.QueryRow(db, &toHezEth, "select * from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, NewEthSignature(fromEth.I), &toHezEth.I)
|
||||||
|
// Clean DB
|
||||||
|
_, err = db.Exec("delete from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Insert into DB using EthSignature Scan/Value
|
||||||
|
fromHezEth := hezEthSignStruct{
|
||||||
|
I: *NewEthSignature([]byte(s)),
|
||||||
|
}
|
||||||
|
err = meddler.Insert(db, "test", &fromHezEth)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Read from DB using []byte Scan/Value
|
||||||
|
toEth := ethSignStruct{}
|
||||||
|
err = meddler.QueryRow(db, &toEth, "select * from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, &fromHezEth.I, NewEthSignature(toEth.I))
|
||||||
|
|
||||||
|
// Nil case
|
||||||
|
// Clean DB
|
||||||
|
_, err = db.Exec("delete from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Insert into DB using []byte Scan/Value
|
||||||
|
fromEthNil := ethSignStruct{
|
||||||
|
I: nil,
|
||||||
|
}
|
||||||
|
err = meddler.Insert(db, "test", &fromEthNil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Read from DB using EthSignature Scan/Value
|
||||||
|
foo := EthSignature("foo")
|
||||||
|
toHezEthNil := hezEthSignStructNil{
|
||||||
|
I: &foo, // check that this will be set to nil, not because of not being initialized
|
||||||
|
}
|
||||||
|
err = meddler.QueryRow(db, &toHezEthNil, "select * from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, toHezEthNil.I)
|
||||||
|
// Clean DB
|
||||||
|
_, err = db.Exec("delete from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Insert into DB using EthSignature Scan/Value
|
||||||
|
fromHezEthNil := hezEthSignStructNil{
|
||||||
|
I: nil,
|
||||||
|
}
|
||||||
|
err = meddler.Insert(db, "test", &fromHezEthNil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Read from DB using []byte Scan/Value
|
||||||
|
toEthNil := ethSignStruct{
|
||||||
|
I: []byte(s), // check that this will be set to nil, not because of not being initialized
|
||||||
|
}
|
||||||
|
err = meddler.QueryRow(db, &toEthNil, "select * from test")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, toEthNil.I)
|
||||||
|
}
|
||||||
|
|||||||
@@ -891,12 +891,17 @@ func (hdb *HistoryDB) GetAllExits() ([]common.ExitInfo, error) {
|
|||||||
return db.SlicePtrsToSlice(exits).([]common.ExitInfo), err
|
return db.SlicePtrsToSlice(exits).([]common.ExitInfo), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExit returns a exit from the DB
|
// GetExitAPI returns a exit from the DB
|
||||||
func (hdb *HistoryDB) GetExit(batchNum *uint, idx *common.Idx) (*HistoryExit, error) {
|
func (hdb *HistoryDB) GetExitAPI(batchNum *uint, idx *common.Idx) (*ExitAPI, error) {
|
||||||
exit := &HistoryExit{}
|
exit := &ExitAPI{}
|
||||||
err := meddler.QueryRow(
|
err := meddler.QueryRow(
|
||||||
hdb.db, exit, `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
|
hdb.db, exit, `SELECT exit_tree.item_id, exit_tree.batch_num,
|
||||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update
|
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
|
FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx
|
||||||
INNER JOIN token ON account.token_id = token.token_id
|
INNER JOIN token ON account.token_id = token.token_id
|
||||||
WHERE exit_tree.batch_num = $1 AND exit_tree.account_idx = $2;`, batchNum, idx,
|
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
|
return exit, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetExits returns a list of exits from the DB and pagination info
|
// GetExitsAPI returns a list of exits from the DB and pagination info
|
||||||
func (hdb *HistoryDB) GetExits(
|
func (hdb *HistoryDB) GetExitsAPI(
|
||||||
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
|
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
|
||||||
tokenID *common.TokenID, idx *common.Idx, batchNum *uint,
|
tokenID *common.TokenID, idx *common.Idx, batchNum *uint,
|
||||||
fromItem, limit *uint, order string,
|
fromItem, limit *uint, order string,
|
||||||
) ([]HistoryExit, *db.Pagination, error) {
|
) ([]ExitAPI, *db.Pagination, error) {
|
||||||
if ethAddr != nil && bjj != nil {
|
if ethAddr != nil && bjj != nil {
|
||||||
return nil, nil, errors.New("ethAddr and bjj are incompatible")
|
return nil, nil, errors.New("ethAddr and bjj are incompatible")
|
||||||
}
|
}
|
||||||
var query string
|
var query string
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
queryStr := `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
|
queryStr := `SELECT exit_tree.item_id, exit_tree.batch_num,
|
||||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
hez_idx(exit_tree.account_idx, token.symbol) AS account_idx,
|
||||||
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
|
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
|
FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx
|
||||||
INNER JOIN token ON account.token_id = token.token_id `
|
INNER JOIN token ON account.token_id = token.token_id `
|
||||||
// Apply filters
|
// Apply filters
|
||||||
@@ -989,14 +999,14 @@ func (hdb *HistoryDB) GetExits(
|
|||||||
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
||||||
query = hdb.db.Rebind(queryStr)
|
query = hdb.db.Rebind(queryStr)
|
||||||
// log.Debug(query)
|
// log.Debug(query)
|
||||||
exits := []*HistoryExit{}
|
exits := []*ExitAPI{}
|
||||||
if err := meddler.QueryAll(hdb.db, &exits, query, args...); err != nil {
|
if err := meddler.QueryAll(hdb.db, &exits, query, args...); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if len(exits) == 0 {
|
if len(exits) == 0 {
|
||||||
return nil, nil, sql.ErrNoRows
|
return nil, nil, sql.ErrNoRows
|
||||||
}
|
}
|
||||||
return db.SlicePtrsToSlice(exits).([]HistoryExit), &db.Pagination{
|
return db.SlicePtrsToSlice(exits).([]ExitAPI), &db.Pagination{
|
||||||
TotalItems: exits[0].TotalItems,
|
TotalItems: exits[0].TotalItems,
|
||||||
FirstItem: exits[0].FirstItem,
|
FirstItem: exits[0].FirstItem,
|
||||||
LastItem: exits[0].LastItem,
|
LastItem: exits[0].LastItem,
|
||||||
|
|||||||
@@ -151,14 +151,14 @@ type TokenWithUSD struct {
|
|||||||
LastItem int `json:"-" meddler:"last_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
|
// required by the API, and extracted by joining token table
|
||||||
type HistoryExit struct {
|
type ExitAPI struct {
|
||||||
ItemID int `meddler:"item_id"`
|
ItemID int `meddler:"item_id"`
|
||||||
BatchNum common.BatchNum `meddler:"batch_num"`
|
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"`
|
MerkleProof *merkletree.CircomVerifierProof `meddler:"merkle_proof,json"`
|
||||||
Balance *big.Int `meddler:"balance,bigint"`
|
Balance apitypes.BigIntStr `meddler:"balance"`
|
||||||
InstantWithdrawn *int64 `meddler:"instant_withdrawn"`
|
InstantWithdrawn *int64 `meddler:"instant_withdrawn"`
|
||||||
DelayedWithdrawRequest *int64 `meddler:"delayed_withdraw_request"`
|
DelayedWithdrawRequest *int64 `meddler:"delayed_withdraw_request"`
|
||||||
DelayedWithdrawn *int64 `meddler:"delayed_withdrawn"`
|
DelayedWithdrawn *int64 `meddler:"delayed_withdrawn"`
|
||||||
@@ -166,6 +166,7 @@ type HistoryExit struct {
|
|||||||
FirstItem int `meddler:"first_item"`
|
FirstItem int `meddler:"first_item"`
|
||||||
LastItem int `meddler:"last_item"`
|
LastItem int `meddler:"last_item"`
|
||||||
TokenID common.TokenID `meddler:"token_id"`
|
TokenID common.TokenID `meddler:"token_id"`
|
||||||
|
TokenItemID int `meddler:"token_item_id"`
|
||||||
TokenEthBlockNum int64 `meddler:"token_block"`
|
TokenEthBlockNum int64 `meddler:"token_block"`
|
||||||
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
||||||
TokenName string `meddler:"name"`
|
TokenName string `meddler:"name"`
|
||||||
@@ -175,6 +176,45 @@ type HistoryExit struct {
|
|||||||
TokenUSDUpdate *time.Time `meddler:"usd_update"`
|
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
|
// CoordinatorAPI is a representation of a coordinator with additional information
|
||||||
// required by the API
|
// required by the API
|
||||||
type CoordinatorAPI struct {
|
type CoordinatorAPI struct {
|
||||||
@@ -198,16 +238,15 @@ type BatchAPI struct {
|
|||||||
Timestamp time.Time `json:"timestamp" meddler:"timestamp,utctime"`
|
Timestamp time.Time `json:"timestamp" meddler:"timestamp,utctime"`
|
||||||
ForgerAddr ethCommon.Address `json:"forgerAddr" meddler:"forger_addr"`
|
ForgerAddr ethCommon.Address `json:"forgerAddr" meddler:"forger_addr"`
|
||||||
CollectedFees apitypes.CollectedFees `json:"collectedFees" meddler:"fees_collected,json"`
|
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"`
|
||||||
TotalFeesUSD *float64 `json:"historicTotalCollectedFeesUSD" meddler:"total_fees_usd"`
|
StateRoot apitypes.BigIntStr `json:"stateRoot" meddler:"state_root"`
|
||||||
StateRoot apitypes.BigIntStr `json:"stateRoot" meddler:"state_root"`
|
NumAccounts int `json:"numAccounts" meddler:"num_accounts"`
|
||||||
NumAccounts int `json:"numAccounts" meddler:"num_accounts"`
|
ExitRoot apitypes.BigIntStr `json:"exitRoot" meddler:"exit_root"`
|
||||||
ExitRoot apitypes.BigIntStr `json:"exitRoot" meddler:"exit_root"`
|
ForgeL1TxsNum *int64 `json:"forgeL1TransactionsNum" meddler:"forge_l1_txs_num"`
|
||||||
ForgeL1TxsNum *int64 `json:"forgeL1TransactionsNum" meddler:"forge_l1_txs_num"`
|
SlotNum int64 `json:"slotNum" meddler:"slot_num"`
|
||||||
SlotNum int64 `json:"slotNum" meddler:"slot_num"`
|
TotalItems int `json:"-" meddler:"total_items"`
|
||||||
TotalItems int `json:"-" meddler:"total_items"`
|
FirstItem int `json:"-" meddler:"first_item"`
|
||||||
FirstItem int `json:"-" meddler:"first_item"`
|
LastItem int `json:"-" meddler:"last_item"`
|
||||||
LastItem int `json:"-" meddler:"last_item"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network define status of the network
|
// Network define status of the network
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func (l2db *L2DB) AddAccountCreationAuth(auth *common.AccountCreationAuth) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountCreationAuth returns an account creation authorization into the DB
|
// GetAccountCreationAuth returns an account creation authorization from the DB
|
||||||
func (l2db *L2DB) GetAccountCreationAuth(addr ethCommon.Address) (*common.AccountCreationAuth, error) {
|
func (l2db *L2DB) GetAccountCreationAuth(addr ethCommon.Address) (*common.AccountCreationAuth, error) {
|
||||||
auth := new(common.AccountCreationAuth)
|
auth := new(common.AccountCreationAuth)
|
||||||
return auth, meddler.QueryRow(
|
return auth, meddler.QueryRow(
|
||||||
@@ -65,6 +65,16 @@ func (l2db *L2DB) GetAccountCreationAuth(addr ethCommon.Address) (*common.Accoun
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccountCreationAuthAPI returns an account creation authorization from the DB
|
||||||
|
func (l2db *L2DB) GetAccountCreationAuthAPI(addr ethCommon.Address) (*AccountCreationAuthAPI, error) {
|
||||||
|
auth := new(AccountCreationAuthAPI)
|
||||||
|
return auth, meddler.QueryRow(
|
||||||
|
l2db.db, auth,
|
||||||
|
"SELECT * FROM account_creation_auth WHERE eth_addr = $1;",
|
||||||
|
addr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// AddTx inserts a tx to the pool
|
// AddTx inserts a tx to the pool
|
||||||
func (l2db *L2DB) AddTx(tx *PoolL2TxWrite) error {
|
func (l2db *L2DB) AddTx(tx *PoolL2TxWrite) error {
|
||||||
return meddler.Insert(l2db.db, "tx_pool", tx)
|
return meddler.Insert(l2db.db, "tx_pool", tx)
|
||||||
|
|||||||
@@ -114,3 +114,11 @@ 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" `
|
||||||
|
Signature apitypes.EthSignature `json:"signature" meddler:"signature" `
|
||||||
|
Timestamp time.Time `json:"timestamp" meddler:"timestamp,utctime"`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user