mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-06 19:06:42 +01:00
Implement tx pool endpoints
This commit is contained in:
10
api/api.go
10
api/api.go
@@ -12,8 +12,8 @@ import (
|
||||
var h *historydb.HistoryDB
|
||||
var cg *configAPI
|
||||
|
||||
// var s *statedb.StateDB // Not 100% sure if this is needed
|
||||
// var l2 *l2db.L2DB
|
||||
var s *statedb.StateDB
|
||||
var l2 *l2db.L2DB
|
||||
|
||||
// SetAPIEndpoints sets the endpoints and the appropriate handlers, but doesn't start the server
|
||||
func SetAPIEndpoints(
|
||||
@@ -35,8 +35,8 @@ func SetAPIEndpoints(
|
||||
|
||||
h = hdb
|
||||
cg = config
|
||||
// s = sdb
|
||||
// l2 = l2db
|
||||
s = sdb
|
||||
l2 = l2db
|
||||
|
||||
// Add coordinator endpoints
|
||||
if coordinatorEndpoints {
|
||||
@@ -45,7 +45,7 @@ func SetAPIEndpoints(
|
||||
server.GET("/account-creation-authorization/:hermezEthereumAddress", getAccountCreationAuth)
|
||||
// Transaction
|
||||
server.POST("/transactions-pool", postPoolTx)
|
||||
server.POST("/transactions-pool/:id", getPoolTx)
|
||||
server.GET("/transactions-pool/:id", getPoolTx)
|
||||
}
|
||||
|
||||
// Add explorer endpoints
|
||||
|
||||
265
api/api_test.go
265
api/api_test.go
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -37,17 +38,19 @@ const apiPort = ":4010"
|
||||
const apiURL = "http://localhost" + apiPort + "/"
|
||||
|
||||
type testCommon struct {
|
||||
blocks []common.Block
|
||||
tokens []tokenAPI
|
||||
batches []common.Batch
|
||||
usrAddr string
|
||||
usrBjj string
|
||||
accs []common.Account
|
||||
usrTxs []historyTxAPI
|
||||
allTxs []historyTxAPI
|
||||
exits []exitAPI
|
||||
usrExits []exitAPI
|
||||
router *swagger.Router
|
||||
blocks []common.Block
|
||||
tokens []tokenAPI
|
||||
batches []common.Batch
|
||||
usrAddr string
|
||||
usrBjj string
|
||||
accs []common.Account
|
||||
usrTxs []historyTxAPI
|
||||
allTxs []historyTxAPI
|
||||
exits []exitAPI
|
||||
usrExits []exitAPI
|
||||
poolTxsToSend []receivedPoolTx
|
||||
poolTxsToReceive []sendPoolTx
|
||||
router *swagger.Router
|
||||
}
|
||||
|
||||
// TxSortFields represents the fields needed to sort L1 and L2 transactions
|
||||
@@ -281,7 +284,7 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Gen accounts and add them to DB
|
||||
// Gen accounts and add them to HistoryDB and StateDB
|
||||
const totalAccounts = 40
|
||||
const userAccounts = 4
|
||||
usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
|
||||
@@ -292,6 +295,11 @@ func TestMain(m *testing.M) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i := 0; i < len(accs); i++ {
|
||||
if _, err := s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// Gen exits and add them to DB
|
||||
const totalExits = 40
|
||||
exits := test.GenExitTree(totalExits, batches, accs)
|
||||
@@ -387,7 +395,7 @@ func TestMain(m *testing.M) {
|
||||
token := getToken(l1.TokenID)
|
||||
tx := historyTxAPI{
|
||||
IsL1: "L1",
|
||||
TxID: l1.TxID.String(),
|
||||
TxID: l1.TxID,
|
||||
Type: l1.Type,
|
||||
Position: l1.Position,
|
||||
ToIdx: idxToHez(l1.ToIdx, token.Symbol),
|
||||
@@ -444,7 +452,7 @@ func TestMain(m *testing.M) {
|
||||
token := getToken(tokenID)
|
||||
tx := historyTxAPI{
|
||||
IsL1: "L2",
|
||||
TxID: l2.TxID.String(),
|
||||
TxID: l2.TxID,
|
||||
Type: l2.Type,
|
||||
Position: l2.Position,
|
||||
ToIdx: idxToHez(l2.ToIdx, token.Symbol),
|
||||
@@ -511,18 +519,124 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Prepare pool Txs
|
||||
// Generate common.PoolL2Tx
|
||||
// WARNING: this should be replaced once transakcio is ready
|
||||
poolTxs := []common.PoolL2Tx{}
|
||||
amount := new(big.Int)
|
||||
amount, ok := amount.SetString("100000000000000", 10)
|
||||
if !ok {
|
||||
panic("bad amount")
|
||||
}
|
||||
poolTx := common.PoolL2Tx{
|
||||
FromIdx: accs[0].Idx,
|
||||
ToIdx: accs[1].Idx,
|
||||
Amount: amount,
|
||||
TokenID: accs[0].TokenID,
|
||||
Nonce: 6,
|
||||
}
|
||||
if _, err := common.NewPoolL2Tx(&poolTx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
h, err := poolTx.HashToSign()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
poolTx.Signature = privK.SignPoseidon(h).Compress()
|
||||
poolTxs = append(poolTxs, poolTx)
|
||||
// Transform to API formats
|
||||
poolTxsToSend := []receivedPoolTx{}
|
||||
poolTxsToReceive := []sendPoolTx{}
|
||||
for _, poolTx := range poolTxs {
|
||||
// common.PoolL2Tx ==> receivedPoolTx
|
||||
token := getToken(poolTx.TokenID)
|
||||
genSendTx := receivedPoolTx{
|
||||
TxID: poolTx.TxID,
|
||||
Type: poolTx.Type,
|
||||
TokenID: poolTx.TokenID,
|
||||
FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
|
||||
Amount: poolTx.Amount.String(),
|
||||
Fee: poolTx.Fee,
|
||||
Nonce: poolTx.Nonce,
|
||||
Signature: poolTx.Signature,
|
||||
RqFee: &poolTx.RqFee,
|
||||
RqNonce: &poolTx.RqNonce,
|
||||
}
|
||||
// common.PoolL2Tx ==> receivedPoolTx
|
||||
genReceiveTx := sendPoolTx{
|
||||
TxID: poolTx.TxID,
|
||||
Type: poolTx.Type,
|
||||
FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
|
||||
Amount: poolTx.Amount.String(),
|
||||
Fee: poolTx.Fee,
|
||||
Nonce: poolTx.Nonce,
|
||||
State: poolTx.State,
|
||||
Signature: poolTx.Signature,
|
||||
Timestamp: poolTx.Timestamp,
|
||||
// BatchNum: poolTx.BatchNum,
|
||||
RqFee: &poolTx.RqFee,
|
||||
RqNonce: &poolTx.RqNonce,
|
||||
Token: token,
|
||||
}
|
||||
if poolTx.ToIdx != 0 {
|
||||
toIdx := idxToHez(poolTx.ToIdx, token.Symbol)
|
||||
genSendTx.ToIdx = &toIdx
|
||||
genReceiveTx.ToIdx = &toIdx
|
||||
}
|
||||
if poolTx.ToEthAddr != common.EmptyAddr {
|
||||
toEth := ethAddrToHez(poolTx.ToEthAddr)
|
||||
genSendTx.ToEthAddr = &toEth
|
||||
genReceiveTx.ToEthAddr = &toEth
|
||||
}
|
||||
if poolTx.ToBJJ != nil {
|
||||
toBJJ := bjjToString(poolTx.ToBJJ)
|
||||
genSendTx.ToBJJ = &toBJJ
|
||||
genReceiveTx.ToBJJ = &toBJJ
|
||||
}
|
||||
if poolTx.RqFromIdx != 0 {
|
||||
rqFromIdx := idxToHez(poolTx.RqFromIdx, token.Symbol)
|
||||
genSendTx.RqFromIdx = &rqFromIdx
|
||||
genReceiveTx.RqFromIdx = &rqFromIdx
|
||||
genSendTx.RqTokenID = &token.TokenID
|
||||
genReceiveTx.RqTokenID = &token.TokenID
|
||||
rqAmount := poolTx.RqAmount.String()
|
||||
genSendTx.RqAmount = &rqAmount
|
||||
genReceiveTx.RqAmount = &rqAmount
|
||||
|
||||
if poolTx.RqToIdx != 0 {
|
||||
rqToIdx := idxToHez(poolTx.RqToIdx, token.Symbol)
|
||||
genSendTx.RqToIdx = &rqToIdx
|
||||
genReceiveTx.RqToIdx = &rqToIdx
|
||||
}
|
||||
if poolTx.RqToEthAddr != common.EmptyAddr {
|
||||
rqToEth := ethAddrToHez(poolTx.RqToEthAddr)
|
||||
genSendTx.RqToEthAddr = &rqToEth
|
||||
genReceiveTx.RqToEthAddr = &rqToEth
|
||||
}
|
||||
if poolTx.RqToBJJ != nil {
|
||||
rqToBJJ := bjjToString(poolTx.RqToBJJ)
|
||||
genSendTx.RqToBJJ = &rqToBJJ
|
||||
genReceiveTx.RqToBJJ = &rqToBJJ
|
||||
}
|
||||
}
|
||||
poolTxsToSend = append(poolTxsToSend, genSendTx)
|
||||
poolTxsToReceive = append(poolTxsToReceive, genReceiveTx)
|
||||
}
|
||||
// Set testCommon
|
||||
tc = testCommon{
|
||||
blocks: blocks,
|
||||
tokens: tokensUSD,
|
||||
batches: batches,
|
||||
usrAddr: ethAddrToHez(usrAddr),
|
||||
usrBjj: bjjToString(usrBjj),
|
||||
accs: accs,
|
||||
usrTxs: usrTxs,
|
||||
allTxs: allTxs,
|
||||
exits: apiExits,
|
||||
usrExits: usrExits,
|
||||
router: router,
|
||||
blocks: blocks,
|
||||
tokens: tokensUSD,
|
||||
batches: batches,
|
||||
usrAddr: ethAddrToHez(usrAddr),
|
||||
usrBjj: bjjToString(usrBjj),
|
||||
accs: accs,
|
||||
usrTxs: usrTxs,
|
||||
allTxs: allTxs,
|
||||
exits: apiExits,
|
||||
usrExits: usrExits,
|
||||
poolTxsToSend: poolTxsToSend,
|
||||
poolTxsToReceive: poolTxsToReceive,
|
||||
router: router,
|
||||
}
|
||||
// Run tests
|
||||
result := m.Run()
|
||||
@@ -533,6 +647,9 @@ func TestMain(m *testing.M) {
|
||||
if err := database.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(result)
|
||||
}
|
||||
|
||||
@@ -713,7 +830,7 @@ func TestGetHistoryTx(t *testing.T) {
|
||||
fetchedTxs := []historyTxAPI{}
|
||||
for _, tx := range tc.allTxs {
|
||||
fetchedTx := historyTxAPI{}
|
||||
assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID, nil, &fetchedTx))
|
||||
assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx))
|
||||
fetchedTxs = append(fetchedTxs, fetchedTx)
|
||||
}
|
||||
assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
|
||||
@@ -876,7 +993,7 @@ func TestGetExits(t *testing.T) {
|
||||
path = fmt.Sprintf("%s?batchNum=999999", endpoint)
|
||||
err = doBadReq("GET", path, nil, 404)
|
||||
assert.NoError(t, err)
|
||||
path = fmt.Sprintf("%s?limit=1000&fromItem=1000", endpoint)
|
||||
path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
|
||||
err = doBadReq("GET", path, nil, 404)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -1049,6 +1166,90 @@ func TestGetConfig(t *testing.T) {
|
||||
assert.Equal(t, cg, &configTest)
|
||||
}
|
||||
|
||||
func TestPoolTxs(t *testing.T) {
|
||||
// POST
|
||||
endpoint := apiURL + "transactions-pool"
|
||||
fetchedTxID := common.TxID{}
|
||||
for _, tx := range tc.poolTxsToSend {
|
||||
jsonTxBytes, err := json.Marshal(tx)
|
||||
assert.NoError(t, err)
|
||||
jsonTxReader := bytes.NewReader(jsonTxBytes)
|
||||
fmt.Println(string(jsonTxBytes))
|
||||
assert.NoError(
|
||||
t, doGoodReq(
|
||||
"POST",
|
||||
endpoint,
|
||||
jsonTxReader, &fetchedTxID,
|
||||
),
|
||||
)
|
||||
assert.Equal(t, tx.TxID, fetchedTxID)
|
||||
}
|
||||
// 400
|
||||
// Wrong signature
|
||||
badTx := tc.poolTxsToSend[0]
|
||||
badTx.FromIdx = "hez:foo:1000"
|
||||
jsonTxBytes, err := json.Marshal(badTx)
|
||||
assert.NoError(t, err)
|
||||
jsonTxReader := bytes.NewReader(jsonTxBytes)
|
||||
err = doBadReq("POST", endpoint, jsonTxReader, 400)
|
||||
assert.NoError(t, err)
|
||||
// Wrong to
|
||||
badTx = tc.poolTxsToSend[0]
|
||||
ethAddr := "hez:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
||||
badTx.ToEthAddr = ðAddr
|
||||
badTx.ToIdx = nil
|
||||
jsonTxBytes, err = json.Marshal(badTx)
|
||||
assert.NoError(t, err)
|
||||
jsonTxReader = bytes.NewReader(jsonTxBytes)
|
||||
err = doBadReq("POST", endpoint, jsonTxReader, 400)
|
||||
assert.NoError(t, err)
|
||||
// Wrong rq
|
||||
badTx = tc.poolTxsToSend[0]
|
||||
rqFromIdx := "hez:foo:30"
|
||||
badTx.RqFromIdx = &rqFromIdx
|
||||
jsonTxBytes, err = json.Marshal(badTx)
|
||||
assert.NoError(t, err)
|
||||
jsonTxReader = bytes.NewReader(jsonTxBytes)
|
||||
err = doBadReq("POST", endpoint, jsonTxReader, 400)
|
||||
assert.NoError(t, err)
|
||||
// GET
|
||||
endpoint += "/"
|
||||
for _, tx := range tc.poolTxsToReceive {
|
||||
fetchedTx := sendPoolTx{}
|
||||
assert.NoError(
|
||||
t, doGoodReq(
|
||||
"GET",
|
||||
endpoint+tx.TxID.String(),
|
||||
nil, &fetchedTx,
|
||||
),
|
||||
)
|
||||
assertPoolTx(t, tx, fetchedTx)
|
||||
}
|
||||
// 400
|
||||
err = doBadReq("GET", endpoint+"0xG20000000156660000000090", nil, 400)
|
||||
assert.NoError(t, err)
|
||||
// 404
|
||||
err = doBadReq("GET", endpoint+"0x020000000156660000000090", nil, 404)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func assertPoolTx(t *testing.T, expected, actual sendPoolTx) {
|
||||
// state should be pending
|
||||
assert.Equal(t, common.PoolL2TxStatePending, actual.State)
|
||||
expected.State = actual.State
|
||||
// timestamp should be very close to now
|
||||
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
||||
expected.Timestamp = actual.Timestamp
|
||||
// token timestamp
|
||||
if expected.Token.USDUpdate == nil {
|
||||
assert.Equal(t, expected.Token.USDUpdate, actual.Token.USDUpdate)
|
||||
} else {
|
||||
assert.Equal(t, expected.Token.USDUpdate.Unix(), actual.Token.USDUpdate.Unix())
|
||||
expected.Token.USDUpdate = actual.Token.USDUpdate
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func doGoodReqPaginated(
|
||||
path, order string,
|
||||
iterStruct db.Paginationer,
|
||||
@@ -1091,7 +1292,13 @@ func doGoodReqPaginated(
|
||||
func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
|
||||
ctx := context.Background()
|
||||
client := &http.Client{}
|
||||
httpReq, _ := http.NewRequest(method, path, reqBody)
|
||||
httpReq, err := http.NewRequest(method, path, reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reqBody != nil {
|
||||
httpReq.Header.Add("Content-Type", "application/json")
|
||||
}
|
||||
route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1171,7 +1378,7 @@ func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int)
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != expectedResponseCode {
|
||||
return fmt.Errorf("Unexpected response code: %d", resp.StatusCode)
|
||||
return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
// Validate response against swagger spec
|
||||
responseValidationInput := &swagger.ResponseValidationInput{
|
||||
|
||||
@@ -2,6 +2,8 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -10,11 +12,14 @@ import (
|
||||
"github.com/hermeznetwork/hermez-node/common"
|
||||
"github.com/hermeznetwork/hermez-node/db"
|
||||
"github.com/hermeznetwork/hermez-node/db/historydb"
|
||||
"github.com/hermeznetwork/hermez-node/db/l2db"
|
||||
"github.com/hermeznetwork/hermez-node/eth"
|
||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||
"github.com/iden3/go-merkletree"
|
||||
)
|
||||
|
||||
const exitIdx = "hez:EXIT:1"
|
||||
|
||||
type errorMsg struct {
|
||||
Message string
|
||||
}
|
||||
@@ -34,6 +39,9 @@ func ethAddrToHez(addr ethCommon.Address) string {
|
||||
}
|
||||
|
||||
func idxToHez(idx common.Idx, tokenSymbol string) string {
|
||||
if idx == 1 {
|
||||
return exitIdx
|
||||
}
|
||||
return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx))
|
||||
}
|
||||
|
||||
@@ -74,7 +82,7 @@ type l2Info struct {
|
||||
|
||||
type historyTxAPI struct {
|
||||
IsL1 string `json:"L1orL2"`
|
||||
TxID string `json:"id"`
|
||||
TxID common.TxID `json:"id"`
|
||||
ItemID int `json:"itemId"`
|
||||
Type common.TxType `json:"type"`
|
||||
Position int `json:"position"`
|
||||
@@ -93,7 +101,7 @@ func historyTxsToAPI(dbTxs []historydb.HistoryTx) []historyTxAPI {
|
||||
apiTxs := []historyTxAPI{}
|
||||
for i := 0; i < len(dbTxs); i++ {
|
||||
apiTx := historyTxAPI{
|
||||
TxID: dbTxs[i].TxID.String(),
|
||||
TxID: dbTxs[i].TxID,
|
||||
ItemID: dbTxs[i].ItemID,
|
||||
Type: dbTxs[i].Type,
|
||||
Position: dbTxs[i].Position,
|
||||
@@ -276,3 +284,319 @@ type configAPI struct {
|
||||
AuctionConstants eth.AuctionConstants `json:"auction"`
|
||||
WDelayerConstants eth.WDelayerConstants `json:"withdrawalDelayer"`
|
||||
}
|
||||
|
||||
// PoolL2Tx
|
||||
|
||||
type receivedPoolTx struct {
|
||||
TxID common.TxID `json:"id" binding:"required"`
|
||||
Type common.TxType `json:"type" binding:"required"`
|
||||
TokenID common.TokenID `json:"tokenId"`
|
||||
FromIdx string `json:"fromAccountIndex" binding:"required"`
|
||||
ToIdx *string `json:"toAccountIndex"`
|
||||
ToEthAddr *string `json:"toHezEthereumAddress"`
|
||||
ToBJJ *string `json:"toBjj"`
|
||||
Amount string `json:"amount" binding:"required"`
|
||||
Fee common.FeeSelector `json:"fee"`
|
||||
Nonce common.Nonce `json:"nonce"`
|
||||
Signature babyjub.SignatureComp `json:"signature" binding:"required"`
|
||||
RqFromIdx *string `json:"requestFromAccountIndex"`
|
||||
RqToIdx *string `json:"requestToAccountIndex"`
|
||||
RqToEthAddr *string `json:"requestToHezEthereumAddress"`
|
||||
RqToBJJ *string `json:"requestToBjj"`
|
||||
RqTokenID *common.TokenID `json:"requestTokenId"`
|
||||
RqAmount *string `json:"requestAmount"`
|
||||
RqFee *common.FeeSelector `json:"requestFee"`
|
||||
RqNonce *common.Nonce `json:"requestNonce"`
|
||||
}
|
||||
|
||||
func (tx *receivedPoolTx) toDBWritePoolL2Tx() (*l2db.PoolL2TxWrite, error) {
|
||||
amount := new(big.Int)
|
||||
amount.SetString(tx.Amount, 10)
|
||||
txw := &l2db.PoolL2TxWrite{
|
||||
TxID: tx.TxID,
|
||||
TokenID: tx.TokenID,
|
||||
Amount: amount,
|
||||
Fee: tx.Fee,
|
||||
Nonce: tx.Nonce,
|
||||
State: common.PoolL2TxStatePending,
|
||||
Signature: tx.Signature,
|
||||
RqTokenID: tx.RqTokenID,
|
||||
RqFee: tx.RqFee,
|
||||
RqNonce: tx.RqNonce,
|
||||
Type: tx.Type,
|
||||
}
|
||||
// Check FromIdx (required)
|
||||
fidx, err := stringToIdx(tx.FromIdx, "fromAccountIndex")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fidx == nil {
|
||||
return nil, errors.New("invalid fromAccountIndex")
|
||||
}
|
||||
// Set FromIdx
|
||||
txw.FromIdx = common.Idx(*fidx)
|
||||
// Set AmountFloat
|
||||
f := new(big.Float).SetInt(amount)
|
||||
amountF, _ := f.Float64()
|
||||
txw.AmountFloat = amountF
|
||||
if amountF < 0 {
|
||||
return nil, errors.New("amount must be positive")
|
||||
}
|
||||
// Check "to" fields, only one of: ToIdx, ToEthAddr, ToBJJ
|
||||
if tx.ToIdx != nil { // Case: Tx with ToIdx setted
|
||||
// Set ToIdx
|
||||
tidxUint, err := stringToIdx(*tx.ToIdx, "toAccountIndex")
|
||||
if err != nil || tidxUint == nil {
|
||||
return nil, errors.New("invalid toAccountIndex")
|
||||
}
|
||||
tidx := common.Idx(*tidxUint)
|
||||
txw.ToIdx = &tidx
|
||||
} else if tx.ToBJJ != nil { // Case: Tx with ToBJJ setted
|
||||
// tx.ToEthAddr must be equal to ethAddrWhenBJJLower or ethAddrWhenBJJUpper
|
||||
if tx.ToEthAddr != nil {
|
||||
toEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "toHezEthereumAddress")
|
||||
if err != nil || *toEthAddr != common.FFAddr {
|
||||
return nil, fmt.Errorf("if toBjj is setted, toHezEthereumAddress must be hez:%s", common.FFAddr.Hex())
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("if toBjj is setted, toHezEthereumAddress must be hez:%s and toAccountIndex must be null", common.FFAddr.Hex())
|
||||
}
|
||||
// Set ToEthAddr and ToBJJ
|
||||
toBJJ, err := hezStringToBJJ(*tx.ToBJJ, "toBjj")
|
||||
if err != nil || toBJJ == nil {
|
||||
return nil, errors.New("invalid toBjj")
|
||||
}
|
||||
txw.ToBJJ = toBJJ
|
||||
txw.ToEthAddr = &common.FFAddr
|
||||
} else if tx.ToEthAddr != nil { // Case: Tx with ToEthAddr setted
|
||||
// Set ToEthAddr
|
||||
toEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "toHezEthereumAddress")
|
||||
if err != nil || toEthAddr == nil {
|
||||
return nil, errors.New("invalid toHezEthereumAddress")
|
||||
}
|
||||
txw.ToEthAddr = toEthAddr
|
||||
} else {
|
||||
return nil, errors.New("one of toAccountIndex, toHezEthereumAddress or toBjj must be setted")
|
||||
}
|
||||
// Check "rq" fields
|
||||
if tx.RqFromIdx != nil {
|
||||
// check and set RqFromIdx
|
||||
rqfidxUint, err := stringToIdx(tx.FromIdx, "requestFromAccountIndex")
|
||||
if err != nil || rqfidxUint == nil {
|
||||
return nil, errors.New("invalid requestFromAccountIndex")
|
||||
}
|
||||
// Set RqFromIdx
|
||||
rqfidx := common.Idx(*rqfidxUint)
|
||||
txw.RqFromIdx = &rqfidx
|
||||
// Case: RqTx with RqToIdx setted
|
||||
if tx.RqToIdx != nil {
|
||||
// Set ToIdx
|
||||
tidxUint, err := stringToIdx(*tx.RqToIdx, "requestToAccountIndex")
|
||||
if err != nil || tidxUint == nil {
|
||||
return nil, errors.New("invalid requestToAccountIndex")
|
||||
}
|
||||
tidx := common.Idx(*tidxUint)
|
||||
txw.ToIdx = &tidx
|
||||
} else if tx.RqToBJJ != nil { // Case: Tx with ToBJJ setted
|
||||
// tx.ToEthAddr must be equal to ethAddrWhenBJJLower or ethAddrWhenBJJUpper
|
||||
if tx.RqToEthAddr != nil {
|
||||
rqEthAddr, err := hezStringToEthAddr(*tx.RqToEthAddr, "")
|
||||
if err != nil || *rqEthAddr != common.FFAddr {
|
||||
return nil, fmt.Errorf("if requestToBjj is setted, requestToHezEthereumAddress must be hez:%s", common.FFAddr.Hex())
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("if requestToBjj is setted, toHezEthereumAddress must be hez:%s and requestToAccountIndex must be null", common.FFAddr.Hex())
|
||||
}
|
||||
// Set ToEthAddr and ToBJJ
|
||||
rqToBJJ, err := hezStringToBJJ(*tx.RqToBJJ, "requestToBjj")
|
||||
if err != nil || rqToBJJ == nil {
|
||||
return nil, errors.New("invalid requestToBjj")
|
||||
}
|
||||
txw.RqToBJJ = rqToBJJ
|
||||
txw.RqToEthAddr = &common.FFAddr
|
||||
} else if tx.RqToEthAddr != nil { // Case: Tx with ToEthAddr setted
|
||||
// Set ToEthAddr
|
||||
rqToEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "requestToHezEthereumAddress")
|
||||
if err != nil || rqToEthAddr == nil {
|
||||
return nil, errors.New("invalid requestToHezEthereumAddress")
|
||||
}
|
||||
txw.RqToEthAddr = rqToEthAddr
|
||||
} else {
|
||||
return nil, errors.New("one of requestToAccountIndex, requestToHezEthereumAddress or requestToBjj must be setted")
|
||||
}
|
||||
if tx.RqAmount == nil {
|
||||
return nil, errors.New("requestAmount must be provided if other request fields are setted")
|
||||
}
|
||||
rqAmount := new(big.Int)
|
||||
rqAmount.SetString(*tx.RqAmount, 10)
|
||||
txw.RqAmount = rqAmount
|
||||
} else if tx.RqToIdx != nil && tx.RqToEthAddr != nil && tx.RqToBJJ != nil &&
|
||||
tx.RqTokenID != nil && tx.RqAmount != nil && tx.RqNonce != nil && tx.RqFee != nil {
|
||||
// if tx.RqToIdx is not setted, tx.Rq* must be null as well
|
||||
return nil, errors.New("if requestFromAccountIndex is setted, the rest of request fields must be null as well")
|
||||
}
|
||||
|
||||
return txw, validatePoolL2TxWrite(txw)
|
||||
}
|
||||
|
||||
func validatePoolL2TxWrite(txw *l2db.PoolL2TxWrite) error {
|
||||
poolTx := common.PoolL2Tx{
|
||||
TxID: txw.TxID,
|
||||
FromIdx: txw.FromIdx,
|
||||
ToBJJ: txw.ToBJJ,
|
||||
TokenID: txw.TokenID,
|
||||
Amount: txw.Amount,
|
||||
Fee: txw.Fee,
|
||||
Nonce: txw.Nonce,
|
||||
State: txw.State,
|
||||
Signature: txw.Signature,
|
||||
RqToBJJ: txw.RqToBJJ,
|
||||
RqAmount: txw.RqAmount,
|
||||
Type: txw.Type,
|
||||
}
|
||||
// ToIdx
|
||||
if txw.ToIdx != nil {
|
||||
poolTx.ToIdx = *txw.ToIdx
|
||||
}
|
||||
// ToEthAddr
|
||||
if txw.ToEthAddr == nil {
|
||||
poolTx.ToEthAddr = common.EmptyAddr
|
||||
} else {
|
||||
poolTx.ToEthAddr = *txw.ToEthAddr
|
||||
}
|
||||
// RqFromIdx
|
||||
if txw.RqFromIdx != nil {
|
||||
poolTx.RqFromIdx = *txw.RqFromIdx
|
||||
}
|
||||
// RqToIdx
|
||||
if txw.RqToIdx != nil {
|
||||
poolTx.RqToIdx = *txw.RqToIdx
|
||||
}
|
||||
// RqToEthAddr
|
||||
if txw.RqToEthAddr == nil {
|
||||
poolTx.RqToEthAddr = common.EmptyAddr
|
||||
} else {
|
||||
poolTx.RqToEthAddr = *txw.RqToEthAddr
|
||||
}
|
||||
// RqTokenID
|
||||
if txw.RqTokenID != nil {
|
||||
poolTx.RqTokenID = *txw.RqTokenID
|
||||
}
|
||||
// RqFee
|
||||
if txw.RqFee != nil {
|
||||
poolTx.RqFee = *txw.RqFee
|
||||
}
|
||||
// RqNonce
|
||||
if txw.RqNonce != nil {
|
||||
poolTx.RqNonce = *txw.RqNonce
|
||||
}
|
||||
// Check type and id
|
||||
_, err := common.NewPoolL2Tx(&poolTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check signature
|
||||
// Get public key
|
||||
account, err := s.GetAccount(poolTx.FromIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !poolTx.VerifySignature(account.PublicKey) {
|
||||
return errors.New("wrong signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sendPoolTx struct {
|
||||
TxID common.TxID `json:"id"`
|
||||
Type common.TxType `json:"type"`
|
||||
FromIdx string `json:"fromAccountIndex"`
|
||||
ToIdx *string `json:"toAccountIndex"`
|
||||
ToEthAddr *string `json:"toHezEthereumAddress"`
|
||||
ToBJJ *string `json:"toBjj"`
|
||||
Amount string `json:"amount"`
|
||||
Fee common.FeeSelector `json:"fee"`
|
||||
Nonce common.Nonce `json:"nonce"`
|
||||
State common.PoolL2TxState `json:"state"`
|
||||
Signature babyjub.SignatureComp `json:"signature"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
BatchNum *common.BatchNum `json:"batchNum"`
|
||||
RqFromIdx *string `json:"requestFromAccountIndex"`
|
||||
RqToIdx *string `json:"requestToAccountIndex"`
|
||||
RqToEthAddr *string `json:"requestToHezEthereumAddress"`
|
||||
RqToBJJ *string `json:"requestToBJJ"`
|
||||
RqTokenID *common.TokenID `json:"requestTokenId"`
|
||||
RqAmount *string `json:"requestAmount"`
|
||||
RqFee *common.FeeSelector `json:"requestFee"`
|
||||
RqNonce *common.Nonce `json:"requestNonce"`
|
||||
Token tokenAPI `json:"token"`
|
||||
}
|
||||
|
||||
func poolL2TxReadToSend(dbTx *l2db.PoolL2TxRead) *sendPoolTx {
|
||||
tx := &sendPoolTx{
|
||||
TxID: dbTx.TxID,
|
||||
Type: dbTx.Type,
|
||||
FromIdx: idxToHez(dbTx.FromIdx, dbTx.TokenSymbol),
|
||||
Amount: dbTx.Amount.String(),
|
||||
Fee: dbTx.Fee,
|
||||
Nonce: dbTx.Nonce,
|
||||
State: dbTx.State,
|
||||
Signature: dbTx.Signature,
|
||||
Timestamp: dbTx.Timestamp,
|
||||
BatchNum: dbTx.BatchNum,
|
||||
RqTokenID: dbTx.RqTokenID,
|
||||
RqFee: dbTx.RqFee,
|
||||
RqNonce: dbTx.RqNonce,
|
||||
Token: tokenAPI{
|
||||
TokenID: dbTx.TokenID,
|
||||
EthBlockNum: dbTx.TokenEthBlockNum,
|
||||
EthAddr: dbTx.TokenEthAddr,
|
||||
Name: dbTx.TokenName,
|
||||
Symbol: dbTx.TokenSymbol,
|
||||
Decimals: dbTx.TokenDecimals,
|
||||
USD: dbTx.TokenUSD,
|
||||
USDUpdate: dbTx.TokenUSDUpdate,
|
||||
},
|
||||
}
|
||||
// ToIdx
|
||||
if dbTx.ToIdx != nil {
|
||||
toIdx := idxToHez(*dbTx.ToIdx, dbTx.TokenSymbol)
|
||||
tx.ToIdx = &toIdx
|
||||
}
|
||||
// ToEthAddr
|
||||
if dbTx.ToEthAddr != nil {
|
||||
toEth := ethAddrToHez(*dbTx.ToEthAddr)
|
||||
tx.ToEthAddr = &toEth
|
||||
}
|
||||
// ToBJJ
|
||||
if dbTx.ToBJJ != nil {
|
||||
toBJJ := bjjToString(dbTx.ToBJJ)
|
||||
tx.ToBJJ = &toBJJ
|
||||
}
|
||||
// RqFromIdx
|
||||
if dbTx.RqFromIdx != nil {
|
||||
rqFromIdx := idxToHez(*dbTx.RqFromIdx, dbTx.TokenSymbol)
|
||||
tx.RqFromIdx = &rqFromIdx
|
||||
}
|
||||
// RqToIdx
|
||||
if dbTx.RqToIdx != nil {
|
||||
rqToIdx := idxToHez(*dbTx.RqToIdx, dbTx.TokenSymbol)
|
||||
tx.RqToIdx = &rqToIdx
|
||||
}
|
||||
// RqToEthAddr
|
||||
if dbTx.RqToEthAddr != nil {
|
||||
rqToEth := ethAddrToHez(*dbTx.RqToEthAddr)
|
||||
tx.RqToEthAddr = &rqToEth
|
||||
}
|
||||
// RqToBJJ
|
||||
if dbTx.RqToBJJ != nil {
|
||||
rqToBJJ := bjjToString(dbTx.RqToBJJ)
|
||||
tx.RqToBJJ = &rqToBJJ
|
||||
}
|
||||
// RqAmount
|
||||
if dbTx.RqAmount != nil {
|
||||
rqAmount := dbTx.RqAmount.String()
|
||||
tx.RqAmount = &rqAmount
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
@@ -28,15 +28,46 @@ func postAccountCreationAuth(c *gin.Context) {
|
||||
}
|
||||
|
||||
func getAccountCreationAuth(c *gin.Context) {
|
||||
|
||||
}
|
||||
|
||||
func postPoolTx(c *gin.Context) {
|
||||
|
||||
// Parse body
|
||||
var receivedTx receivedPoolTx
|
||||
if err := c.ShouldBindJSON(&receivedTx); err != nil {
|
||||
retBadReq(err, c)
|
||||
return
|
||||
}
|
||||
// Transform from received to insert format and validate
|
||||
writeTx, err := receivedTx.toDBWritePoolL2Tx()
|
||||
if err != nil {
|
||||
retBadReq(err, c)
|
||||
return
|
||||
}
|
||||
// Insert to DB
|
||||
if err := l2.AddTx(writeTx); err != nil {
|
||||
retSQLErr(err, c)
|
||||
return
|
||||
}
|
||||
// Return TxID
|
||||
c.JSON(http.StatusOK, writeTx.TxID.String())
|
||||
}
|
||||
|
||||
func getPoolTx(c *gin.Context) {
|
||||
|
||||
// Get TxID
|
||||
txID, err := parseParamTxID(c)
|
||||
if err != nil {
|
||||
retBadReq(err, c)
|
||||
return
|
||||
}
|
||||
// Fetch tx from l2DB
|
||||
dbTx, err := l2.GetTx(txID)
|
||||
if err != nil {
|
||||
retSQLErr(err, c)
|
||||
return
|
||||
}
|
||||
apiTx := poolL2TxReadToSend(dbTx)
|
||||
// Build succesfull response
|
||||
c.JSON(http.StatusOK, apiTx)
|
||||
}
|
||||
|
||||
func getAccounts(c *gin.Context) {
|
||||
|
||||
102
api/parsers.go
102
api/parsers.go
@@ -75,61 +75,16 @@ func parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolin
|
||||
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
|
||||
return hezStringToEthAddr(addrStr, name)
|
||||
}
|
||||
|
||||
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
|
||||
return hezStringToBJJ(bjjStr, name)
|
||||
}
|
||||
|
||||
func parseQueryTxType(c querier) (*common.TxType, error) {
|
||||
@@ -302,3 +257,56 @@ func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error
|
||||
}
|
||||
return dflt, nil
|
||||
}
|
||||
|
||||
func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
|
||||
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 hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
|
||||
const decodedLen = 33
|
||||
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
|
||||
}
|
||||
|
||||
217
api/swagger.yml
217
api/swagger.yml
@@ -340,7 +340,7 @@ paths:
|
||||
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.
|
||||
description: Get exit information form a specific exit tree and account. This information is required to perform a withdraw. Exits are identified with accounIndex and batchNum since every batch that has exits has a different exit tree.
|
||||
operationId: getExit
|
||||
parameters:
|
||||
- name: batchNum
|
||||
@@ -614,6 +614,12 @@ paths:
|
||||
description: Include only `batchNum > maxBatchNum` batches.
|
||||
schema:
|
||||
type: number
|
||||
- name: slotNum
|
||||
in: query
|
||||
required: false
|
||||
description: Include only batches that were forged within the specified slot.
|
||||
schema:
|
||||
$ref: '#/components/schemas/SlotNum'
|
||||
- name: forgerAddr
|
||||
in: query
|
||||
required: false
|
||||
@@ -941,40 +947,12 @@ paths:
|
||||
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.
|
||||
summary: Return information that represents the current state of the network.
|
||||
description: Return information that represents the current state of the network. It also includes metrics and statistics.
|
||||
operationId: getState
|
||||
responses:
|
||||
'200':
|
||||
@@ -1133,27 +1111,6 @@ paths:
|
||||
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:
|
||||
@@ -1251,6 +1208,7 @@ components:
|
||||
description: Position of the item in the DB. This is useful for pagination, but has nothing to do with the protocol.
|
||||
PostPoolL2Transaction:
|
||||
type: object
|
||||
description: L2 transaction to be posted.
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/TransactionId'
|
||||
@@ -1264,18 +1222,21 @@ components:
|
||||
type: string
|
||||
description: >-
|
||||
Identifier of the destination account. It references the position where the account is inside the state Merkle tree.
|
||||
The identifier is built using: `hez:` + `token symbol:` + `index`
|
||||
The identifier is built using: `hez:` + `token symbol:` + `index`. If this is provided, toHezEthereumAddress and toBjj
|
||||
must be null. To perform an exit the value hez:EXIT:1 must be used.
|
||||
example: null
|
||||
nullable: true
|
||||
toHezEthereumAddress:
|
||||
type: string
|
||||
description: "Address of an Etherum account linked to the Hermez network."
|
||||
description: "Address of an Etherum account linked to the Hermez network. If this is provided, toAccountIndex and toBjj must be null."
|
||||
pattern: "^hez:0x[a-fA-F0-9]{40}$"
|
||||
example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a"
|
||||
nullable: true
|
||||
toBjj:
|
||||
type: string
|
||||
description: "BabyJubJub public key, encoded as base64 URL (RFC 4648), which result in 33 bytes. The padding byte is replaced by a sum of the encoded bytes."
|
||||
description: >-
|
||||
BabyJubJub public key, encoded as base64 URL (RFC 4648), which result in 33 bytes. The padding byte is replaced by a sum of the encoded bytes.
|
||||
If this is prvided, toAccountIndex must be null and toHezEthereumAddress must be hez:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF.
|
||||
pattern: "^hez:[A-Za-z0-9_-]{44}$"
|
||||
example: null
|
||||
nullable: true
|
||||
@@ -1309,9 +1270,9 @@ components:
|
||||
pattern: "^hez:0x[a-fA-F0-9]{40}$"
|
||||
example: null
|
||||
nullable: true
|
||||
requestToBJJ:
|
||||
requestToBjj:
|
||||
type: string
|
||||
description: References the `toBJJ` of the requested transaction.
|
||||
description: References the `toBjj` of the requested transaction.
|
||||
pattern: "^hez:[A-Za-z0-9_-]{44}$"
|
||||
example: null
|
||||
nullable: true
|
||||
@@ -1340,22 +1301,10 @@ components:
|
||||
- type
|
||||
- tokenId
|
||||
- fromAccountIndex
|
||||
- toAccountIndex
|
||||
- toHezAccountIndex
|
||||
- toHezEthereumAddress
|
||||
- toBjj
|
||||
- amount
|
||||
- fee
|
||||
- nonce
|
||||
- signature
|
||||
- requestFromAccountIndex
|
||||
- requestToAccountIndex
|
||||
- requestToHezEthereumAddress
|
||||
- requestToBJJ
|
||||
- requestTokenId
|
||||
- requestAmount
|
||||
- requestFee
|
||||
- requestNonce
|
||||
PoolL2Transaction:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1405,63 +1354,87 @@ components:
|
||||
description: Moment in which the transaction was added to the pool.
|
||||
format: date-time
|
||||
batchNum:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/BatchNum'
|
||||
- nullable: true
|
||||
- example: 5432
|
||||
type: integer
|
||||
description: Identifier of a batch. Every new forged batch increments by one the batchNum, starting at 0.
|
||||
minimum: 0
|
||||
maximum: 4294967295
|
||||
nullable: true
|
||||
example: null
|
||||
requestFromAccountIndex:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AccountIndex'
|
||||
- nullable: true
|
||||
- example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a"
|
||||
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`
|
||||
nullable: true
|
||||
example: null
|
||||
requestToAccountIndex:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AccountIndex'
|
||||
- nullable: true
|
||||
- example: "hez:DAI:33"
|
||||
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`
|
||||
nullable: true
|
||||
example: null
|
||||
requestToHezEthereumAddress:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/HezEthereumAddress'
|
||||
- nullable: true
|
||||
- example: "hez:0xbb942cfcd25ad4d90a62358b0dd84f33b3982699"
|
||||
type: string
|
||||
description: "Address of an Etherum account linked to the Hermez network."
|
||||
pattern: "^hez:0x[a-fA-F0-9]{40}$"
|
||||
nullable: true
|
||||
example: null
|
||||
requestToBJJ:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/BJJ'
|
||||
- nullable: true
|
||||
- example: "hez:HVrB8xQHAYt9QTpPUsj3RGOzDmrCI4IgrYslTeTqo6Ix"
|
||||
type: string
|
||||
description: "BabyJubJub public key, encoded as base64 URL (RFC 4648), which result in 33 bytes. The padding byte is replaced by a sum of the encoded bytes."
|
||||
pattern: "^hez:[A-Za-z0-9_-]{44}$"
|
||||
nullable: true
|
||||
example: null
|
||||
requestTokenId:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/TokenId'
|
||||
- nullable: true
|
||||
- example: 4444
|
||||
type: integer
|
||||
description: References the `tokenId` of the requested transaction.
|
||||
example: null
|
||||
nullable: true
|
||||
requestAmount:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/BigInt'
|
||||
- description: Amount of tokens to be sent.
|
||||
- example: "700000000000000000"
|
||||
- nullable: true
|
||||
type: string
|
||||
description: BigInt is an integer encoded as a string for numbers that are very large.
|
||||
nullable: true
|
||||
example: null
|
||||
requestFee:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/FeeSelector'
|
||||
- nullable: true
|
||||
- example: 8
|
||||
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
|
||||
nullable: true
|
||||
example: null
|
||||
requestNonce:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Nonce'
|
||||
- nullable: true
|
||||
- example: 6
|
||||
type: integer
|
||||
description: Number that can only be used once per account. Increments by one with each transaction.
|
||||
minimum: 0
|
||||
maximum: 1.84467440737096e+19
|
||||
nullable: true
|
||||
example: null
|
||||
token:
|
||||
$ref: '#/components/schemas/Token'
|
||||
required:
|
||||
- fromAccountIndex
|
||||
- toHezAccountIndex
|
||||
- toHezEthereumAddress
|
||||
- toBjj
|
||||
- tokenId
|
||||
- amount
|
||||
- fee
|
||||
- nonce
|
||||
- signature
|
||||
- id
|
||||
- type
|
||||
- fromAccountIndex
|
||||
- toAccountIndex
|
||||
- toHezEthereumAddress
|
||||
- toBjj
|
||||
- amount
|
||||
- fee
|
||||
- nonce
|
||||
- state
|
||||
- signature
|
||||
- timestamp
|
||||
- batchNum
|
||||
- requestFromAccountIndex
|
||||
- requestToAccountIndex
|
||||
- requestToHezEthereumAddress
|
||||
- requestToBJJ
|
||||
- requestTokenId
|
||||
- requestAmount
|
||||
- requestFee
|
||||
- requestNonce
|
||||
- token
|
||||
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)
|
||||
@@ -1514,7 +1487,7 @@ components:
|
||||
description: Identifier of a token registered in the network.
|
||||
minimum: 0
|
||||
maximum: 4294967295
|
||||
example: 4444
|
||||
example: 98765
|
||||
BigInt:
|
||||
type: string
|
||||
description: BigInt is an integer encoded as a string for numbers that are very large.
|
||||
@@ -1776,9 +1749,6 @@ components:
|
||||
$ref: '#/components/schemas/EthereumAddress'
|
||||
collectedFees:
|
||||
$ref: '#/components/schemas/CollectedFees'
|
||||
totalCollectedFeesUSD:
|
||||
type: number
|
||||
description: Value in USD of the collected tokens by the forger in concept of fees. This is calculated at the moment the batch is forged, with the conversion rates at that time.
|
||||
historicTotalCollectedFeesUSD:
|
||||
type: number
|
||||
description: Sum of the all the fees collected, in USD, at the moment the batch was forged.
|
||||
@@ -2124,11 +2094,6 @@ components:
|
||||
- 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:
|
||||
@@ -2212,6 +2177,10 @@ components:
|
||||
type: number
|
||||
description: Average fee percentage paid for L2 transactions in the last 24 hours.
|
||||
example: 1.54
|
||||
nextForgers:
|
||||
$ref: '#/components/schemas/NextForgers'
|
||||
recommendedFee:
|
||||
$ref: '#/components/schemas/RecommendedFee'
|
||||
governance:
|
||||
type: object
|
||||
description: Network setings that are updatable by the governance.
|
||||
|
||||
Reference in New Issue
Block a user