From 85fe885265893bbfa5e44e56dbc8059dec0dd9eb Mon Sep 17 00:00:00 2001 From: Arnau B Date: Mon, 21 Sep 2020 00:11:20 +0200 Subject: [PATCH] Add GET histroy-transactions endpoint --- api/README.md | 19 + api/api.go | 77 + api/api_test.go | 621 +++++++ api/dbtoapistructs.go | 130 ++ api/docker-compose.yml | 32 + api/handlers.go | 204 +++ api/parsers.go | 204 +++ api/parsers_test.go | 282 ++++ api/run.sh | 29 + api/swagger.yml | 2266 ++++++++++++++++++++++++++ common/fee.go | 16 + common/tx.go | 10 +- db/historydb/historydb.go | 110 +- db/historydb/historydb_test.go | 15 +- db/historydb/migrations/001_init.sql | 9 +- db/historydb/views.go | 46 + db/l2db/l2db.go | 7 +- db/l2db/l2db_test.go | 34 +- go.mod | 7 +- go.sum | 76 +- test/historydb.go | 8 +- 21 files changed, 4114 insertions(+), 88 deletions(-) create mode 100644 api/README.md create mode 100644 api/api.go create mode 100644 api/api_test.go create mode 100644 api/dbtoapistructs.go create mode 100644 api/docker-compose.yml create mode 100644 api/handlers.go create mode 100644 api/parsers.go create mode 100644 api/parsers_test.go create mode 100755 api/run.sh create mode 100644 api/swagger.yml create mode 100644 db/historydb/views.go diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..482d1e1 --- /dev/null +++ b/api/README.md @@ -0,0 +1,19 @@ +# Hermez API + +Easy to deploy and scale API for Hermez operators. +You will need to have [docker](https://docs.docker.com/engine/install/) and [docker-compose](https://docs.docker.com/compose/install/) installed on your machine in order to use this repo. + +## Documentation + +As of now the documentation is not hosted anywhere, but you can easily do it yourself by running `./run.sh doc` and then [opening the documentation in your browser](http://localhost:8001) + +## Mock Up + +To use a mock up of the endpoints in the API run `./run.sh doc` (UI + mock up server) or `./run.sh mock` (only mock up server). You can play with the mocked up endpoints using the [web UI](http://localhost:8001), importing `swagger.yml` into Postman or using your preferred language and using `http://loclahost:4010` as base URL. + +## Editor + +It is recomended to edit `swagger.yml` using a dedicated editor as they provide spec validation and real time visualization. Of course you can use your prefered editor. To use the editor run `./run.sh editor` and then [opening the editor in your browser](http://localhost:8002). +**Keep in mind that you will need to manually save the file otherwise you will lose the changes** you made once you close your browser seshion or stop the server. + +**Note:** Your browser may cache the swagger definition, so in order to see updated changes it may be needed to refresh the page without cache (Ctrl + Shift + R). diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..6e4c23f --- /dev/null +++ b/api/api.go @@ -0,0 +1,77 @@ +package api + +import ( + "errors" + "fmt" + + "github.com/gin-gonic/gin" + "github.com/hermeznetwork/hermez-node/db/historydb" + "github.com/hermeznetwork/hermez-node/db/l2db" + "github.com/hermeznetwork/hermez-node/db/statedb" +) + +var h *historydb.HistoryDB +var s *statedb.StateDB // Not 100% sure if this is needed +var l2 *l2db.L2DB + +// SetAPIEndpoints sets the endpoints and the appropriate handlers, but doesn't start the server +func SetAPIEndpoints( + coordinatorEndpoints, explorerEndpoints bool, + server *gin.Engine, + hdb *historydb.HistoryDB, + sdb *statedb.StateDB, + l2db *l2db.L2DB, +) error { + // Check input + // TODO: is stateDB only needed for explorer endpoints or for both? + if coordinatorEndpoints && l2db == nil { + return errors.New("cannot serve Coordinator endpoints without L2DB") + } + if explorerEndpoints && hdb == nil { + return errors.New("cannot serve Explorer endpoints without HistoryDB") + } + + h = hdb + s = sdb + l2 = l2db + + // tmp + fmt.Println(h, s, l2) + // Add coordinator endpoints + if coordinatorEndpoints { + // Account + server.POST("/account-creation-authorization", postAccountCreationAuth) + server.GET("/account-creation-authorization/:hermezEthereumAddress", getAccountCreationAuth) + // Transaction + server.POST("/transactions-pool", postPoolTx) + server.POST("/transactions-pool/:id", getPoolTx) + } + + // Add explorer endpoints + if explorerEndpoints { + // Account + server.GET("/accounts", getAccounts) + server.GET("/accounts/:hermezEthereumAddress/:accountIndex", getAccount) + server.GET("/exits", getExits) + server.GET("/exits/:batchNum/:accountIndex", getExit) + // Transaction + server.GET("/transactions-history", getHistoryTxs) + server.GET("/transactions-history/:id", getHistoryTx) + // Status + server.GET("/batches", getBatches) + server.GET("/batches/:batchNum", getBatch) + server.GET("/full-batches/:batchNum", getFullBatch) + server.GET("/slots", getSlots) + server.GET("/bids", getBids) + server.GET("/next-forgers", getNextForgers) + server.GET("/state", getState) + server.GET("/config", getConfig) + server.GET("/tokens", getTokens) + server.GET("/tokens/:id", getToken) + server.GET("/recommendedFee", getRecommendedFee) + server.GET("/coordinators", getCoordinators) + server.GET("/coordinators/:forgerAddr", getCoordinator) + } + + return nil +} diff --git a/api/api_test.go b/api/api_test.go new file mode 100644 index 0000000..1c71ac4 --- /dev/null +++ b/api/api_test.go @@ -0,0 +1,621 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "math/big" + "net/http" + "os" + "sort" + "strconv" + "testing" + "time" + + ethCommon "github.com/ethereum/go-ethereum/common" + swagger "github.com/getkin/kin-openapi/openapi3filter" + "github.com/gin-gonic/gin" + "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/db/historydb" + "github.com/hermeznetwork/hermez-node/db/l2db" + "github.com/hermeznetwork/hermez-node/db/statedb" + "github.com/hermeznetwork/hermez-node/log" + "github.com/hermeznetwork/hermez-node/test" + "github.com/iden3/go-iden3-crypto/babyjub" + "github.com/jinzhu/copier" + "github.com/stretchr/testify/assert" +) + +const apiPort = ":4010" +const apiURL = "http://localhost" + apiPort + "/" + +type testCommon struct { + blocks []common.Block + tokens []common.Token + batches []common.Batch + usrAddr string + usrBjj string + accs []common.Account + usrTxs historyTxAPIs + othrTxs historyTxAPIs + allTxs historyTxAPIs + router *swagger.Router +} + +type historyTxAPIs []historyTxAPI + +func (h historyTxAPIs) Len() int { return len(h) } +func (h historyTxAPIs) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h historyTxAPIs) Less(i, j int) bool { + // i not forged yet + if h[i].BatchNum == nil { + if h[j].BatchNum != nil { // j is already forged + return false + } + // Both aren't forged, is i in a smaller position? + return h[i].Position < h[j].Position + } + // i is forged + if h[j].BatchNum == nil { + return true // j is not forged + } + // Both are forged + if *h[i].BatchNum == *h[j].BatchNum { + // At the same batch, is i in a smaller position? + return h[i].Position < h[j].Position + } + // At different batches, is i in a smaller batch? + return *h[i].BatchNum < *h[j].BatchNum +} + +var tc testCommon + +func TestMain(m *testing.M) { + // Init swagger + router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml") + // Init DBs + pass := os.Getenv("POSTGRES_PASS") + hdb, err := historydb.NewHistoryDB(5432, "localhost", "hermez", pass, "history") + if err != nil { + panic(err) + } + dir, err := ioutil.TempDir("", "tmpdb") + if err != nil { + panic(err) + } + sdb, err := statedb.NewStateDB(dir, false, 0) + if err != nil { + panic(err) + } + l2db, err := l2db.NewL2DB(5432, "localhost", "hermez", pass, "l2", 10, 512, 24*time.Hour) + if err != nil { + panic(err) + } + // Init API + api := gin.Default() + if err := SetAPIEndpoints( + true, + true, + api, + hdb, + sdb, + l2db, + ); err != nil { + panic(err) + } + // Start server + server := &http.Server{Addr: apiPort, Handler: api} + go func() { + if err := server.ListenAndServe(); err != nil && + err != http.ErrServerClosed { + panic(err) + } + }() + // Populate DBs + // Clean DB + err = h.Reorg(0) + if err != nil { + panic(err) + } + // Gen blocks and add them to DB + const nBlocks = 5 + blocks := test.GenBlocks(1, nBlocks+1) + err = h.AddBlocks(blocks) + if err != nil { + panic(err) + } + // Gen tokens and add them to DB + const nTokens = 10 + tokens := test.GenTokens(nTokens, blocks) + err = h.AddTokens(tokens) + if err != nil { + panic(err) + } + // Gen batches and add them to DB + const nBatches = 10 + batches := test.GenBatches(nBatches, blocks) + err = h.AddBatches(batches) + if err != nil { + panic(err) + } + // Gen accounts and add them to DB + const totalAccounts = 40 + const userAccounts = 4 + usrAddr := ethCommon.BigToAddress(big.NewInt(4896847)) + privK := babyjub.NewRandPrivKey() + usrBjj := privK.Public() + accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches) + err = h.AddAccounts(accs) + if err != nil { + panic(err) + } + // Gen L1Txs and add them to DB + const totalL1Txs = 40 + const userL1Txs = 4 + usrL1Txs, othrL1Txs := test.GenL1Txs(0, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches) + var l1Txs []common.L1Tx + l1Txs = append(l1Txs, usrL1Txs...) + l1Txs = append(l1Txs, othrL1Txs...) + err = h.AddL1Txs(l1Txs) + if err != nil { + panic(err) + } + // Gen L2Txs and add them to DB + const totalL2Txs = 20 + const userL2Txs = 4 + usrL2Txs, othrL2Txs := test.GenL2Txs(totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches) + var l2Txs []common.L2Tx + l2Txs = append(l2Txs, usrL2Txs...) + l2Txs = append(l2Txs, othrL2Txs...) + err = h.AddL2Txs(l2Txs) + if err != nil { + panic(err) + } + + // Set test commons + txsToAPITxs := func(l1Txs []common.L1Tx, l2Txs []common.L2Tx, blocks []common.Block, tokens []common.Token) historyTxAPIs { + // Transform L1Txs and L2Txs to generic Txs + genericTxs := []*common.Tx{} + for _, l1tx := range l1Txs { + genericTxs = append(genericTxs, l1tx.Tx()) + } + for _, l2tx := range l2Txs { + genericTxs = append(genericTxs, l2tx.Tx()) + } + // Transform generic Txs to HistoryTx + historyTxs := []*historydb.HistoryTx{} + for _, genericTx := range genericTxs { + // find timestamp + var timestamp time.Time + for i := 0; i < len(blocks); i++ { + if blocks[i].EthBlockNum == genericTx.EthBlockNum { + timestamp = blocks[i].Timestamp + break + } + } + // find token + token := common.Token{} + for i := 0; i < len(tokens); i++ { + if tokens[i].TokenID == genericTx.TokenID { + token = tokens[i] + break + } + } + historyTxs = append(historyTxs, &historydb.HistoryTx{ + IsL1: genericTx.IsL1, + TxID: genericTx.TxID, + Type: genericTx.Type, + Position: genericTx.Position, + FromIdx: genericTx.FromIdx, + ToIdx: genericTx.ToIdx, + Amount: genericTx.Amount, + AmountFloat: genericTx.AmountFloat, + TokenID: genericTx.TokenID, + USD: token.USD * genericTx.AmountFloat, + BatchNum: genericTx.BatchNum, + EthBlockNum: genericTx.EthBlockNum, + ToForgeL1TxsNum: genericTx.ToForgeL1TxsNum, + UserOrigin: genericTx.UserOrigin, + FromEthAddr: genericTx.FromEthAddr, + FromBJJ: genericTx.FromBJJ, + LoadAmount: genericTx.LoadAmount, + LoadAmountFloat: genericTx.LoadAmountFloat, + LoadAmountUSD: token.USD * genericTx.LoadAmountFloat, + Fee: genericTx.Fee, + FeeUSD: genericTx.Fee.Percentage() * token.USD * genericTx.AmountFloat, + Nonce: genericTx.Nonce, + Timestamp: timestamp, + TokenSymbol: token.Symbol, + CurrentUSD: token.USD * genericTx.AmountFloat, + USDUpdate: token.USDUpdate, + }) + } + return historyTxAPIs(historyTxsToAPI(historyTxs)) + } + usrTxs := txsToAPITxs(usrL1Txs, usrL2Txs, blocks, tokens) + sort.Sort(usrTxs) + othrTxs := txsToAPITxs(othrL1Txs, othrL2Txs, blocks, tokens) + sort.Sort(othrTxs) + allTxs := append(usrTxs, othrTxs...) + sort.Sort(allTxs) + tc = testCommon{ + blocks: blocks, + tokens: tokens, + batches: batches, + usrAddr: "hez:" + usrAddr.String(), + usrBjj: bjjToString(usrBjj), + accs: accs, + usrTxs: usrTxs, + othrTxs: othrTxs, + allTxs: allTxs, + router: router, + } + // Run tests + result := m.Run() + // Stop server + if err := server.Shutdown(context.Background()); err != nil { + panic(err) + } + if err := h.Close(); err != nil { + panic(err) + } + if err := l2.Close(); err != nil { + panic(err) + } + os.Exit(result) +} + +func TestGetHistoryTxs(t *testing.T) { + endpoint := apiURL + "transactions-history" + fetchedTxs := historyTxAPIs{} + appendIter := func(intr interface{}) { + for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ { + tmp := &historyTxAPI{} + if err := copier.Copy(tmp, &intr.(*historyTxsAPI).Txs[i]); err != nil { + panic(err) + } + fetchedTxs = append(fetchedTxs, *tmp) + } + } + // Get all (no filters) + limit := 8 + path := fmt.Sprintf("%s?limit=%d&offset=", endpoint, limit) + err := doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs) + // Get by ethAddr + fetchedTxs = historyTxAPIs{} + limit = 7 + path = fmt.Sprintf( + "%s?hermezEthereumAddress=%s&limit=%d&offset=", + endpoint, tc.usrAddr, limit, + ) + err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs) + // Get by bjj + fetchedTxs = historyTxAPIs{} + limit = 6 + path = fmt.Sprintf( + "%s?BJJ=%s&limit=%d&offset=", + endpoint, tc.usrBjj, limit, + ) + err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs) + // Get by tokenID + fetchedTxs = historyTxAPIs{} + limit = 5 + tokenID := tc.allTxs[0].TokenID + path = fmt.Sprintf( + "%s?tokenId=%d&limit=%d&offset=", + endpoint, tokenID, limit, + ) + err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + tokenIDTxs := historyTxAPIs{} + for i := 0; i < len(tc.allTxs); i++ { + if tc.allTxs[i].TokenID == tokenID { + tokenIDTxs = append(tokenIDTxs, tc.allTxs[i]) + } + } + assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs) + // idx + fetchedTxs = historyTxAPIs{} + limit = 4 + idx := tc.allTxs[0].FromIdx + path = fmt.Sprintf( + "%s?accountIndex=%s&limit=%d&offset=", + endpoint, idx, limit, + ) + err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + idxTxs := historyTxAPIs{} + for i := 0; i < len(tc.allTxs); i++ { + if tc.allTxs[i].FromIdx == idx { + idxTxs = append(idxTxs, tc.allTxs[i]) + } + } + assertHistoryTxAPIs(t, idxTxs, fetchedTxs) + // batchNum + fetchedTxs = historyTxAPIs{} + limit = 3 + batchNum := tc.allTxs[0].BatchNum + path = fmt.Sprintf( + "%s?batchNum=%d&limit=%d&offset=", + endpoint, *batchNum, limit, + ) + err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + batchNumTxs := historyTxAPIs{} + for i := 0; i < len(tc.allTxs); i++ { + if tc.allTxs[i].BatchNum != nil && + *tc.allTxs[i].BatchNum == *batchNum { + batchNumTxs = append(batchNumTxs, tc.allTxs[i]) + } + } + assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs) + // type + txTypes := []common.TxType{ + common.TxTypeExit, + common.TxTypeWithdrawn, + common.TxTypeTransfer, + common.TxTypeDeposit, + common.TxTypeCreateAccountDeposit, + common.TxTypeCreateAccountDepositTransfer, + common.TxTypeDepositTransfer, + common.TxTypeForceTransfer, + common.TxTypeForceExit, + common.TxTypeTransferToEthAddr, + common.TxTypeTransferToBJJ, + } + for _, txType := range txTypes { + fetchedTxs = historyTxAPIs{} + limit = 2 + path = fmt.Sprintf( + "%s?type=%s&limit=%d&offset=", + endpoint, txType, limit, + ) + err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + txTypeTxs := historyTxAPIs{} + for i := 0; i < len(tc.allTxs); i++ { + if tc.allTxs[i].Type == txType { + txTypeTxs = append(txTypeTxs, tc.allTxs[i]) + } + } + assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs) + } + // Multiple filters + fetchedTxs = historyTxAPIs{} + limit = 1 + path = fmt.Sprintf( + "%s?batchNum=%d&tokeId=%d&limit=%d&offset=", + endpoint, *batchNum, tokenID, limit, + ) + err = doGoodReqPaginated(path, &historyTxsAPI{}, appendIter) + assert.NoError(t, err) + mixedTxs := historyTxAPIs{} + for i := 0; i < len(tc.allTxs); i++ { + if tc.allTxs[i].BatchNum != nil { + if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].TokenID == tokenID { + mixedTxs = append(mixedTxs, tc.allTxs[i]) + } + } + } + assertHistoryTxAPIs(t, mixedTxs, fetchedTxs) + // All, in reverse order + fetchedTxs = historyTxAPIs{} + limit = 5 + path = fmt.Sprintf("%s?", endpoint) + appendIterRev := func(intr interface{}) { + tmpAll := historyTxAPIs{} + for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ { + tmpItem := &historyTxAPI{} + if err := copier.Copy(tmpItem, &intr.(*historyTxsAPI).Txs[i]); err != nil { + panic(err) + } + tmpAll = append(tmpAll, *tmpItem) + } + fetchedTxs = append(tmpAll, fetchedTxs...) + } + err = doGoodReqPaginatedReverse(path, &historyTxsAPI{}, appendIterRev, limit) + assert.NoError(t, err) + assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs) + // 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&offset=1000", endpoint) + err = doBadReq("GET", path, nil, 404) + assert.NoError(t, err) +} + +func assertHistoryTxAPIs(t *testing.T, expected, actual historyTxAPIs) { + assert.Equal(t, len(expected), len(actual)) + for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop + assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix()) + expected[i].Timestamp = actual[i].Timestamp + assert.Equal(t, expected[i].USDUpdate.Unix(), actual[i].USDUpdate.Unix()) + expected[i].USDUpdate = actual[i].USDUpdate + if expected[i].L2Info != nil { + if expected[i].L2Info.FeeUSD > actual[i].L2Info.FeeUSD { + assert.Less(t, 0.999, actual[i].L2Info.FeeUSD/expected[i].L2Info.FeeUSD) + } else if expected[i].L2Info.FeeUSD < actual[i].L2Info.FeeUSD { + assert.Less(t, 0.999, expected[i].L2Info.FeeUSD/actual[i].L2Info.FeeUSD) + } + expected[i].L2Info.FeeUSD = actual[i].L2Info.FeeUSD + } + assert.Equal(t, expected[i], actual[i]) + } +} + +func doGoodReqPaginated( + path string, + iterStruct paginationer, + appendIter func(res interface{}), +) error { + next := 0 + for { + // Call API to get this iteration items + if err := doGoodReq("GET", path+strconv.Itoa(next), nil, iterStruct); err != nil { + return err + } + appendIter(iterStruct) + // Keep iterating? + pag := iterStruct.GetPagination() + if pag.LastReturnedItem == pag.TotalItems-1 { // No + break + } else { // Yes + next = int(pag.LastReturnedItem + 1) + } + } + return nil +} + +func doGoodReqPaginatedReverse( + path string, + iterStruct paginationer, + appendIter func(res interface{}), + limit int, +) error { + next := 0 + first := true + for { + // Call API to get this iteration items + if first { + first = false + pagQuery := fmt.Sprintf("last=true&limit=%d", limit) + if err := doGoodReq("GET", path+pagQuery, nil, iterStruct); err != nil { + return err + } + } else { + pagQuery := fmt.Sprintf("offset=%d&limit=%d", next, limit) + if err := doGoodReq("GET", path+pagQuery, nil, iterStruct); err != nil { + return err + } + } + appendIter(iterStruct) + // Keep iterating? + pag := iterStruct.GetPagination() + if iterStruct.Len() == pag.TotalItems || pag.LastReturnedItem-iterStruct.Len() == -1 { // No + break + } else { // Yes + prevOffset := next + next = pag.LastReturnedItem - iterStruct.Len() - limit + 1 + if next < 0 { + next = 0 + limit = prevOffset + } + } + } + return nil +} + +func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error { + ctx := context.Background() + client := &http.Client{} + httpReq, _ := http.NewRequest(method, path, reqBody) + route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL) + if err != nil { + return err + } + // Validate request against swagger spec + requestValidationInput := &swagger.RequestValidationInput{ + Request: httpReq, + PathParams: pathParams, + Route: route, + } + if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil { + return err + } + // Do API call + resp, err := client.Do(httpReq) + if err != nil { + return err + } + if resp.Body == nil { + return errors.New("Nil body") + } + //nolint + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if resp.StatusCode != 200 { + return fmt.Errorf("%d response: %s", resp.StatusCode, string(body)) + } + // Unmarshal body into return struct + if err := json.Unmarshal(body, returnStruct); err != nil { + return err + } + // Validate response against swagger spec + responseValidationInput := &swagger.ResponseValidationInput{ + RequestValidationInput: requestValidationInput, + Status: resp.StatusCode, + Header: resp.Header, + } + responseValidationInput = responseValidationInput.SetBodyBytes(body) + return swagger.ValidateResponse(ctx, responseValidationInput) +} + +func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error { + ctx := context.Background() + client := &http.Client{} + httpReq, _ := http.NewRequest(method, path, reqBody) + route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL) + if err != nil { + return err + } + // Validate request against swagger spec + requestValidationInput := &swagger.RequestValidationInput{ + Request: httpReq, + PathParams: pathParams, + Route: route, + } + if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil { + if expectedResponseCode != 400 { + return err + } + log.Warn("The request does not match the API spec") + } + // Do API call + resp, err := client.Do(httpReq) + if err != nil { + return err + } + if resp.Body == nil { + return errors.New("Nil body") + } + //nolint + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if resp.StatusCode != expectedResponseCode { + return fmt.Errorf("Unexpected response code: %d", resp.StatusCode) + } + // Validate response against swagger spec + responseValidationInput := &swagger.ResponseValidationInput{ + RequestValidationInput: requestValidationInput, + Status: resp.StatusCode, + Header: resp.Header, + } + responseValidationInput = responseValidationInput.SetBodyBytes(body) + return swagger.ValidateResponse(ctx, responseValidationInput) +} diff --git a/api/dbtoapistructs.go b/api/dbtoapistructs.go new file mode 100644 index 0000000..3557120 --- /dev/null +++ b/api/dbtoapistructs.go @@ -0,0 +1,130 @@ +package api + +import ( + "encoding/base64" + "strconv" + "time" + + "github.com/hermeznetwork/hermez-node/common" + "github.com/hermeznetwork/hermez-node/db/historydb" + "github.com/iden3/go-iden3-crypto/babyjub" +) + +// Commons of the API + +type pagination struct { + TotalItems int `json:"totalItems"` + LastReturnedItem int `json:"lastReturnedItem"` +} + +type paginationer interface { + GetPagination() pagination + Len() int +} + +type errorMsg struct { + Message string +} + +// History Tx related + +type historyTxsAPI struct { + Txs []historyTxAPI `json:"transactions"` + Pagination pagination `json:"pagination"` +} + +func (htx *historyTxsAPI) GetPagination() pagination { return htx.Pagination } +func (htx *historyTxsAPI) Len() int { return len(htx.Txs) } + +type l1Info struct { + ToForgeL1TxsNum int64 `json:"toForgeL1TransactionsNum"` + UserOrigin bool `json:"userOrigin"` + FromEthAddr string `json:"fromEthereumAddress"` + FromBJJ string `json:"fromBJJ"` + LoadAmount string `json:"loadAmount"` + LoadAmountUSD float64 `json:"loadAmountUSD"` + EthBlockNum int64 `json:"ethereumBlockNum"` +} + +type l2Info struct { + Fee common.FeeSelector `json:"fee"` + FeeUSD float64 `json:"feeUSD"` + Nonce common.Nonce `json:"nonce"` +} + +type historyTxAPI struct { + IsL1 string `json:"L1orL2"` + TxID common.TxID `json:"id"` + Type common.TxType `json:"type"` + Position int `json:"position"` + FromIdx string `json:"fromAccountIndex"` + ToIdx string `json:"toAccountIndex"` + Amount string `json:"amount"` + BatchNum *common.BatchNum `json:"batchNum"` + TokenID common.TokenID `json:"tokenId"` + TokenSymbol string `json:"tokenSymbol"` + USD float64 `json:"historicUSD"` + Timestamp time.Time `json:"timestamp"` + CurrentUSD float64 `json:"currentUSD"` + USDUpdate time.Time `json:"fiatUpdate"` + L1Info *l1Info `json:"L1Info"` + L2Info *l2Info `json:"L2Info"` +} + +func historyTxsToAPI(dbTxs []*historydb.HistoryTx) []historyTxAPI { + apiTxs := []historyTxAPI{} + for i := 0; i < len(dbTxs); i++ { + apiTx := historyTxAPI{ + TxID: dbTxs[i].TxID, + Type: dbTxs[i].Type, + Position: dbTxs[i].Position, + FromIdx: "hez:" + dbTxs[i].TokenSymbol + ":" + strconv.Itoa(int(dbTxs[i].FromIdx)), + ToIdx: "hez:" + dbTxs[i].TokenSymbol + ":" + strconv.Itoa(int(dbTxs[i].ToIdx)), + Amount: dbTxs[i].Amount.String(), + TokenID: dbTxs[i].TokenID, + USD: dbTxs[i].USD, + BatchNum: nil, + Timestamp: dbTxs[i].Timestamp, + TokenSymbol: dbTxs[i].TokenSymbol, + CurrentUSD: dbTxs[i].CurrentUSD, + USDUpdate: dbTxs[i].USDUpdate, + L1Info: nil, + L2Info: nil, + } + bn := dbTxs[i].BatchNum + if dbTxs[i].BatchNum != 0 { + apiTx.BatchNum = &bn + } + if dbTxs[i].IsL1 { + apiTx.IsL1 = "L1" + apiTx.L1Info = &l1Info{ + ToForgeL1TxsNum: dbTxs[i].ToForgeL1TxsNum, + UserOrigin: dbTxs[i].UserOrigin, + FromEthAddr: "hez:" + dbTxs[i].FromEthAddr.String(), + FromBJJ: bjjToString(dbTxs[i].FromBJJ), + LoadAmount: dbTxs[i].LoadAmount.String(), + LoadAmountUSD: dbTxs[i].LoadAmountUSD, + EthBlockNum: dbTxs[i].EthBlockNum, + } + } else { + apiTx.IsL1 = "L2" + apiTx.L2Info = &l2Info{ + Fee: dbTxs[i].Fee, + FeeUSD: dbTxs[i].FeeUSD, + Nonce: dbTxs[i].Nonce, + } + } + apiTxs = append(apiTxs, apiTx) + } + return apiTxs +} + +func bjjToString(bjj *babyjub.PublicKey) string { + pkComp := [32]byte(bjj.Compress()) + sum := pkComp[0] + for i := 1; i < len(pkComp); i++ { + sum += pkComp[i] + } + bjjSum := append(pkComp[:], sum) + return "hez:" + base64.RawURLEncoding.EncodeToString(bjjSum) +} diff --git a/api/docker-compose.yml b/api/docker-compose.yml new file mode 100644 index 0000000..0183830 --- /dev/null +++ b/api/docker-compose.yml @@ -0,0 +1,32 @@ +version: "3" +services: + hermez-api-doc: + container_name: hermez-api-doc + image: swaggerapi/swagger-ui + restart: unless-stopped + ports: + - 8001:8080 + volumes: + - .:/spec + environment: + - SWAGGER_JSON=/spec/swagger.yml + hermez-api-mock: + container_name: hermez-api-mock + image: stoplight/prism + restart: unless-stopped + ports: + - 4010:4010 + volumes: + - .:/spec + command: mock -h 0.0.0.0 "/spec/swagger.yml" + #docker run -d -p 80:8080 -e URL=/foo/swagger.json -v /bar:/usr/share/nginx/html/foo swaggerapi/swagger-editor + hermez-api-editor: + container_name: hermez-api-editor + image: swaggerapi/swagger-editor + restart: unless-stopped + ports: + - 8002:8080 + volumes: + - .:/spec + environment: + - SWAGGER_FILE=/spec/swagger.yml diff --git a/api/handlers.go b/api/handlers.go new file mode 100644 index 0000000..6b500dc --- /dev/null +++ b/api/handlers.go @@ -0,0 +1,204 @@ +package api + +import ( + "database/sql" + "errors" + "net/http" + + "github.com/gin-gonic/gin" +) + +// maxLimit is the max permited items to be returned in paginated responses +const maxLimit uint = 2049 + +// dfltLast indicates how paginated endpoints use the query param last if not provided +const dfltLast = false + +// dfltLimit indicates the limit of returned items in paginated responses if the query param limit is not provided +const dfltLimit uint = 20 + +// 2^32 -1 +const maxUint32 = 4294967295 + +func postAccountCreationAuth(c *gin.Context) { + +} + +func getAccountCreationAuth(c *gin.Context) { + +} + +func postPoolTx(c *gin.Context) { + +} + +func getPoolTx(c *gin.Context) { + +} + +func getAccounts(c *gin.Context) { + +} + +func getAccount(c *gin.Context) { + +} + +func getExits(c *gin.Context) { + +} + +func getExit(c *gin.Context) { + +} + +func getHistoryTxs(c *gin.Context) { + // Get query parameters + // TokenID + tokenID, err := parseQueryUint("tokenId", nil, 0, maxUint32, c) + if err != nil { + retBadReq(err, c) + return + } + // Hez Eth addr + addr, err := parseQueryHezEthAddr(c) + if err != nil { + retBadReq(err, c) + return + } + // BJJ + bjj, err := parseQueryBJJ(c) + if err != nil { + retBadReq(err, c) + return + } + if addr != nil && bjj != nil { + retBadReq(errors.New("bjj and hermezEthereumAddress params are incompatible"), c) + return + } + // Idx + idx, err := parseIdx(c) + if err != nil { + retBadReq(err, c) + return + } + if idx != nil && (addr != nil || bjj != nil || tokenID != nil) { + retBadReq(errors.New("accountIndex is incompatible with BJJ, hermezEthereumAddress and tokenId"), c) + return + } + // BatchNum + batchNum, err := parseQueryUint("batchNum", nil, 0, maxUint32, c) + if err != nil { + retBadReq(err, c) + return + } + // TxType + txType, err := parseQueryTxType(c) + if err != nil { + retBadReq(err, c) + return + } + // Pagination + offset, last, limit, err := parsePagination(c) + if err != nil { + retBadReq(err, c) + return + } + + // Fetch txs from historyDB + txs, totalItems, err := h.GetHistoryTxs( + addr, bjj, tokenID, idx, batchNum, txType, offset, limit, *last, + ) + if err != nil { + retSQLErr(err, c) + return + } + + // Build succesfull response + apiTxs := historyTxsToAPI(txs) + lastRet := int(*offset) + len(apiTxs) - 1 + if *last { + lastRet = totalItems - 1 + } + c.JSON(http.StatusOK, &historyTxsAPI{ + Txs: apiTxs, + Pagination: pagination{ + TotalItems: totalItems, + LastReturnedItem: lastRet, + }, + }) +} + +func getHistoryTx(c *gin.Context) { + +} + +func getBatches(c *gin.Context) { + +} + +func getBatch(c *gin.Context) { + +} + +func getFullBatch(c *gin.Context) { + +} + +func getSlots(c *gin.Context) { + +} + +func getBids(c *gin.Context) { + +} + +func getNextForgers(c *gin.Context) { + +} + +func getState(c *gin.Context) { + +} + +func getConfig(c *gin.Context) { + +} + +func getTokens(c *gin.Context) { + +} + +func getToken(c *gin.Context) { + +} + +func getRecommendedFee(c *gin.Context) { + +} + +func getCoordinators(c *gin.Context) { + +} + +func getCoordinator(c *gin.Context) { + +} + +func retSQLErr(err error, c *gin.Context) { + if err == sql.ErrNoRows { + c.JSON(http.StatusNotFound, errorMsg{ + Message: err.Error(), + }) + } else { + c.JSON(http.StatusInternalServerError, errorMsg{ + Message: err.Error(), + }) + } +} + +func retBadReq(err error, c *gin.Context) { + c.JSON(http.StatusBadRequest, errorMsg{ + Message: err.Error(), + }) +} diff --git a/api/parsers.go b/api/parsers.go new file mode 100644 index 0000000..395dde8 --- /dev/null +++ b/api/parsers.go @@ -0,0 +1,204 @@ +package api + +import ( + "encoding/base64" + "errors" + "fmt" + "strconv" + "strings" + + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/hermeznetwork/hermez-node/common" + "github.com/iden3/go-iden3-crypto/babyjub" +) + +type querier interface { + Query(string) string +} + +func parsePagination(c querier) (*uint, *bool, *uint, error) { + // Offset + offset := new(uint) + *offset = 0 + offset, err := parseQueryUint("offset", offset, 0, maxUint32, c) + if err != nil { + return nil, nil, nil, err + } + // Last + last := new(bool) + *last = dfltLast + last, err = parseQueryBool("last", last, c) + if err != nil { + return nil, nil, nil, err + } + if *last && (offset != nil && *offset > 0) { + return nil, nil, nil, errors.New( + "last and offset are incompatible, provide only one of them", + ) + } + // Limit + limit := new(uint) + *limit = dfltLimit + limit, err = parseQueryUint("limit", limit, 1, maxLimit, c) + if err != nil { + return nil, nil, nil, err + } + return offset, last, limit, nil +} + +func parseQueryUint(name string, dflt *uint, min, max uint, c querier) (*uint, error) { //nolint:SA4009 res may be not overwriten + str := c.Query(name) + if str != "" { + resInt, err := strconv.Atoi(str) + if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) { + return nil, fmt.Errorf( + "Inavlid %s. Must be an integer within the range [%d, %d]", + name, min, max) + } + res := uint(resInt) + return &res, nil + } + return dflt, nil +} + +func parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolint:SA4009 res may be not overwriten + str := c.Query(name) + if str == "" { + return dflt, nil + } + if str == "true" { + res := new(bool) + *res = true + return res, nil + } + if str == "false" { + res := new(bool) + *res = false + return res, nil + } + return nil, fmt.Errorf("Inavlid %s. Must be eithe true or false", name) +} + +func parseQueryHezEthAddr(c querier) (*ethCommon.Address, error) { + const name = "hermezEthereumAddress" + addrStr := c.Query(name) + if addrStr == "" { + return nil, nil + } + splitted := strings.Split(addrStr, "hez:") + if len(splitted) != 2 || len(splitted[1]) != 42 { + return nil, fmt.Errorf( + "Invalid %s, must follow this regex: ^hez:0x[a-fA-F0-9]{40}$", name) + } + var addr ethCommon.Address + err := addr.UnmarshalText([]byte(splitted[1])) + return &addr, err +} + +func parseQueryBJJ(c querier) (*babyjub.PublicKey, error) { + const name = "BJJ" + const decodedLen = 33 + bjjStr := c.Query(name) + if bjjStr == "" { + return nil, nil + } + splitted := strings.Split(bjjStr, "hez:") + if len(splitted) != 2 || len(splitted[1]) != 44 { + return nil, fmt.Errorf( + "Invalid %s, must follow this regex: ^hez:[A-Za-z0-9+/=]{44}$", + name) + } + decoded, err := base64.RawURLEncoding.DecodeString(splitted[1]) + if err != nil { + return nil, fmt.Errorf( + "Invalid %s, error decoding base64 string: %s", + name, err.Error()) + } + if len(decoded) != decodedLen { + return nil, fmt.Errorf( + "invalid %s, error decoding base64 string: unexpected byte array length", + name) + } + bjjBytes := [decodedLen - 1]byte{} + copy(bjjBytes[:decodedLen-1], decoded[:decodedLen-1]) + sum := bjjBytes[0] + for i := 1; i < len(bjjBytes); i++ { + sum += bjjBytes[i] + } + if decoded[decodedLen-1] != sum { + return nil, fmt.Errorf("invalid %s, checksum failed", + name) + } + bjjComp := babyjub.PublicKeyComp(bjjBytes) + bjj, err := bjjComp.Decompress() + if err != nil { + return nil, fmt.Errorf( + "invalid %s, error decompressing public key: %s", + name, err.Error()) + } + return bjj, nil +} + +func parseQueryTxType(c querier) (*common.TxType, error) { + const name = "type" + typeStr := c.Query(name) + if typeStr == "" { + return nil, nil + } + switch common.TxType(typeStr) { + case common.TxTypeExit: + ret := common.TxTypeExit + return &ret, nil + case common.TxTypeWithdrawn: + ret := common.TxTypeWithdrawn + return &ret, nil + case common.TxTypeTransfer: + ret := common.TxTypeTransfer + return &ret, nil + case common.TxTypeDeposit: + ret := common.TxTypeDeposit + return &ret, nil + case common.TxTypeCreateAccountDeposit: + ret := common.TxTypeCreateAccountDeposit + return &ret, nil + case common.TxTypeCreateAccountDepositTransfer: + ret := common.TxTypeCreateAccountDepositTransfer + return &ret, nil + case common.TxTypeDepositTransfer: + ret := common.TxTypeDepositTransfer + return &ret, nil + case common.TxTypeForceTransfer: + ret := common.TxTypeForceTransfer + return &ret, nil + case common.TxTypeForceExit: + ret := common.TxTypeForceExit + return &ret, nil + case common.TxTypeTransferToEthAddr: + ret := common.TxTypeTransferToEthAddr + return &ret, nil + case common.TxTypeTransferToBJJ: + ret := common.TxTypeTransferToBJJ + return &ret, nil + } + return nil, fmt.Errorf( + "invalid %s, %s is not a valid option. Check the valid options in the docmentation", + name, typeStr, + ) +} + +func parseIdx(c querier) (*uint, error) { + const name = "accountIndex" + addrStr := c.Query(name) + if addrStr == "" { + return nil, nil + } + splitted := strings.Split(addrStr, ":") + const expectedLen = 3 + if len(splitted) != expectedLen { + return nil, fmt.Errorf( + "invalid %s, must follow this: hez::index", name) + } + idxInt, err := strconv.Atoi(splitted[2]) + idx := uint(idxInt) + return &idx, err +} diff --git a/api/parsers_test.go b/api/parsers_test.go new file mode 100644 index 0000000..3e591d0 --- /dev/null +++ b/api/parsers_test.go @@ -0,0 +1,282 @@ +package api + +import ( + "encoding/base64" + "math/big" + "strconv" + "testing" + + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/hermeznetwork/hermez-node/common" + "github.com/iden3/go-iden3-crypto/babyjub" + "github.com/stretchr/testify/assert" +) + +type queryParser struct { + m map[string]string +} + +func (qp *queryParser) Query(query string) string { + if val, ok := qp.m[query]; ok { + return val + } + return "" +} + +func TestParseQueryUint(t *testing.T) { + name := "foo" + c := &queryParser{} + c.m = make(map[string]string) + var min uint = 1 + var max uint = 10 + var dflt *uint + // Not uint + c.m[name] = "-1" + _, err := parseQueryUint(name, dflt, min, max, c) + assert.Error(t, err) + c.m[name] = "a" + _, err = parseQueryUint(name, dflt, min, max, c) + assert.Error(t, err) + c.m[name] = "0.1" + _, err = parseQueryUint(name, dflt, min, max, c) + assert.Error(t, err) + c.m[name] = "1.0" + _, err = parseQueryUint(name, dflt, min, max, c) + assert.Error(t, err) + // Out of range + c.m[name] = strconv.Itoa(int(min) - 1) + _, err = parseQueryUint(name, dflt, min, max, c) + assert.Error(t, err) + c.m[name] = strconv.Itoa(int(max) + 1) + _, err = parseQueryUint(name, dflt, min, max, c) + assert.Error(t, err) + // Default nil + c.m[name] = "" + res, err := parseQueryUint(name, dflt, min, max, c) + assert.NoError(t, err) + assert.Nil(t, res) + // Default not nil + dflt = new(uint) + *dflt = uint(min) + res, err = parseQueryUint(name, dflt, min, max, c) + assert.NoError(t, err) + assert.Equal(t, uint(min), *res) + // Correct + c.m[name] = strconv.Itoa(int(max)) + res, err = parseQueryUint(name, res, min, max, c) + assert.NoError(t, err) + assert.Equal(t, uint(max), *res) +} + +func TestParseQueryBool(t *testing.T) { + name := "foo" + c := &queryParser{} + c.m = make(map[string]string) + var dflt *bool + // Not bool + c.m[name] = "x" + _, err := parseQueryBool(name, dflt, c) + assert.Error(t, err) + c.m[name] = "False" + _, err = parseQueryBool(name, dflt, c) + assert.Error(t, err) + c.m[name] = "0" + _, err = parseQueryBool(name, dflt, c) + assert.Error(t, err) + c.m[name] = "1" + _, err = parseQueryBool(name, dflt, c) + assert.Error(t, err) + // Default nil + c.m[name] = "" + res, err := parseQueryBool(name, dflt, c) + assert.NoError(t, err) + assert.Nil(t, res) + // Default not nil + dflt = new(bool) + *dflt = true + res, err = parseQueryBool(name, dflt, c) + assert.NoError(t, err) + assert.True(t, *res) + // Correct + c.m[name] = "false" + res, err = parseQueryBool(name, dflt, c) + assert.NoError(t, err) + assert.False(t, *res) + c.m[name] = "true" + res, err = parseQueryBool(name, dflt, c) + assert.NoError(t, err) + assert.True(t, *res) +} + +func TestParsePagination(t *testing.T) { + c := &queryParser{} + c.m = make(map[string]string) + // Offset out of range + c.m["offset"] = "-1" + _, _, _, err := parsePagination(c) + assert.Error(t, err) + c.m["offset"] = strconv.Itoa(maxUint32 + 1) + _, _, _, err = parsePagination(c) + assert.Error(t, err) + c.m["offset"] = "" + // Limit out of range + c.m["limit"] = "0" + _, _, _, err = parsePagination(c) + assert.Error(t, err) + c.m["limit"] = strconv.Itoa(int(maxLimit) + 1) + _, _, _, err = parsePagination(c) + assert.Error(t, err) + c.m["limit"] = "" + // Last and offset + c.m["offset"] = "1" + c.m["last"] = "true" + _, _, _, err = parsePagination(c) + assert.Error(t, err) + // Default + c.m["offset"] = "" + c.m["last"] = "" + c.m["limit"] = "" + offset, last, limit, err := parsePagination(c) + assert.NoError(t, err) + assert.Equal(t, 0, int(*offset)) + assert.Equal(t, dfltLast, *last) + assert.Equal(t, dfltLimit, *limit) + // Correct + c.m["offset"] = "" + c.m["last"] = "true" + c.m["limit"] = "25" + offset, last, limit, err = parsePagination(c) + assert.NoError(t, err) + assert.Equal(t, 0, int(*offset)) + assert.True(t, *last) + assert.Equal(t, 25, int(*limit)) + c.m["offset"] = "25" + c.m["last"] = "false" + c.m["limit"] = "50" + offset, last, limit, err = parsePagination(c) + assert.NoError(t, err) + assert.Equal(t, 25, int(*offset)) + assert.False(t, *last) + assert.Equal(t, 50, int(*limit)) +} + +func TestParseQueryHezEthAddr(t *testing.T) { + name := "hermezEthereumAddress" + c := &queryParser{} + c.m = make(map[string]string) + ethAddr := ethCommon.BigToAddress(big.NewInt(int64(347683))) + // Not HEZ Eth addr + c.m[name] = "hez:0xf" + _, err := parseQueryHezEthAddr(c) + assert.Error(t, err) + c.m[name] = ethAddr.String() + _, err = parseQueryHezEthAddr(c) + assert.Error(t, err) + c.m[name] = "hez:0xXX942cfcd25ad4d90a62358b0dd84f33b39826XX" + _, err = parseQueryHezEthAddr(c) + assert.Error(t, err) + // Default + c.m[name] = "" + res, err := parseQueryHezEthAddr(c) + assert.NoError(t, err) + assert.Nil(t, res) + // Correct + c.m[name] = "hez:" + ethAddr.String() + res, err = parseQueryHezEthAddr(c) + assert.NoError(t, err) + assert.Equal(t, ethAddr, *res) +} + +func TestParseQueryBJJ(t *testing.T) { + name := "BJJ" + c := &queryParser{} + c.m = make(map[string]string) + privK := babyjub.NewRandPrivKey() + pubK := privK.Public() + pkComp := [32]byte(pubK.Compress()) + // Not HEZ Eth addr + c.m[name] = "hez:abcd" + _, err := parseQueryBJJ(c) + assert.Error(t, err) + c.m[name] = pubK.String() + _, err = parseQueryBJJ(c) + assert.Error(t, err) + // Wrong checksum + bjjSum := append(pkComp[:], byte(1)) + c.m[name] = "hez:" + base64.RawStdEncoding.EncodeToString(bjjSum) + _, err = parseQueryBJJ(c) + assert.Error(t, err) + // Default + c.m[name] = "" + res, err := parseQueryBJJ(c) + assert.NoError(t, err) + assert.Nil(t, res) + // Correct + c.m[name] = bjjToString(pubK) + res, err = parseQueryBJJ(c) + assert.NoError(t, err) + assert.Equal(t, *pubK, *res) +} + +func TestParseQueryTxType(t *testing.T) { + name := "type" + c := &queryParser{} + c.m = make(map[string]string) + // Incorrect values + c.m[name] = "deposit" + _, err := parseQueryTxType(c) + assert.Error(t, err) + c.m[name] = "1" + _, err = parseQueryTxType(c) + assert.Error(t, err) + // Default + c.m[name] = "" + res, err := parseQueryTxType(c) + assert.NoError(t, err) + assert.Nil(t, res) + // Correct values + c.m[name] = string(common.TxTypeExit) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeExit, *res) + c.m[name] = string(common.TxTypeWithdrawn) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeWithdrawn, *res) + c.m[name] = string(common.TxTypeTransfer) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeTransfer, *res) + c.m[name] = string(common.TxTypeDeposit) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeDeposit, *res) + c.m[name] = string(common.TxTypeCreateAccountDeposit) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeCreateAccountDeposit, *res) + c.m[name] = string(common.TxTypeCreateAccountDepositTransfer) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeCreateAccountDepositTransfer, *res) + c.m[name] = string(common.TxTypeDepositTransfer) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeDepositTransfer, *res) + c.m[name] = string(common.TxTypeForceTransfer) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeForceTransfer, *res) + c.m[name] = string(common.TxTypeForceExit) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeForceExit, *res) + c.m[name] = string(common.TxTypeTransferToEthAddr) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeTransferToEthAddr, *res) + c.m[name] = string(common.TxTypeTransferToBJJ) + res, err = parseQueryTxType(c) + assert.NoError(t, err) + assert.Equal(t, common.TxTypeTransferToBJJ, *res) +} diff --git a/api/run.sh b/api/run.sh new file mode 100755 index 0000000..048c82a --- /dev/null +++ b/api/run.sh @@ -0,0 +1,29 @@ +#!/bin/sh -e + +USAGE="Available options: + doc Start documentation UI at http://loclahost:8001 and the mock up server at http://loclahost:4010 + mock Start the mock up server at http://loclahost:4010 + editor Start the documentation editor at http://loclahost:8002 + stop Stop all runing services started using this script + help display this message" + +case "$1" in + doc) + sudo docker-compose up -d hermez-api-doc hermez-api-mock && echo "\n\nStarted documentation UI at http://localhost:8001 and mockup server at http://localhost:4010" + ;; + mock) + sudo docker-compose up -d hermez-api-mock && echo "\n\nStarted mockup server at http://localhost:4010" + ;; + editor) + sudo docker-compose up -d hermez-api-editor hermez-api-mock && echo "\n\nStarted spec editor at http://localhost:8002 and mockup server at http://localhost:4010" + ;; + stop) + sudo docker-compose rm -sf && echo "\n\nStopped all the services initialized by this script" + ;; + help) + echo "$USAGE" + ;; + *) + echo "Invalid option.\n\n$USAGE" + ;; + esac \ No newline at end of file diff --git a/api/swagger.yml b/api/swagger.yml new file mode 100644 index 0000000..2d8e8c1 --- /dev/null +++ b/api/swagger.yml @@ -0,0 +1,2266 @@ +openapi: 3.0.0 +info: + description: | + This API server is the layer that allows 3rd party apps and services interfacing with the coordinator to explore, monitor and use the Hermez rollup. + Example of these apps are: + * Wallet: send L2 transactions, check balance, ... + * Explorer: List transactions, slots, batches, ... + * Exchange integrations + + All the endpoints that return a list of undefined size use pagination. Unless the opposite is explicitly said. + All the retunred items are ordered by ascending chronological order. + This may not be trivial to deduce as the atributes used to order are not timestamps but the protocol ensures that those atributes follow the mentioned chronological order. + Each endpoint description clarify this in the `offset` description. + + The response of the calls to these endpoints will always include a `pagination` object that includes `totalItems` and `lastReturnedItem`. + To iterate over the items the following query parameters are used: + - `offset`: Indicates the first item that will be returned. Defaul 0. Incompatible with `last`. + - `limit`: Indicates the maximum number of returned items. Default 20. Maximum 2049. + - `last`: When true the last `limit` items are returned. Default false. Incompatible with `offset`. + + Iterate items in ascending chronological order: + + 1. Call the endpoint with no `offset` nor `last`. + 2. Call the endpoint with `offset=` until `lastReturnedItem == totalItems - 1`. + + Iterate items in descending chronological order: + + 1. Call the endpoint with `last`. + 2. Call the endpoint with `offset=`. Once the `calculated offset == 0`, it will be known that that call will return the first item and therefore no subsequent calls need to be done. + If the `totalItems` change while iterating, it means that new items have been added at the end of the list. To fetch this items, use the following: `offset=`, and from there iterate as decribed in *Iterate items in ascending chronological order*. + + **Note:** The returned list will alway be in ascending chronlogical order, so the returned arrays must be iterated from end to start in order to achieve reverse chronological order. + + **Note:** Pagination safety can be affected by Ethereum reorgs. In most of the cases this means that the last page can be changed, but older items should be safe. + + version: "0.0.1" + title: Hermez Network API +# termsOfService: 'http://swagger.io/terms/' +# contact: +# email: apiteam@swagger.io +# license: +# name: Apache 2.0 +# url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +externalDocs: + description: Find out more about Hermez. + url: 'https://hermez.io' +servers: + - description: Hosted mock up + url: http://167.71.59.190:4010 + - description: Localhost mock Up + url: http://localhost:4010 +tags: + - name: Account + description: Hermez account and the tokens it holds. + externalDocs: + description: Find out more. + url: 'https://idocs.hermez.io/#/spec/zkrollup/README?id=account-types' + - name: Transaction + description: Send tokens off chain and track transactions. + externalDocs: + description: Find out more + url: 'https://idocs.hermez.io/#/spec/zkrollup/README?id=transaction-types' + - name: Hermez status + description: Info about operators, tokens, auctions and more. + externalDocs: + description: Find out more. + url: 'https://idocs.hermez.io/#/spec/zkrollup/README' +paths: + '/account-creation-authorization': + post: + tags: + - Account + summary: Send an authorization that will allow the coordinator to register accounts associated to an Ethereum address on behalf of the user. + description: >- + Send an authorization to create rollup accounts associated to an Ethereum address. Each account creation (an account can only hold a specific token) is effective once the coordinator forges the corresponding L1CoordinatorTx (which are always of type *account creation*). + operationId: postRegister + requestBody: + description: Account creation authorization. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AccountCreationAuthorization' + responses: + '200': + description: Successful operation. + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/account-creation-authorization/{hermezEthereumAddress}': + get: + tags: + - Account + summary: Get to know if the coordinator has the ability to create accounts associated to an Ethereum address. + description: >- + True if the coordinator has the required authorization to perform an account creation with the given Ethereum address on behalf of the Ethereum address holder. + operationId: getAccountCreationAuthorization + parameters: + - name: hermezEthereumAddress + in: path + description: Ethereum address. + required: true + schema: + $ref: '#/components/schemas/HermezEthereumAddress' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/AccountCreationAuthorization' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/accounts': + get: + tags: + - Account + summary: Get accounts balances and other associated information. + description: Get accounts balances and other associated information. + operationId: getAccounts + parameters: + - name: hermezEthereumAddress + in: query + description: Only get accounts associated to an Ethereum address. Incompatible with the query `BJJ`. + required: false + schema: + $ref: '#/components/schemas/HermezEthereumAddress' + - name: BJJ + in: query + description: Only get accounts associated to a BabyJubJub public key. Incompatible with the query `hermezEthereumAddress`. + required: false + schema: + $ref: '#/components/schemas/BJJ' + - name: tokenIds + in: query + required: false + description: Only get accounts of specific tokens. + schema: + type: string + description: Comma separated list of token identifiers. + example: "3,87,91" + - name: offset + in: query + required: false + description: | + - Order: accounts will be ordered by increasing account index. + - Default first item: the first account to be returned will be the one that has the smallest account index. + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Accounts' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/accounts/{accountIndex}': + get: + tags: + - Account + summary: Get an account by its index. + description: Get an account by its index. + operationId: getAccount + parameters: + - name: accountIndex + in: path + description: Identifier of an account. + required: true + schema: + $ref: '#/components/schemas/AccountIndex' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/exits': + get: + tags: + - Account + summary: Get exit information. This information is required to perform a withdraw. + description: Get exit information. This information is required to perform a withdraw. + operationId: getExits + parameters: + - name: hermezEthereumAddress + in: query + description: Get exits associated to a Ethereum address. Incompatible with query `BJJ` and `accountIndex`. + required: false + schema: + $ref: '#/components/schemas/HermezEthereumAddress' + - name: BJJ + in: query + description: Get exits associated to a BabyJubJub public key. Incompatible with query `hermezEthereumAddress` and `accountIndex`. + required: false + schema: + $ref: '#/components/schemas/BJJ' + - name: accountIndex + in: query + description: Get exits associated to a specific account. Incompatible with queries `hermezEthereumAddress` and `BJJ`. + required: false + schema: + $ref: '#/components/schemas/AccountIndex' + - name: batchNum + in: query + description: Get exits from the exit tree of a specific batch. + required: false + schema: + $ref: '#/components/schemas/BatchNum' + - name: offset + in: query + required: false + description: | + - Order: exits will be ordered by increasing (batchNum, accountIndex). + - Default first item: the first exit to be returned will be the one that has the smallest (baychNum, accountIndex). + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Exits' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/exits/{batchNum}/{accountIndex}': + get: + tags: + - Account + summary: Get specific exit information. + description: Get exit information form a specific exit tree and account. This information is required to perform a withdraw. + operationId: getExit + parameters: + - name: batchNum + in: path + description: Batch of the exit tree. + required: true + schema: + $ref: '#/components/schemas/BatchNum' + - name: accountIndex + in: path + description: Account identifier. + required: true + schema: + $ref: '#/components/schemas/AccountIndex' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Exit' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/transactions-pool': + post: + tags: + - Transaction + summary: Add an L2 transaction to the coordinator's pool + description: >- + Send L2 transaction. The transaction will be stored in the transaction pool of the coordinator and eventually forged. + operationId: postTx + requestBody: + description: Signed transaction. + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PoolL2Transaction' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/TransactionId' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/transactions-pool/{id}': + get: + tags: + - Transaction + summary: Get details and status of a transaction that is in the pool. + description: >- + Get transaction from the pool by its id. This endpoint is specially useful for tracking the status of a transaction that may not be forged yet. + Only transactions from the pool will be returned. + Note that the transaction pool is different for each coordinator and therefore only a coordinator that has received a specific transaction + will be able to provide information about that transaction. + operationId: getPoolTx + parameters: + - name: id + in: path + description: Transaction identifier. + required: true + schema: + $ref: '#/components/schemas/TransactionId' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/PoolL2Transaction' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/transactions-history': + get: + tags: + - Transaction + summary: Get details and status of transactions that have been forged. + description: >- + Get historical transactions. This endpoint will return all the different types of transactions except for: + - Transactions that are still in the transaction pool of any coordinator. These transactions can be fetched using `GET /transactions-pool/{id}`. + - L1 transactions that have not been forged yet. These transactions can be fetched using `GET /transactions-history/{id}`. + operationId: getHistoryTxs + parameters: + - name: tokenId + in: query + required: false + description: Only get transactions of specific token + schema: + $ref: '#/components/schemas/TokenId' + - name: hermezEthereumAddress + in: query + required: false + description: Only get transactions sent from or to an account associated to an Ethereum address Incompatible with the queries `BJJ` and `accountIndex`. + schema: + $ref: '#/components/schemas/HermezEthereumAddress' + - name: BJJ + in: query + description: Only get transactions associated to a BabyJubJub public key. Incompatible with the queries `hermezEthereumAddress` and `accountIndex`. + required: false + schema: + $ref: '#/components/schemas/BJJ' + - name: accountIndex + in: query + required: false + description: Only get transactions sent from or to a specific account. Incompatible with the queries `tokenId`, `hermezEthereumAddress` and `BJJ`. + schema: + $ref: '#/components/schemas/AccountIndex' + - name: batchNum + in: query + required: false + description: Only get transactions forged in a specific batch. + schema: + $ref: '#/components/schemas/BatchNum' + - name: type + in: query + required: false + description: Only get transactions of a specific type. + schema: + $ref: '#/components/schemas/TransactionType' + - name: offset + in: query + required: false + description: | + - Order: History transactions will be ordered by increasing (batchNum, position). + - Default first item: the first transaction to be returned will be the one that has the smallest (batchNum, position). + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/HistoryTransactions' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/transactions-history/{id}': + get: + tags: + - Transaction + summary: Get details and status of a historical transaction. + description: >- + Get transaction from the history by its id. This endpoint will return all the different types of transactions except those that are still in the pool of any coordinator. + operationId: getHistoryTx + parameters: + - name: id + in: path + description: Transaction identifier. + required: true + schema: + $ref: '#/components/schemas/TransactionId' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/HistoryTransaction' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/batches': + get: + tags: + - Hermez status + summary: Get information about forged batches. + description: >- + Get information about forged batches. + operationId: getBatches + parameters: + - name: minBatchNum + in: query + required: false + description: Include only `batchNum < minBatchNum` batches. + schema: + $ref: '#/components/schemas/BatchNum' + - name: maxBatchNum + in: query + required: false + description: Include only `batchNum > maxBatchNum` batches. + schema: + type: number + - name: forgerAddr + in: query + required: false + description: Include only batches forged by `forgerAddr` + schema: + $ref: '#/components/schemas/EthereumAddress' + - name: offset + in: query + required: false + description: | + - Order: batches will be ordered by increasing `batchNum`. + - Default first item: the first batch to be returned will be the one that has the smallest `batchNum`. + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Batches' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/batches/{batchNum}': + get: + tags: + - Hermez status + summary: Get a specific batch. + description: >- + Get a specific batch. + operationId: getBatch + parameters: + - name: batchNum + in: path + description: Batch identifier. + required: true + schema: + $ref: '#/components/schemas/BatchNum' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Batch' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/full-batches/{batchNum}': + get: + tags: + - Hermez status + summary: Get a full batch + description: >- + Get a specific batch, including the associated transactions. The object returned in this method can be a bit heavy. + If you're devloping a front end, you may consider using a combinaton of `GET /batches/{batchnum}` and `GET /history-transactions?batchNum={batchNum}`. + operationId: getFullBatch + parameters: + - name: batchNum + in: path + description: Batch identifier + required: true + schema: + $ref: '#/components/schemas/BatchNum' + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/FullBatch' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/slots': + get: + tags: + - Hermez status + summary: Get information about slots. + description: >- + Get information about slots. + operationId: getSlots + parameters: + - name: minSlotNum + in: query + required: false + description: Only include batches with `slotNum < minSlotNum`. + schema: + $ref: '#/components/schemas/SlotNum' + - name: maxSlothNum + in: query + required: false + description: Only include batches with `slotNum > maxSlotNum`. + schema: + $ref: '#/components/schemas/SlotNum' + - name: wonByEthereumAddress + in: query + required: false + description: Only include slots won by a coordinator whose `forgerAddr == wonByEthereumAddress`. + schema: + $ref: '#/components/schemas/EthereumAddress' + - name: finishedAuction + in: query + required: false + description: If set to true, only include slots whose auction has finished. + schema: + type: boolean + - name: offset + in: query + required: false + description: | + - Order: slots will be ordered by increasing `slotNum`. + - Default first item: the first slot to be returned will be the one that has the smallest `slotNum`. + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Slots' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/slots/{slotNum}': + get: + tags: + - Hermez status + summary: Get information about a specific slot. + description: >- + Get information about a specific slot. + operationId: getSlot + parameters: + - name: slotNum + in: path + required: true + description: Identifier of the slot. + schema: + $ref: '#/components/schemas/SlotNum' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Slot' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/bids': + get: + tags: + - Hermez status + summary: Get a list of bids made for a specific slot auction. + description: Get a list of bids made for a specific slot auction. + operationId: getSlotBids + parameters: + - name: slotNum + in: query + description: Slot identifier. Specify the auction where the returned bids were made. + required: false + schema: + $ref: '#/components/schemas/SlotNum' + - name: forgerAddr + in: query + description: Get only bids made by a coordinator identified by its forger address. + required: false + schema: + $ref: '#/components/schemas/EthereumAddress' + - name: offset + in: query + required: false + description: | + - Order: bids will be ordered by increasing (slotNum, bidValue)`. + - Default first item: the first bid to be returned will be the one that has the smallest (slotNum, bidValue). + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Bids' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/next-forgers': + get: + tags: + - Hermez status + summary: Get next coordinators to forge. + description: >- + Return a list of the coordinators that will forge in the next slots. + The response includes the coordinator that is currently forging, and the ones that have won the upcomming slots whose auctions are closed. + operationId: getNextForgers + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/NextForgers' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/state': + get: + tags: + - Hermez status + summary: Return global statistics and metrics of the network. + description: Return global statistics and metrics of the network. + operationId: getState + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/State' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/config': + get: + tags: + - Hermez status + summary: Return constant configuration of the network. + description: Return constant configuration of the network. + operationId: getConfig + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Config' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/tokens': + get: + tags: + - Hermez status + summary: Get information of the supported tokens in the Hermez network. + description: Get information of the supported tokens in the Hermez network. + operationId: getTokens + parameters: + - name: ids + in: query + required: false + description: Include only specific tokens by their Hermez identifiers. + schema: + type: string + description: Comma separated list of token identifiers + example: "2,44,689" + - name: symbols + in: query + required: false + description: Include only specific tokens by their symbols. + schema: + type: string + description: Comma separated list of token symbols. + example: "DAI,NEC,UMA" + - name: name + in: query + required: false + description: Include token(s) by their names (or a substring of the name). + schema: + type: string + - name: offset + in: query + required: false + description: | + - Order: tokens will be ordered by increasing tokenID. + - Default first item: the first token to be returned will be the one that has the smallest tokenID. + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Tokens' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/tokens/{id}': + get: + tags: + - Hermez status + summary: Get information of a token supported by Hermez network. + description: Get information of a token supported by Hermez network. + operationId: getToken + parameters: + - name: id + in: path + description: Token identifier + required: true + schema: + $ref: '#/components/schemas/TokenId' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Token' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/recommended-fee': + get: + tags: + - Hermez status + summary: Get recommended fee in USD. + description: >- + Get recommended fee in USD. Recommended price to pay according to the status of the destination account. + operationId: getFee + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/RecommendedFee' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/coordinators': + get: + tags: + - Hermez status + summary: Get information about coordinators. + description: Get information about coordinators. + operationId: getCoordinators + parameters: + - name: offset + in: query + required: false + description: | + - Order: coordinators will be ordered by increasing (ethereumBlock, forgerAddr). + - Default first item: the first token to be returned will be the one that has the smallest (ethereumBlock, forgerAddr). + schema: + type: number + - name: last + in: query + required: false + description: Get the last page. + schema: + type: boolean + - name: limit + in: query + required: false + description: Maximum number of items to be returned. + schema: + type: integer + minimum: 1 + maximum: 2049 + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Coordinators' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' + '/coordinators/{forgerAddr}': + get: + tags: + - Hermez status + summary: Get the information of a coordinator. + description: Get the information of a coordinator. + operationId: getCoordinator + parameters: + - name: forgerAddr + in: path + description: Coordinator identifier + required: true + schema: + $ref: '#/components/schemas/EthereumAddress' + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + $ref: '#/components/schemas/Coordinator' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '404': + description: Not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Error404' + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' +components: + schemas: + PoolL2Transaction: + type: object + properties: + id: + $ref: '#/components/schemas/TransactionId' + type: + $ref: '#/components/schemas/TransactionType' + fromAccountIndex: + $ref: '#/components/schemas/AccountIndex' + toAccountIndex: + allOf: + - $ref: '#/components/schemas/AccountIndex' + - example: "hez:DAI:672" + toEthereumAddress: + $ref: '#/components/schemas/HermezEthereumAddress' + toBjj: + $ref: '#/components/schemas/BJJ' + tokenId: + $ref: '#/components/schemas/TokenId' + USD: + type: number + description: Value of the token in USD. + example: 4.53 + fiatUpdate: + type: string + format: date-time + description: Timestamp of the moment the `USD` value was updated. + amount: + allOf: + - $ref: '#/components/schemas/BigInt' + - description: Amount of tokens to be sent. + example: "63" + fee: + $ref: '#/components/schemas/FeeSelector' + feeUSD: + type: number + description: Fee in USD. + example: 0.75 + nonce: + $ref: '#/components/schemas/Nonce' + state: + $ref: '#/components/schemas/PoolL2TransactionState' + signature: + allOf: + - $ref: '#/components/schemas/Signature' + - description: Signature of the transaction. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=l2a-idl2). + - example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03" + timestamp: + type: string + description: Moment in which the transaction was added to the pool. + format: date-time + batchNum: + allOf: + - $ref: '#/components/schemas/BatchNum' + - nullable: true + - example: 5432 + requestFromAccountIndex: + allOf: + - $ref: '#/components/schemas/AccountIndex' + - nullable: true + - example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a" + requestToAccountIndex: + allOf: + - $ref: '#/components/schemas/AccountIndex' + - nullable: true + - example: "hez:DAI:33" + requestToEthereumAddress: + allOf: + - $ref: '#/components/schemas/HermezEthereumAddress' + - nullable: true + - example: "hez:0xbb942cfcd25ad4d90a62358b0dd84f33b3982699" + requestToBJJ: + allOf: + - $ref: '#/components/schemas/BJJ' + - nullable: true + - example: "hez:HVrB8xQHAYt9QTpPUsj3RGOzDmrCI4IgrYslTeTqo6Ix" + requestTokenId: + allOf: + - $ref: '#/components/schemas/TokenId' + - nullable: true + - example: 4444 + requestAmount: + allOf: + - $ref: '#/components/schemas/BigInt' + - description: Amount of tokens to be sent. + - example: "7" + - nullable: true + requestFee: + allOf: + - $ref: '#/components/schemas/FeeSelector' + - nullable: true + - example: 8 + requestNonce: + allOf: + - $ref: '#/components/schemas/Nonce' + - nullable: true + - example: 6 + tokenSymbol: + $ref: '#/components/schemas/TokenSymbol' + required: + - fromAccountIndex + - toAccountIndex + - toEthereumAddress + - toBjj + - tokenId + - amount + - fee + - nonce + - signature + TransactionId: + type: string + description: Identifier for transactions. Used for any kind of transaction (both L1 and L2). More info on how the identifiers are built [here](https://idocs.hermez.io/#/spec/architecture/db/README?id=txid) + example: "0x0040e2010000000000470000" + EthereumAddress: + type: string + description: "Address of an Etherum account." + pattern: "^0x[a-fA-F0-9]{40}$" + example: "0xaa942cfcd25ad4d90a62358b0dd84f33b398262a" + HermezEthereumAddress: + type: string + description: "Address of an Etherum account linked to the Hermez network." + pattern: "^hez:0x[a-fA-F0-9]{40}$" + example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a" + BJJ: + type: string + description: "BabyJubJub public key, encoded as base64, which result in 33 bytes (last byte used as checksum)." + pattern: "^hez:[A-Za-z0-9_-]{44}$" + example: "hez:rR7LXKal-av7I56Y0dEBCVmwc9zpoLY5ERhy5w7G-xwe" + AccountIndex: + type: string + description: >- + Identifier of an account. It references the position where the account is inside the state Merkle tree. + The identifier is built using: `hez:` + `token symbol:` + `index` + example: "hez:DAI:4444" + TransactionType: + type: string + description: Type of transaction. + enum: + - Exit + - Withdrawn + - Transfer + - Deposit + - CreateAccountDeposit + - CreateAccountDepositTransfer + - DepositTransfer + - ForceTransfer + - ForceExit + - TransferToEthAddr + - TransferToBJJ + TokenId: + type: integer + description: Identifier of a token registered in the network. + minimum: 0 + maximum: 4294967295 + example: 4444 + BigInt: + type: string + description: BigInt is an integer encoded as a string for numbers that are very large. + example: "870885693" + FeeSelector: + type: integer + description: Index of the fee type to select, more info [here](https://idocs.hermez.io/#/spec/zkrollup/fee-table?id=transaction-fee-table). + minimum: 0 + maximum: 256 + example: 36 + Nonce: + type: integer + description: Number that can only be used once per account, increments by one at each transaction. + minimum: 0 + maximum: 1.84467440737096e+19 + example: 121 + PoolL2TransactionState: + type: string + description: > + State of a L2 transaction from the coordinator pool. + * pend: Pending + * fing: Forging + * fged: Forged + * invl: Invalid + enum: + - pend + - fing + - fged + - invl + Signature: + type: string + description: BabyJubJub compressed signature. + pattern: "^[a-fA-F0-9]{128}$" + example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03" + BatchNum: + type: integer + description: Identifier of a batch. Every new forged batch increments by one the batchNum, starting at 0. + minimum: 0 + maximum: 4294967295 + example: 5432 + AccountCreationAuthorization: + type: object + properties: + timestamp: + type: string + format: date-time + ethereumAddress: + $ref: '#/components/schemas/HermezEthereumAddress' + bjj: + $ref: '#/components/schemas/BJJ' + signature: + allOf: + - $ref: '#/components/schemas/Signature' + - description: Signature of the auth message. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=regular-rollup-account). + - example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03" + required: + - ethereumAddress + - bjj + - signature + HistoryTransaction: + type: object + description: Transaction of the Hermez network + properties: + L1orL2: + type: string + enum: + - L1 + - L2 + id: + $ref: '#/components/schemas/TransactionId' + type: + $ref: '#/components/schemas/TransactionType' + position: + $ref: '#/components/schemas/TransactionPosition' + fromAccountIndex: + $ref: '#/components/schemas/AccountIndex' + toAccountIndex: + allOf: + - $ref: '#/components/schemas/AccountIndex' + - example: "hez:DAI:672" + amount: + allOf: + - $ref: '#/components/schemas/BigInt' + - description: Amount of tokens to be sent. + - example: "49" + batchNum: + type: integer + description: Batch in which the transaction was forged. Null indicates not forged yet. + minimum: 0 + maximum: 4294967295 + example: 5432 + nullable: true + tokenId: + $ref: '#/components/schemas/TokenId' + tokenSymbol: + $ref: '#/components/schemas/TokenSymbol' + historicUSD: + type: number + description: Value in USD at the moment the transaction was forged. + example: 49.7 + currentUSD: + type: number + description: Value in USD at the current token/USD conversion. + example: 50.01 + fiatUpdate: + type: string + format: date-time + description: Timestamp of the moment the `currentUSD` value was updated. + timestamp: + type: string + format: date-time + description: In the case of L1 indicates the moment where the transaction was added in the smart contract. For L2 indicates when the transaction was forged. + L1Info: + type: object + description: Additional information that only applies to L1 transactions. + nullable: true + properties: + toForgeL1TransactionsNum: + $ref: '#/components/schemas/ToForgeL1TransactionsNum' + userOrigin: + type: boolean + description: True if the transaction was sent by a user. False if it was sent by a coordinator. + fromEthereumAddress: + $ref: '#/components/schemas/HermezEthereumAddress' + fromBJJ: + $ref: '#/components/schemas/BJJ' + loadAmount: + allOf: + - $ref: '#/components/schemas/BigInt' + - description: Tokens transfered from L1 to L2. + - example: "49" + loadAmountUSD: + type: number + description: Load amount in USD, at the moment the transaction was made. + example: 3.897 + ethereumBlockNum: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Ethereum block in which the transaction was added to the smart contract forge queue. + - example: 258723049 + required: + - toForgeL1TransactionsNum + - userOrigin + - fromEthereumAddress + - fromBJJ + - loadAmount + - loadAmountUSD + - ethereumBlockNum + additionalProperties: false + L2Info: + type: object + description: Additional information that only applies to L2 transactions. + nullable: true + properties: + fee: + $ref: '#/components/schemas/FeeSelector' + feeUSD: + type: number + description: Fee in USD, at the moment the transaction was forged. + example: 263.89 + nonce: + $ref: '#/components/schemas/Nonce' + example: null + required: + - fee + - feeUSD + - nonce + additionalProperties: false + required: + - L1orL2 + - id + - type + - position + - fromAccountIndex + - toAccountIndex + - amount + - batchNum + - tokenId + - tokenSymbol + - historicUSD + - currentUSD + - fiatUpdate + - timestamp + - L1Info + - L2Info + additionalProperties: false + HistoryTransactions: + type: object + properties: + transactions: + type: array + description: List of history transactions. + items: + $ref: '#/components/schemas/HistoryTransaction' + pagination: + $ref: '#/components/schemas/PaginationInfo' + required: + - transactions + - pagination + additionalProperties: false + EthBlockNum: + type: integer + description: Ethereum block number + minimum: 0 + maximum: 1.84467440737096e+19 + example: 762375478 + ToForgeL1TransactionsNum: + type: integer + description: Reference to know in which batch a L1 transaction was forged / will be forged. + minimum: 0 + maximum: 4294967295 + example: 784 + TransactionPosition: + type: integer + description: Position that a transaction occupies in a batch. + minimum: 0 + example: 5 + URL: + type: string + description: HTTP URL + example: "https://hermez.io" + TokenSymbol: + type: string + description: Abreviation of the token name. + example: "DAI" + TokenName: + type: string + description: Token name. + example: "Dai" + CollectedFees: + type: array + description: Collected fees by the forger of the batch. A maximum of 64 different tokens can be used. + items: + type: object + properties: + tokenId: + $ref: '#/components/schemas/TokenId' + tokenSymbol: + $ref: '#/components/schemas/TokenSymbol' + amount: + allOf: + - $ref: '#/components/schemas/BigInt' + - description: Ammount of collected tokens + - example: "53" + Batch: + type: object + description: Group of transactions forged in a coordinator and sent and validated in Ethereum. + properties: + batchNum: + $ref: '#/components/schemas/BatchNum' + ethereumBlockNum: + $ref: '#/components/schemas/EthBlockNum' + forgerAddr: + $ref: '#/components/schemas/EthereumAddress' + collectedFees: + $ref: '#/components/schemas/CollectedFees' + totalCollectedFeesUSD: + type: number + description: Sum of the all the fees collected, in USD. + example: 23.3 + stateRoot: + allOf: + - $ref: '#/components/schemas/Hash' + - description: Root of the accounts Merkle Tree. + - example: "2734657026572a8708d883" + numAccounts: + type: integer + description: Number of registered accounts in this batch. + exitRoot: + allOf: + - $ref: '#/components/schemas/Hash' + - description: Root of the exit Merkle Tree associated to this batch. + - example: "2734657026572a8708d883" + forgeL1TransactionsNum: + allOf: + - $ref: '#/components/schemas/ToForgeL1TransactionsNum' + - description: Identifier that corresponds to the group of L1 transactions forged in the current batch. + - nullable: true + - example: 5 + slotNum: + $ref: '#/components/schemas/SlotNum' + FullBatch: + type: object + description: Group of transactions forged in a coordinator and sent and validated in Ethereum. + properties: + batchNum: + $ref: '#/components/schemas/BatchNum' + forgerAddr: + $ref: '#/components/schemas/EthereumAddress' + collectedFees: + $ref: '#/components/schemas/CollectedFees' + ethereumBlockNum: + $ref: '#/components/schemas/EthBlockNum' + stateRoot: + allOf: + - $ref: '#/components/schemas/Hash' + - description: Root of the accounts Merkle Tree. + - example: "2734657026572a8708d883" + numAccounts: + type: integer + description: Number of registered accounts in this batch. + exitRoot: + allOf: + - $ref: '#/components/schemas/Hash' + - description: Root of the exit Merkle Tree associated to this batch. + - example: "2734657026572a8708d883" + forgeL1TransactionsNum: + allOf: + - $ref: '#/components/schemas/ToForgeL1TransactionsNum' + - description: Identifier that corresponds to the group of L1 transactions forged in the current batch. + - nullable: true + - example: 9 + slotNum: + $ref: '#/components/schemas/SlotNum' + transactions: + type: array + description: List of forged transactions in the batch + items: + $ref: '#/components/schemas/HistoryTransaction' + Hash: + type: string + description: hashed data + example: "2734657026572a8708d883" + SlotNum: + type: integer + description: Identifier of a slot. + minimum: 0 + maximum: 4294967295 + example: 784 + Batches: + type: object + properties: + batches: + type: array + description: List of batches. + items: + $ref: '#/components/schemas/Batch' + pagination: + $ref: '#/components/schemas/PaginationInfo' + Coordinator: + type: object + properties: + forgerAddr: + $ref: '#/components/schemas/EthereumAddress' + withdrawAddr: + $ref: '#/components/schemas/EthereumAddress' + URL: + $ref: '#/components/schemas/URL' + ethereumBlock: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Ethereum block in which the coordinator registered into the network. + - example: 5735943738 + Coordinators: + type: object + properties: + coordinators: + type: array + description: List of coordinators. + items: + $ref: '#/components/schemas/Coordinator' + pagination: + $ref: '#/components/schemas/PaginationInfo' + Bid: + type: object + description: Tokens placed in an auction by a coordinator to gain the right to forge batches during a specific slot. + properties: + forgerAddr: + $ref: '#/components/schemas/EthereumAddress' + withdrawAddr: + $ref: '#/components/schemas/EthereumAddress' + URL: + $ref: '#/components/schemas/URL' + bidValue: + $ref: '#/components/schemas/BigInt' + ethereumBlockNum: + $ref: '#/components/schemas/EthBlockNum' + timestamp: + type: string + format: date-time + Bids: + type: object + properties: + bids: + type: array + description: List of bids. + items: + $ref: '#/components/schemas/Bid' + pagination: + $ref: '#/components/schemas/PaginationInfo' + RecommendedFee: + type: object + description: Fee that the coordinator recommends per transaction in USD. + properties: + existingAccount: + type: number + description: Recommended fee if the destination account of the transaction already exists. + minimum: 0 + example: 0.1 + createAccount: + type: number + description: Recommended fee if the destination account of the transaction doesn't exist, but the coordinator has an authorization to create a valid account associated to an Ethereum address and a BJJ public key controlled by the receiver. + minimum: 0 + example: 1.3 + createAccountInternal: + type: number + description: Recommended fee if the destination account of the transaction doesn't exist, but the coordinator has the ability to create a valid account associated to a BJJ public key controlled by the receiver. Note that these kind of accounts are not associated to an Ethereum address and therefore can only operate in L2. + minimum: 0 + example: 0.5 + Token: + type: object + description: Hermez network compatible and registered token. + properties: + id: + $ref: '#/components/schemas/TokenId' + ethereumAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address in which the token is deployed. + - example: "0xaa942cfcd25ad4d90a62358b0dd84f33b398262a" + name: + type: string + description: full name of the token + example: Maker Dai + symbol: + allOf: + - $ref: '#/components/schemas/TokenSymbol' + - example: DAI + decimals: + type: integer + description: Number of decimals of the token. + example: 5 + ethereumBlockNum: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Ethereum block number in which the token was added to the Hermez network. + - example: 539847538 + USD: + type: number + description: Value of the token in USD. + example: 4.53 + fiatUpdate: + type: string + format: date-time + description: Timestamp of the moment the `USD` value was updated. + Tokens: + type: object + properties: + tokens: + type: array + description: List of tokens. + items: + $ref: '#/components/schemas/Token' + pagination: + $ref: '#/components/schemas/PaginationInfo' + Exit: + type: object + description: Exit tree leaf. It Contains the necessary information to perform a withdrawal. + properties: + batchNum: + allOf: + - $ref: '#/components/schemas/BatchNum' + - description: Batch in which the exit was forged. + - example: 7394 + accountIndex: + $ref: '#/components/schemas/AccountIndex' + merkleProof: + type: string + description: Existence proof of a leaf in a given Merkle Root. Encoded as hexadecimal string. + example: "0x347089321de8971320489793a823470918fffeab" + balance: + $ref: '#/components/schemas/BigInt' + nullifier: + $ref: '#/components/schemas/BigInt' + instantWithdrawn: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Block in which the exit balance was instantly withdrawn. Null indicates that an instant withdrawn hasn't been performed. + - example: 74747363 + delayedWithdrawRequest: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Block in which the exit balance was requested to delay withdraw. Null indicates that a delay withdraw hasn't been performed. + - example: null + delayedWithdrawn: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Block in which the exit balance was delayed withdrawn after a delay withdraw request. Null indicates that a delay withdraw hasn't been performed. + - example: null + Exits: + type: object + properties: + exits: + type: array + description: List of exits. + items: + $ref: '#/components/schemas/Exit' + pagination: + $ref: '#/components/schemas/PaginationInfo' + Account: + type: object + description: State tree leaf. It contains balance and nonce of an account. + properties: + accountIndex: + $ref: '#/components/schemas/AccountIndex' + tokenId: + $ref: '#/components/schemas/TokenId' + tokenSymbol: + $ref: '#/components/schemas/TokenSymbol' + tokenName: + $ref: '#/components/schemas/TokenName' + nonce: + $ref: '#/components/schemas/Nonce' + balance: + $ref: '#/components/schemas/BigInt' + balanceUSD: + type: integer + description: Balance of the account in USD + example: 1304 + bjj: + $ref: '#/components/schemas/BJJ' + ethereumAddress: + $ref: '#/components/schemas/HermezEthereumAddress' + Accounts: + type: object + properties: + accounts: + type: array + description: List of accounts. + items: + $ref: '#/components/schemas/Account' + pagination: + $ref: '#/components/schemas/PaginationInfo' + Slot: + type: object + description: Slot information. + properties: + slotNum: + $ref: '#/components/schemas/SlotNum' + firstBlock: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Block in which the slot began or will begin + - example: 76238647846 + lastBlock: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Block in which the slot ended or will end + - example: 4475934 + closedAuction: + type: boolean + description: Whether the auction of the slot has finished or not. + winner: + allOf: + - $ref: '#/components/schemas/Coordinator' + - description: Coordinator who won the auction. Only applicable if the auction is closed. + - nullable: true + - example: null + batchNums: + type: array + description: List of batch numbers that were forged during the slot + items: + $ref: '#/components/schemas/BatchNum' + Slots: + type: object + properties: + nextForgers: + type: array + description: List of slots. + items: + $ref: '#/components/schemas/Slot' + pagination: + $ref: '#/components/schemas/PaginationInfo' + NextForger: + type: object + description: Coordinator information along with the scheduled forging period + properties: + coordinator: + $ref: '#/components/schemas/Coordinator' + period: + type: object + description: Time period in which the coordinator will have the ability to forge. Specified both in Ethereum blocks and timestamp + properties: + fromBlock: + $ref: '#/components/schemas/EthBlockNum' + toBlock: + $ref: '#/components/schemas/EthBlockNum' + fromTimestamp: + type: string + format: date-time + toTimestamp: + type: string + format: date-time + NextForgers: + type: object + properties: + nextForgers: + type: array + description: List of next coordinators to forge. + items: + $ref: '#/components/schemas/NextForger' + pagination: + $ref: '#/components/schemas/PaginationInfo' + State: + type: object + description: Gobal statistics of the network. + properties: + lastBlock: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Last synchronized Etherum block. + - example: 3457437 + lastBatch: + allOf: + - $ref: '#/components/schemas/BatchNum' + - description: Last batch that has been forged. + - example: 76523 + currentSlot: + allOf: + - $ref: '#/components/schemas/SlotNum' + - description: Slot where batches are currently being forged. + - example: 2334 + transactionsPerBatch: + type: number + description: Average transactions per batch in the last 24 hours. + example: 2002.7 + batchFrequency: + type: number + description: Average elapsed time between batches in the last 24 hours, in seconds. + example: 8.9 + transactionsPerSecond: + type: number + description: Average transactions per second in the last 24 hours. + example: 302.3 + totalAccounts: + type: integer + description: Number of created accounts. + example: 90473 + totalBJJs: + type: integer + description: Number of different registered BJJs. + example: 23067 + avgTransactionFee: + type: number + description: Average fee percentage paid for L2 transactions in the last 24 hours. + example: 1.54 + governance: + type: object + description: Network setings that are updatable by the governance. + properties: + rollup: + type: object + description: Rollup parameters. + properties: + forgeTimeout: + type: integer + description: Time delay between the beggining of a slot and the beggining of the period in which anyone can forge if the auction winner of the slot hasn't forged any batch yet. Time is measured in Ethereum blocks. + example: 5 + feeAddToken: + type: integer + description: fee to pay when registering tokens into the network. + example: 5698 + auction: + type: object + description: Auction parameters. + properties: + bootCoordinator: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address of the boot coordinator. + slotDeadline: + type: integer + description: Number of blocks at the end of a slot in which any coordinator can forge if the winner has not forged one before. + example: 3 + closedAuctionSlots: + type: integer + description: Amount of slots between the current slot and the slot auction that is closed. Example if the value is 2, when slot 10 begins, the auction of the slot 12 gets closed. + example: 2 + openAuctionSlots: + type: integer + description: How many days in advance are auctions opened. + defaultSlotSetBid: + type: array + description: "Initial minimal bid for each auction. Expressed as an array of 6 values. To calculate which value corresponds to each slot: `initialMinimalBidding[slotNum%6]`." + items: + type: integer + example: [32,0,68,21,55,99] + outbidding: + type: number + description: Minimum outbid over the previous one to consider it valid. + example: 3.64 + donationAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address where the donations will go to. + allocationRatio: + type: array + description: Percentage in which fees will be splitted between donations, governance and burning. The sum of the tree values should be 100. + items: + type: integer + example: [80,10,10] + withdrawalDelayer: + type: object + description: Withdrawal delayer parameters. + properties: + rollupAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address of the rollup smart contract. + governanceAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address of the governance mechanism. + whitheHackerGroupAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum Address that can claim the funds in an emergency when the maximum emergency mode time is exceeded. + keeperAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum Address that can enable emergency mode and modify the delay to make a withdrawal. + withdrawalDelay: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: The time that anyone needs to wait until a withdrawal of the funds is allowed, in Ethereum blocks. + emergencyModeStartingTime: + type: integer + description: Ethereum block in which the emergency mode will be activated. + example: 10 + emergencyMode: + type: boolean + description: Indicates if emergency mode has been activated. + + PaginationInfo: + type: object + description: Give pagination information + properties: + totalItems: + type: integer + description: Amount of items that the endpoint can return given the filters and the current state of the database. + example: 2048 + lastReturnedItem: + type: integer + description: Index of the last returned item. Useful to query next items. + example: 439 + Config: + type: object + description: Configuration parameters of the different smart contracts that power the Hermez network. + properties: + hermez: + type: object + description: Constant configuration of the Hermez smart contract. + properties: + HEZTokenAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address of the HEZ token. + maxTxVerifiers: + type: integer + description: Maximum transactions of the verifiers. + example: 100 + maxLoadAmount: + type: integer + description: Maximum load amount (L1 to L2) allowed. + example: 321 + maxAmount: + type: integer + description: Maximum amount (L2 to L2) allowed. + example: 837 + maxTokens: + type: integer + description: Maximum number of different tokens that can be registered in the network. + example: 4294967295 + reservedAccountIndex: + type: integer + description: First user index. Bellow this number the indexes are reserved for the protocol. + example: 256 + lastAccountIndex: + type: integer + description: Biggest account index that can be registered. + example: 4294967295 + exitAccountIndex: + type: integer + description: Account index used to indicate that a transaction is an `exit` or `force exit`. + example: 1 + L1CoordinatorBytes: + type: integer + description: Number of bytes that a L1 coordinator transaction has ([4 bytes] token + [32 bytes] babyjub + [65 bytes] compressedSignature). + example: 23 + L1UserBytes: + type: integer + description: Number of bytes that a L2 transaction has ([4 bytes] fromIdx + [4 bytes] toIdx + [2 bytes] amountFloat16 + [1 bytes] fee). + example: 32 + L2Bytes: + type: integer + description: Number of bytes that a L2 transaction has ([4 bytes] fromIdx + [4 bytes] toIdx + [2 bytes] amountFloat16 + [1 bytes] fee). + example: 33 + maxL1Transactions: + type: integer + description: Maximum L1 transactions allowed to be queued in a batch. + example: 128 + maxL1UserTransactions: + type: integer + description: Maximum L1 transactions allowed to be queued in a batch by users (anyone who is not a coordinator). + example: 32 + RField: + allOf: + - $ref: '#/components/schemas/BigInt' + - description: Modulus zkSNARK. + withdrawManager: + type: object + description: Constant configuration of the withdraw manager smart contract. + properties: + noLimit: + type: integer + description: Reserved bucket index when the token value is 0 USD. + example: 0 + amountOfBuckets: + type: integer + description: Number of buckets + maxWithdrawalDelay: + type: integer + description: Maximum delay to withdraw tokens. Time is measured in Ethereum blocks. + auction: + type: object + description: Constant configuration of the auction smart contract. + properties: + HEZTokenAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address of the HEZ token. + rollupAddress: + allOf: + - $ref: '#/components/schemas/EthereumAddress' + - description: Ethereum address of the rollup smart contract. + genesisBlockNum: + allOf: + - $ref: '#/components/schemas/EthBlockNum' + - description: Ethereum block number in which the smart contract starts operating. + delayGenesis: + type: integer + description: Time delay between `genesisBlockNum` and the beginning of the first block. Time is measured in Ethereum blocks. + blocksPerSlot: + type: integer + description: Blocks per slot. + initialMinimalBidding: + type: integer + description: Minimum bid when no one has bid yet. + withdrawalDelayer: + type: object + description: Constant configuration of the withdrawal delayer smart contract. + properties: + maxWithdrawalDelay: + type: integer + description: Maximum time delay in which the tokens can be locked in the contract. Time is measured in Ethereum blocks. + example: 200 + maxEmergencyModeTime: + type: integer + description: Maximum amount of time in which the contract can be in emergency mode. Time is measured in Ethereum blocks. + example: 2000 + Error: + type: object + description: Error response. + properties: + message: + type: string + Error400: + allOf: + - $ref: '#/components/schemas/Error' + - example: + message: Invalid signature. + Error404: + allOf: + - $ref: '#/components/schemas/Error' + - example: + message: Item(s) not found. + Error500: + allOf: + - $ref: '#/components/schemas/Error' + - example: + message: Database error. diff --git a/common/fee.go b/common/fee.go index d2e47bf..57f3c37 100644 --- a/common/fee.go +++ b/common/fee.go @@ -1,5 +1,7 @@ package common +import "math" + // Fee is a type that represents the percentage of tokens that will be paid in a transaction // to incentivaise the materialization of it type Fee float64 @@ -16,6 +18,20 @@ type RecommendedFee struct { // FeeSelector is used to select a percentage from the FeePlan. type FeeSelector uint8 +// Percentage returns the associated percentage of the FeeSelector +func (f FeeSelector) Percentage() float64 { + if f == 0 { + return 0 + //nolint:gomnd + } else if f <= 32 { //nolint:gomnd + return math.Pow(10, -24+(float64(f)/2)) //nolint:gomnd + } else if f <= 223 { //nolint:gomnd + return math.Pow(10, -8+(0.041666666666667*(float64(f)-32))) //nolint:gomnd + } else { + return math.Pow(10, float64(f)-224) //nolint:gomnd + } +} + // MaxFeePlan is the maximum value of the FeePlan const MaxFeePlan = 256 diff --git a/common/tx.go b/common/tx.go index 440ba07..94bc97e 100644 --- a/common/tx.go +++ b/common/tx.go @@ -27,15 +27,15 @@ const ( // TxTypeCreateAccountDepositTransfer represents L1->L2 transfer + L2->L2 transfer TxTypeCreateAccountDepositTransfer TxType = "CreateAccountDepositTransfer" // TxTypeDepositTransfer TBD - TxTypeDepositTransfer TxType = "TxTypeDepositTransfer" + TxTypeDepositTransfer TxType = "DepositTransfer" // TxTypeForceTransfer TBD - TxTypeForceTransfer TxType = "TxTypeForceTransfer" + TxTypeForceTransfer TxType = "ForceTransfer" // TxTypeForceExit TBD - TxTypeForceExit TxType = "TxTypeForceExit" + TxTypeForceExit TxType = "ForceExit" // TxTypeTransferToEthAddr TBD - TxTypeTransferToEthAddr TxType = "TxTypeTransferToEthAddr" + TxTypeTransferToEthAddr TxType = "TransferToEthAddr" // TxTypeTransferToBJJ TBD - TxTypeTransferToBJJ TxType = "TxTypeTransferToBJJ" + TxTypeTransferToBJJ TxType = "TransferToBJJ" ) // Tx is a struct used by the TxSelector & BatchBuilder as a generic type generated from L1Tx & PoolL2Tx diff --git a/db/historydb/historydb.go b/db/historydb/historydb.go index 60f24bf..4946ac9 100644 --- a/db/historydb/historydb.go +++ b/db/historydb/historydb.go @@ -1,11 +1,16 @@ package historydb import ( + "database/sql" + "errors" "fmt" + ethCommon "github.com/ethereum/go-ethereum/common" "github.com/gobuffalo/packr/v2" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db" + "github.com/hermeznetwork/hermez-node/log" + "github.com/iden3/go-iden3-crypto/babyjub" "github.com/jmoiron/sqlx" //nolint:errcheck // driver for postgres DB @@ -37,9 +42,11 @@ func NewHistoryDB(port int, host, user, password, dbname string) (*HistoryDB, er migrations := &migrate.PackrMigrationSource{ Box: packr.New("history-migrations", "./migrations"), } - if _, err := migrate.Exec(hdb.DB, "postgres", migrations, migrate.Up); err != nil { + nMigrations, err := migrate.Exec(hdb.DB, "postgres", migrations, migrate.Up) + if err != nil { return nil, err } + log.Debug("HistoryDB applied ", nMigrations, " migrations for ", dbname, " database") return &HistoryDB{hdb}, nil } @@ -320,6 +327,107 @@ func (hdb *HistoryDB) GetTxs() ([]*common.Tx, error) { return txs, err } +// GetHistoryTxs returns a list of txs from the DB using the HistoryTx struct +func (hdb *HistoryDB) GetHistoryTxs( + ethAddr *ethCommon.Address, bjj *babyjub.PublicKey, + tokenID, idx, batchNum *uint, txType *common.TxType, + offset, limit *uint, last bool, +) ([]*HistoryTx, int, error) { + if ethAddr != nil && bjj != nil { + return nil, 0, errors.New("ethAddr and bjj are incompatible") + } + var query string + var args []interface{} + queryStr := `SELECT tx.*, tx.amount_f * token.usd AS current_usd, + token.symbol, token.usd_update, block.timestamp, count(*) OVER() AS total_items FROM tx + INNER JOIN token ON tx.token_id = token.token_id + INNER JOIN block ON tx.eth_block_num = block.eth_block_num ` + // Apply filters + nextIsAnd := false + // ethAddr filter + if ethAddr != nil { + queryStr = `WITH acc AS + (select idx from account where eth_addr = ?) ` + queryStr + queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) " + nextIsAnd = true + args = append(args, ethAddr) + } else if bjj != nil { // bjj filter + queryStr = `WITH acc AS + (select idx from account where bjj = ?) ` + queryStr + queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) " + nextIsAnd = true + args = append(args, bjj) + } + // tokenID filter + if tokenID != nil { + if nextIsAnd { + queryStr += "AND " + } else { + queryStr += "WHERE " + } + queryStr += "tx.token_id = ? " + args = append(args, tokenID) + nextIsAnd = true + } + // idx filter + if idx != nil { + if nextIsAnd { + queryStr += "AND " + } else { + queryStr += "WHERE " + } + queryStr += "(tx.from_idx = ? OR tx.to_idx = ?) " + args = append(args, idx, idx) + nextIsAnd = true + } + // batchNum filter + if batchNum != nil { + if nextIsAnd { + queryStr += "AND " + } else { + queryStr += "WHERE " + } + queryStr += "tx.batch_num = ? " + args = append(args, batchNum) + nextIsAnd = true + } + // txType filter + if txType != nil { + if nextIsAnd { + queryStr += "AND " + } else { + queryStr += "WHERE " + } + queryStr += "tx.type = ? " + args = append(args, txType) + // nextIsAnd = true + } + // pagination + if last { + queryStr += "ORDER BY (batch_num, position) DESC NULLS FIRST " + } else { + queryStr += "ORDER BY (batch_num, position) ASC NULLS LAST " + queryStr += fmt.Sprintf("OFFSET %d ", *offset) + } + queryStr += fmt.Sprintf("LIMIT %d ", *limit) + query = hdb.db.Rebind(queryStr) + // log.Debug(query) + txs := []*HistoryTx{} + if err := meddler.QueryAll(hdb.db, &txs, query, args...); err != nil { + return nil, 0, err + } + if len(txs) == 0 { + return nil, 0, sql.ErrNoRows + } else if last { + tmp := []*HistoryTx{} + for i := len(txs) - 1; i >= 0; i-- { + tmp = append(tmp, txs[i]) + } + txs = tmp + } + return txs, txs[0].TotalItems, nil +} + // GetTx returns a tx from the DB func (hdb *HistoryDB) GetTx(txID common.TxID) (*common.Tx, error) { tx := new(common.Tx) diff --git a/db/historydb/historydb_test.go b/db/historydb/historydb_test.go index 878cc2c..5222af7 100644 --- a/db/historydb/historydb_test.go +++ b/db/historydb/historydb_test.go @@ -2,7 +2,6 @@ package historydb import ( "fmt" - "math" "math/big" "os" "testing" @@ -186,7 +185,7 @@ func TestAccounts(t *testing.T) { assert.NoError(t, err) // Generate fake accounts const nAccounts = 3 - accs := test.GenAccounts(nAccounts, 0, tokens, nil, batches) + accs := test.GenAccounts(nAccounts, 0, tokens, nil, nil, batches) err = historyDB.AddAccounts(accs) assert.NoError(t, err) // Fetch accounts @@ -219,7 +218,7 @@ func TestTxs(t *testing.T) { assert.NoError(t, err) // Generate fake accounts const nAccounts = 3 - accs := test.GenAccounts(nAccounts, 0, tokens, nil, batches) + accs := test.GenAccounts(nAccounts, 0, tokens, nil, nil, batches) err = historyDB.AddAccounts(accs) assert.NoError(t, err) // Generate fake L1 txs @@ -265,15 +264,7 @@ func TestTxs(t *testing.T) { } else { assert.Less(t, 0.999, fetchedTx.USD/tx.USD) } - if tx.Fee == 0 { - tx.FeeUSD = 0 - } else if tx.Fee <= 32 { - tx.FeeUSD = tx.USD * math.Pow(10, -24+(float64(tx.Fee)/2)) - } else if tx.Fee <= 223 { - tx.FeeUSD = tx.USD * math.Pow(10, -8+(0.041666666666667*(float64(tx.Fee)-32))) - } else { - tx.FeeUSD = tx.USD * math.Pow(10, float64(tx.Fee)-224) - } + tx.FeeUSD = tx.USD * tx.Fee.Percentage() if fetchedTx.FeeUSD > tx.FeeUSD { assert.Less(t, 0.999, tx.FeeUSD/fetchedTx.FeeUSD) } else if fetchedTx.FeeUSD < tx.FeeUSD { diff --git a/db/historydb/migrations/001_init.sql b/db/historydb/migrations/001_init.sql index a54d0da..16307e3 100644 --- a/db/historydb/migrations/001_init.sql +++ b/db/historydb/migrations/001_init.sql @@ -5,7 +5,7 @@ CREATE TABLE block ( hash BYTEA NOT NULL ); -CREATE TABLE coordianator ( +CREATE TABLE coordinator ( forger_addr BYTEA NOT NULL, eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE, withdraw_addr BYTEA NOT NULL, @@ -99,6 +99,8 @@ CREATE TABLE tx ( nonce BIGINT ); +CREATE INDEX tx_order ON tx (batch_num, position); + -- +migrate StatementBegin CREATE FUNCTION set_tx() RETURNS TRIGGER @@ -197,11 +199,10 @@ CREATE TABLE consensus_vars ( DROP TABLE consensus_vars; DROP TABLE rollup_vars; DROP TABLE account; -DROP TABLE l2tx; -DROP TABLE l1tx; +DROP TABLE tx; DROP TABLE token; DROP TABLE bid; DROP TABLE exit_tree; DROP TABLE batch; -DROP TABLE coordianator; +DROP TABLE coordinator; DROP TABLE block; \ No newline at end of file diff --git a/db/historydb/views.go b/db/historydb/views.go new file mode 100644 index 0000000..f0b824c --- /dev/null +++ b/db/historydb/views.go @@ -0,0 +1,46 @@ +package historydb + +import ( + "math/big" + "time" + + ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/hermeznetwork/hermez-node/common" + "github.com/iden3/go-iden3-crypto/babyjub" +) + +// HistoryTx is a representation of a generic Tx with additional information +// required by the API, and extracted by joining block and token tables +type HistoryTx struct { + // Generic + IsL1 bool `meddler:"is_l1"` + TxID common.TxID `meddler:"id"` + Type common.TxType `meddler:"type"` + Position int `meddler:"position"` + FromIdx common.Idx `meddler:"from_idx"` + ToIdx common.Idx `meddler:"to_idx"` + Amount *big.Int `meddler:"amount,bigint"` + AmountFloat float64 `meddler:"amount_f"` + TokenID common.TokenID `meddler:"token_id"` + USD float64 `meddler:"amount_usd,zeroisnull"` + BatchNum common.BatchNum `meddler:"batch_num,zeroisnull"` // batchNum in which this tx was forged. If the tx is L2, this must be != 0 + EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum Block Number in which this L1Tx was added to the queue + // L1 + ToForgeL1TxsNum int64 `meddler:"to_forge_l1_txs_num"` // toForgeL1TxsNum in which the tx was forged / will be forged + UserOrigin bool `meddler:"user_origin"` // true if the tx was originated by a user, false if it was aoriginated by a coordinator. Note that this differ from the spec for implementation simplification purpposes + FromEthAddr ethCommon.Address `meddler:"from_eth_addr"` + FromBJJ *babyjub.PublicKey `meddler:"from_bjj"` + LoadAmount *big.Int `meddler:"load_amount,bigintnull"` + LoadAmountFloat float64 `meddler:"load_amount_f"` + LoadAmountUSD float64 `meddler:"load_amount_usd,zeroisnull"` + // L2 + Fee common.FeeSelector `meddler:"fee,zeroisnull"` + FeeUSD float64 `meddler:"fee_usd,zeroisnull"` + Nonce common.Nonce `meddler:"nonce,zeroisnull"` + // API extras + Timestamp time.Time `meddler:"timestamp,utctime"` + TokenSymbol string `meddler:"symbol"` + CurrentUSD float64 `meddler:"current_usd"` + USDUpdate time.Time `meddler:"usd_update,utctime"` + TotalItems int `meddler:"total_items"` +} diff --git a/db/l2db/l2db.go b/db/l2db/l2db.go index 7d0d5f5..b6dc985 100644 --- a/db/l2db/l2db.go +++ b/db/l2db/l2db.go @@ -8,6 +8,7 @@ import ( "github.com/gobuffalo/packr/v2" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db" + "github.com/hermeznetwork/hermez-node/log" "github.com/jmoiron/sqlx" //nolint:errcheck // driver for postgres DB @@ -48,11 +49,13 @@ func NewL2DB( // Run DB migrations migrations := &migrate.PackrMigrationSource{ - Box: packr.New("history-migrations", "./migrations"), + Box: packr.New("l2db-migrations", "./migrations"), } - if _, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up); err != nil { + nMigrations, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up) + if err != nil { return nil, err } + log.Debug("L2DB applied ", nMigrations, " migrations for ", dbname, " database") return &L2DB{ db: db, diff --git a/db/l2db/l2db_test.go b/db/l2db/l2db_test.go index 9df0b41..4b9bd32 100644 --- a/db/l2db/l2db_test.go +++ b/db/l2db/l2db_test.go @@ -21,7 +21,10 @@ func TestMain(m *testing.M) { pass := os.Getenv("POSTGRES_PASS") l2DB, err = NewL2DB(5432, "localhost", "hermez", pass, "l2", 10, 100, 24*time.Hour) if err != nil { + log.Error("L2DB migration failed: " + err.Error()) panic(err) + } else { + log.Debug("L2DB migration succed") } // Run tests result := m.Run() @@ -34,7 +37,7 @@ func TestMain(m *testing.M) { func TestAddTx(t *testing.T) { const nInserts = 20 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) for _, tx := range txs { err := l2DB.AddTx(tx) @@ -52,7 +55,7 @@ func TestAddTx(t *testing.T) { func BenchmarkAddTx(b *testing.B) { const nInserts = 20 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) now := time.Now() for _, tx := range txs { @@ -64,7 +67,7 @@ func BenchmarkAddTx(b *testing.B) { func TestGetPending(t *testing.T) { const nInserts = 20 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) var pendingTxs []*common.PoolL2Tx for _, tx := range txs { @@ -90,7 +93,7 @@ func TestStartForging(t *testing.T) { // Generate txs const nInserts = 60 const fakeBatchNum common.BatchNum = 33 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) var startForgingTxIDs []common.TxID randomizer := 0 @@ -119,7 +122,7 @@ func TestDoneForging(t *testing.T) { // Generate txs const nInserts = 60 const fakeBatchNum common.BatchNum = 33 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) var doneForgingTxIDs []common.TxID randomizer := 0 @@ -148,7 +151,7 @@ func TestInvalidate(t *testing.T) { // Generate txs const nInserts = 60 const fakeBatchNum common.BatchNum = 33 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) var invalidTxIDs []common.TxID randomizer := 0 @@ -177,7 +180,7 @@ func TestCheckNonces(t *testing.T) { // Generate txs const nInserts = 60 const fakeBatchNum common.BatchNum = 33 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) var invalidTxIDs []common.TxID // Generate accounts @@ -219,7 +222,7 @@ func TestCheckNonces(t *testing.T) { func TestUpdateTxValue(t *testing.T) { // Generate txs const nInserts = 255 // Force all possible fee selector values - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) // Generate tokens const nTokens = 2 @@ -277,7 +280,7 @@ func TestReorg(t *testing.T) { const nInserts = 20 const lastValidBatch common.BatchNum = 20 const reorgBatch common.BatchNum = lastValidBatch + 1 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(nInserts) // Add txs to the DB reorgedTxIDs := []common.TxID{} @@ -312,7 +315,7 @@ func TestReorg(t *testing.T) { func TestPurge(t *testing.T) { // Generate txs nInserts := l2DB.maxTxs + 20 - cleanDB() + test.CleanL2DB(l2DB.DB()) txs := test.GenPoolTxs(int(nInserts)) deletedIDs := []common.TxID{} keepedIDs := []common.TxID{} @@ -360,7 +363,7 @@ func TestPurge(t *testing.T) { } func TestAuth(t *testing.T) { - cleanDB() + test.CleanL2DB(l2DB.DB()) const nAuths = 5 // Generate authorizations auths := test.GenAuths(nAuths) @@ -378,12 +381,3 @@ func TestAuth(t *testing.T) { assert.Equal(t, auths[i].Timestamp.Unix(), auths[i].Timestamp.Unix()) } } - -func cleanDB() { - if _, err := l2DB.db.Exec("DELETE FROM tx_pool"); err != nil { - panic(err) - } - if _, err := l2DB.db.Exec("DELETE FROM account_creation_auth"); err != nil { - panic(err) - } -} diff --git a/go.mod b/go.mod index 8e34fbc..43103a4 100644 --- a/go.mod +++ b/go.mod @@ -6,19 +6,20 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/dghubble/sling v1.3.0 github.com/ethereum/go-ethereum v1.9.17 + github.com/getkin/kin-openapi v0.22.0 + github.com/gin-gonic/gin v1.4.0 github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gobuffalo/packr/v2 v2.8.0 - github.com/iden3/go-iden3-core v0.0.8 github.com/iden3/go-iden3-crypto v0.0.6-0.20200823174058-e04ca5764a15 github.com/iden3/go-merkletree v0.0.0-20200902123354-eeb949f8c334 - github.com/jinzhu/gorm v1.9.15 + github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jmoiron/sqlx v1.2.0 github.com/lib/pq v1.8.0 github.com/mitchellh/copystructure v1.0.0 github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 github.com/russross/meddler v1.0.0 github.com/stretchr/testify v1.6.1 - github.com/urfave/cli v1.22.1 + github.com/ugorji/go v1.1.8 // indirect github.com/urfave/cli/v2 v2.2.0 go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.16.0 diff --git a/go.sum b/go.sum index fa8099f..a7dcd7b 100644 --- a/go.sum +++ b/go.sum @@ -20,9 +20,9 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= @@ -36,8 +36,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -76,6 +76,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA= github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -119,7 +120,6 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vs github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU= github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -142,7 +142,6 @@ github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3 github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/ethereum/go-ethereum v1.9.12/go.mod h1:PvsVkQmhZFx92Y+h2ylythYlheEDt/uBgFbl61Js/jo= github.com/ethereum/go-ethereum v1.9.13/go.mod h1:qwN9d1GLyDh0N7Ab8bMGd0H9knaji2jOBm2RrMGjXls= github.com/ethereum/go-ethereum v1.9.17 h1:2D02O8KcoyQHxfizvMi0vGXXzFIkQTMeKXwt0+4SYEA= @@ -155,23 +154,32 @@ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getkin/kin-openapi v0.22.0 h1:J5IFyKd/5yuB6AZAgwK0CMBKnabWcmkowtsl6bRkz4s= +github.com/getkin/kin-openapi v0.22.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= @@ -274,6 +282,7 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/holiman/uint256 v1.1.0 h1:Iye6ze0DW9s+7EMn8y6Q4ebegDzpu28JQHEVM1Bq+Wg= github.com/holiman/uint256 v1.1.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v0.0.0-20161224104101-679507af18f3/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag= @@ -287,34 +296,10 @@ github.com/iden3/go-iden3-core v0.0.8 h1:PLw7iCiX7Pw1dqBkR+JaLQWqB5RKd+vgu25UBdv github.com/iden3/go-iden3-core v0.0.8/go.mod h1:URNjIhMql6sEbWubIGrjJdw5wHCE1Pk1XghxjBOtA3s= github.com/iden3/go-iden3-crypto v0.0.5 h1:inCSm5a+ry+nbpVTL/9+m6UcIwSv6nhUm0tnIxEbcps= github.com/iden3/go-iden3-crypto v0.0.5/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf h1:/7L5dEqctuzJY2g8OEQct+1Y+n2sMKyd4JoYhw2jy1s= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200723082457-29a66457f0bf/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb h1:4Vqq5ZoqQd9t3Uj7MUbak7eHmtaYs8mqQoo8T1DS7tA= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200806115047-327a8175d6eb/go.mod h1:XKw1oDwYn2CIxKOtr7m/mL5jMn4mLOxAxtZBRxQBev8= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200813145745-66519124ca17 h1:aqy++85MX/ylQyKkuoK7zJ8hHEW5mT4mLL6pguo+X8Y= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200813145745-66519124ca17/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8 h1:1QeVvr/DjiBpk8tw3vBfYD+zs0JeYl3fPIUA/foDq3w= -github.com/iden3/go-iden3-crypto v0.0.6-0.20200814110325-70841d78e7d8/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= github.com/iden3/go-iden3-crypto v0.0.6-0.20200819064831-09d161e9f670 h1:gNBFu/WnRfNn+xywE04fgCWSHlb6wr0nIIll9i4R2fc= github.com/iden3/go-iden3-crypto v0.0.6-0.20200819064831-09d161e9f670/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= github.com/iden3/go-iden3-crypto v0.0.6-0.20200823174058-e04ca5764a15 h1:hzCf9JY0vKUB9wVq+oXzqcei20xDtBu8XEIQIjgOowE= github.com/iden3/go-iden3-crypto v0.0.6-0.20200823174058-e04ca5764a15/go.mod h1:oBgthFLboAWi9feaBUFy7OxEcyn9vA1khHSL/WwWFyg= -github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3 h1:QR6LqG1HqqPE4myiLR73QFIieAfwODG3bqo1juuaqVI= -github.com/iden3/go-merkletree v0.0.0-20200723202738-75e24244a1e3/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME= -github.com/iden3/go-merkletree v0.0.0-20200806171216-dd600560e44c h1:EzVMSVkwKdfcOR1a+rZe9dfbtAj6KdJnBxOEZ5B+gCQ= -github.com/iden3/go-merkletree v0.0.0-20200806171216-dd600560e44c/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME= -github.com/iden3/go-merkletree v0.0.0-20200807083900-f6f82d8375d5 h1:qvWSCt3AYxj65uTdW6lLSKlrbckcHghOAW4TwdfJ+N8= -github.com/iden3/go-merkletree v0.0.0-20200807083900-f6f82d8375d5/go.mod h1:Fc49UeywIsj8nUfb5lxBzmWrMeMmqzTJ5F0OcjdiEME= -github.com/iden3/go-merkletree v0.0.0-20200815105542-2277604e65dd h1:AkPlODLWkgQT9p1k6LnO3aRLIIeVL9ENof/YW87QL14= -github.com/iden3/go-merkletree v0.0.0-20200815105542-2277604e65dd/go.mod h1:/MsQOzDnxK8X/u7XP9ZBoZwZ4gIm9FlwfqckH5dRuTM= -github.com/iden3/go-merkletree v0.0.0-20200815144208-1f1bd54b93ae h1:CC8oKPM+38/Dkq20QymuIjyRo0UFzJZeH5DPbGWURrI= -github.com/iden3/go-merkletree v0.0.0-20200815144208-1f1bd54b93ae/go.mod h1:/MsQOzDnxK8X/u7XP9ZBoZwZ4gIm9FlwfqckH5dRuTM= -github.com/iden3/go-merkletree v0.0.0-20200819075941-77df3096f7ea h1:0MtMnN42ULNxOnJYa0FH1qjeSEBPkYVH6ZCs22rz2FU= -github.com/iden3/go-merkletree v0.0.0-20200819075941-77df3096f7ea/go.mod h1:MRe6i0mi2oDVUzgBIHsNRE6XAg8EBuqIQZMsd+do+dU= -github.com/iden3/go-merkletree v0.0.0-20200819092443-dc656fdd32fc h1:VnRP7JCp5TJHnTC+r8NrXGkRBvp62tRGqeA3FcdEq+Q= -github.com/iden3/go-merkletree v0.0.0-20200819092443-dc656fdd32fc/go.mod h1:MRe6i0mi2oDVUzgBIHsNRE6XAg8EBuqIQZMsd+do+dU= -github.com/iden3/go-merkletree v0.0.0-20200825093552-a4b68208bb41 h1:mCOMMQ/YmL9ST9kk7ifT961chESkB2GFFEp8osF0Jw8= -github.com/iden3/go-merkletree v0.0.0-20200825093552-a4b68208bb41/go.mod h1:MRe6i0mi2oDVUzgBIHsNRE6XAg8EBuqIQZMsd+do+dU= github.com/iden3/go-merkletree v0.0.0-20200902123354-eeb949f8c334 h1:FQngDJKiwM6i4kHlVFvSpJa9sO+QvZ7C+GqoPWe+5BI= github.com/iden3/go-merkletree v0.0.0-20200902123354-eeb949f8c334/go.mod h1:MRe6i0mi2oDVUzgBIHsNRE6XAg8EBuqIQZMsd+do+dU= github.com/iden3/go-wasm3 v0.0.1/go.mod h1:j+TcAB94Dfrjlu5kJt83h2OqAU+oyNUTwNZnQyII1sI= @@ -328,10 +313,8 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o= github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s= -github.com/jinzhu/gorm v1.9.15/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= @@ -340,6 +323,7 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -367,7 +351,6 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -400,7 +383,6 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -419,8 +401,10 @@ github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -442,7 +426,9 @@ github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1 github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o= github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= @@ -577,12 +563,17 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.8 h1:/D9x7IRpfMHDlizVOgxrag5Fh+/NY+LtI8bsr+AswRA= +github.com/ugorji/go v1.1.8/go.mod h1:0lNM99SwWUIRhCXnigEMClngXBk/EmpTXa7mgiewYWA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.8 h1:4dryPvxMP9OtkjIbuNeK2nb27M38XMHLGlfNSNph/5s= +github.com/ugorji/go/codec v1.1.8/go.mod h1:X00B19HDtwvKbQY2DcYjvZxKQp8mzrJoQ6EgoIY/D2E= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= @@ -631,7 +622,6 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -646,6 +636,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -655,8 +646,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -677,10 +668,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -746,9 +735,11 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41 h1:9Di9iYgOt9ThCipBxChBVhgNipDoE5mxO84rQV7D0FE= golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -775,15 +766,19 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= @@ -793,6 +788,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= @@ -800,13 +796,17 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/test/historydb.go b/test/historydb.go index 7b3e0b5..2c2cd80 100644 --- a/test/historydb.go +++ b/test/historydb.go @@ -79,19 +79,21 @@ func GenBatches(nBatches int, blocks []common.Block) []common.Batch { } // GenAccounts generates accounts. WARNING: This is meant for DB/API testing, and may not be fully consistent with the protocol. -func GenAccounts(totalAccounts, userAccounts int, tokens []common.Token, userAddr *ethCommon.Address, batches []common.Batch) []common.Account { +func GenAccounts(totalAccounts, userAccounts int, tokens []common.Token, userAddr *ethCommon.Address, userBjj *babyjub.PublicKey, batches []common.Batch) []common.Account { if totalAccounts < userAccounts { panic("totalAccounts must be greater than userAccounts") } - privK := babyjub.NewRandPrivKey() - pubK := privK.Public() accs := []common.Account{} for i := 0; i < totalAccounts; i++ { var addr ethCommon.Address + var pubK *babyjub.PublicKey if i < userAccounts { addr = *userAddr + pubK = userBjj } else { addr = ethCommon.BigToAddress(big.NewInt(int64(i))) + privK := babyjub.NewRandPrivKey() + pubK = privK.Public() } accs = append(accs, common.Account{ Idx: common.Idx(i),