mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-07 11:26:44 +01:00
Refactor api txs
This commit is contained in:
704
api/api_test.go
704
api/api_test.go
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -45,103 +44,26 @@ type testCommon struct {
|
|||||||
usrAddr string
|
usrAddr string
|
||||||
usrBjj string
|
usrBjj string
|
||||||
accs []common.Account
|
accs []common.Account
|
||||||
usrTxs []historyTxAPI
|
usrTxs []testTx
|
||||||
allTxs []historyTxAPI
|
allTxs []testTx
|
||||||
exits []exitAPI
|
exits []exitAPI
|
||||||
usrExits []exitAPI
|
usrExits []exitAPI
|
||||||
poolTxsToSend []receivedPoolTx
|
poolTxsToSend []testPoolTxSend
|
||||||
poolTxsToReceive []sendPoolTx
|
poolTxsToReceive []testPoolTxReceive
|
||||||
auths []accountCreationAuthAPI
|
auths []accountCreationAuthAPI
|
||||||
router *swagger.Router
|
router *swagger.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxSortFields represents the fields needed to sort L1 and L2 transactions
|
|
||||||
type txSortFields struct {
|
|
||||||
BatchNum *common.BatchNum
|
|
||||||
Position int
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
|
|
||||||
type txSortFielder interface {
|
|
||||||
SortFields() txSortFields
|
|
||||||
L1() *common.L1Tx
|
|
||||||
L2() *common.L2Tx
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxsSort array of TxSortFielder
|
|
||||||
type txsSort []txSortFielder
|
|
||||||
|
|
||||||
func (t txsSort) Len() int { return len(t) }
|
|
||||||
func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
|
||||||
func (t txsSort) Less(i, j int) bool {
|
|
||||||
// i not forged yet
|
|
||||||
isf := t[i].SortFields()
|
|
||||||
jsf := t[j].SortFields()
|
|
||||||
if isf.BatchNum == nil {
|
|
||||||
if jsf.BatchNum != nil { // j is already forged
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Both aren't forged, is i in a smaller position?
|
|
||||||
return isf.Position < jsf.Position
|
|
||||||
}
|
|
||||||
// i is forged
|
|
||||||
if jsf.BatchNum == nil {
|
|
||||||
return false // j is not forged
|
|
||||||
}
|
|
||||||
// Both are forged
|
|
||||||
if *isf.BatchNum == *jsf.BatchNum {
|
|
||||||
// At the same batch, is i in a smaller position?
|
|
||||||
return isf.Position < jsf.Position
|
|
||||||
}
|
|
||||||
// At different batches, is i in a smaller batch?
|
|
||||||
return *isf.BatchNum < *jsf.BatchNum
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrappedL1 common.L1Tx
|
|
||||||
|
|
||||||
// SortFields implements TxSortFielder
|
|
||||||
func (tx *wrappedL1) SortFields() txSortFields {
|
|
||||||
return txSortFields{
|
|
||||||
BatchNum: tx.BatchNum,
|
|
||||||
Position: tx.Position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// L1 implements TxSortFielder
|
|
||||||
func (tx *wrappedL1) L1() *common.L1Tx {
|
|
||||||
l1tx := common.L1Tx(*tx)
|
|
||||||
return &l1tx
|
|
||||||
}
|
|
||||||
|
|
||||||
// L2 implements TxSortFielder
|
|
||||||
func (tx *wrappedL1) L2() *common.L2Tx { return nil }
|
|
||||||
|
|
||||||
type wrappedL2 common.L2Tx
|
|
||||||
|
|
||||||
// SortFields implements TxSortFielder
|
|
||||||
func (tx *wrappedL2) SortFields() txSortFields {
|
|
||||||
return txSortFields{
|
|
||||||
BatchNum: &tx.BatchNum,
|
|
||||||
Position: tx.Position,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// L1 implements TxSortFielder
|
|
||||||
func (tx *wrappedL2) L1() *common.L1Tx { return nil }
|
|
||||||
|
|
||||||
// L2 implements TxSortFielder
|
|
||||||
func (tx *wrappedL2) L2() *common.L2Tx {
|
|
||||||
l2tx := common.L2Tx(*tx)
|
|
||||||
return &l2tx
|
|
||||||
}
|
|
||||||
|
|
||||||
var tc testCommon
|
var tc testCommon
|
||||||
var config configAPI
|
var config configAPI
|
||||||
|
|
||||||
|
// TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
|
||||||
|
// emulating the task of the synchronizer in order to have data to be returned
|
||||||
|
// by the API endpoints that will be tested
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
// Init swagger
|
// Initializations
|
||||||
|
// Swagger
|
||||||
router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
|
router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
|
||||||
// Init DBs
|
|
||||||
// HistoryDB
|
// HistoryDB
|
||||||
pass := os.Getenv("POSTGRES_PASS")
|
pass := os.Getenv("POSTGRES_PASS")
|
||||||
database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
|
database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
|
||||||
@@ -170,7 +92,7 @@ func TestMain(m *testing.M) {
|
|||||||
// L2DB
|
// L2DB
|
||||||
l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
|
l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
|
||||||
test.CleanL2DB(l2DB.DB())
|
test.CleanL2DB(l2DB.DB())
|
||||||
|
// Config (smart contract constants)
|
||||||
config.RollupConstants.ExchangeMultiplier = eth.RollupConstExchangeMultiplier
|
config.RollupConstants.ExchangeMultiplier = eth.RollupConstExchangeMultiplier
|
||||||
config.RollupConstants.ExitIdx = eth.RollupConstExitIDx
|
config.RollupConstants.ExitIdx = eth.RollupConstExitIDx
|
||||||
config.RollupConstants.ReservedIdx = eth.RollupConstReservedIDx
|
config.RollupConstants.ReservedIdx = eth.RollupConstReservedIDx
|
||||||
@@ -213,7 +135,7 @@ func TestMain(m *testing.M) {
|
|||||||
config.AuctionConstants = auctionConstants
|
config.AuctionConstants = auctionConstants
|
||||||
config.WDelayerConstants = wdelayerConstants
|
config.WDelayerConstants = wdelayerConstants
|
||||||
|
|
||||||
// Init API
|
// API
|
||||||
api := gin.Default()
|
api := gin.Default()
|
||||||
if err := SetAPIEndpoints(
|
if err := SetAPIEndpoints(
|
||||||
true,
|
true,
|
||||||
@@ -235,7 +157,7 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Populate DBs
|
// Fill HistoryDB and StateDB with fake data
|
||||||
// Clean DB
|
// Clean DB
|
||||||
err = h.Reorg(0)
|
err = h.Reorg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -302,6 +224,17 @@ func TestMain(m *testing.M) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// helper to vinculate user related resources
|
||||||
|
usrIdxs := []string{}
|
||||||
|
for _, acc := range accs {
|
||||||
|
if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
|
||||||
|
for _, token := range tokens {
|
||||||
|
if token.TokenID == acc.TokenID {
|
||||||
|
usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Gen exits and add them to DB
|
// Gen exits and add them to DB
|
||||||
const totalExits = 40
|
const totalExits = 40
|
||||||
exits := test.GenExitTree(totalExits, batches, accs)
|
exits := test.GenExitTree(totalExits, batches, accs)
|
||||||
@@ -309,15 +242,17 @@ func TestMain(m *testing.M) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
// Gen L1Txs and add them to DB
|
|
||||||
|
// L1 and L2 txs need to be sorted in a combined way
|
||||||
|
// Gen L1Txs
|
||||||
const totalL1Txs = 40
|
const totalL1Txs = 40
|
||||||
const userL1Txs = 4
|
const userL1Txs = 4
|
||||||
usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
|
usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
|
||||||
// Gen L2Txs and add them to DB
|
// Gen L2Txs
|
||||||
const totalL2Txs = 20
|
const totalL2Txs = 20
|
||||||
const userL2Txs = 4
|
const userL2Txs = 4
|
||||||
usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
|
usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
|
||||||
// Order txs
|
// Sort txs
|
||||||
sortedTxs := []txSortFielder{}
|
sortedTxs := []txSortFielder{}
|
||||||
for i := 0; i < len(usrL1Txs); i++ {
|
for i := 0; i < len(usrL1Txs); i++ {
|
||||||
wL1 := wrappedL1(usrL1Txs[i])
|
wL1 := wrappedL1(usrL1Txs[i])
|
||||||
@@ -336,161 +271,30 @@ func TestMain(m *testing.M) {
|
|||||||
sortedTxs = append(sortedTxs, &wL2)
|
sortedTxs = append(sortedTxs, &wL2)
|
||||||
}
|
}
|
||||||
sort.Sort(txsSort(sortedTxs))
|
sort.Sort(txsSort(sortedTxs))
|
||||||
// Add txs to DB and prepare them for test commons
|
// Store txs to DB
|
||||||
usrTxs := []historyTxAPI{}
|
|
||||||
allTxs := []historyTxAPI{}
|
|
||||||
getTimestamp := func(blockNum int64) time.Time {
|
|
||||||
for i := 0; i < len(blocks); i++ {
|
|
||||||
if blocks[i].EthBlockNum == blockNum {
|
|
||||||
return blocks[i].Timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("timesamp not found")
|
|
||||||
}
|
|
||||||
getToken := func(id common.TokenID) historydb.TokenWithUSD {
|
|
||||||
for i := 0; i < len(tokensUSD); i++ {
|
|
||||||
if tokensUSD[i].TokenID == id {
|
|
||||||
return tokensUSD[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("token not found")
|
|
||||||
}
|
|
||||||
getTokenByIdx := func(idx common.Idx) historydb.TokenWithUSD {
|
|
||||||
for _, acc := range accs {
|
|
||||||
if idx == acc.Idx {
|
|
||||||
return getToken(acc.TokenID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("token not found")
|
|
||||||
}
|
|
||||||
usrIdxs := []string{}
|
|
||||||
for _, acc := range accs {
|
|
||||||
if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
|
|
||||||
for _, token := range tokens {
|
|
||||||
if token.TokenID == acc.TokenID {
|
|
||||||
usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isUsrTx := func(tx historyTxAPI) bool {
|
|
||||||
for _, idx := range usrIdxs {
|
|
||||||
if tx.FromIdx != nil && *tx.FromIdx == idx {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if tx.ToIdx == idx {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, genericTx := range sortedTxs {
|
for _, genericTx := range sortedTxs {
|
||||||
l1 := genericTx.L1()
|
l1 := genericTx.L1()
|
||||||
l2 := genericTx.L2()
|
l2 := genericTx.L2()
|
||||||
if l1 != nil {
|
if l1 != nil {
|
||||||
// Add L1 tx to DB
|
|
||||||
err = h.AddL1Txs([]common.L1Tx{*l1})
|
err = h.AddL1Txs([]common.L1Tx{*l1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
// L1Tx ==> historyTxAPI
|
} else if l2 != nil {
|
||||||
token := getToken(l1.TokenID)
|
|
||||||
tx := historyTxAPI{
|
|
||||||
IsL1: "L1",
|
|
||||||
TxID: l1.TxID,
|
|
||||||
Type: l1.Type,
|
|
||||||
Position: l1.Position,
|
|
||||||
ToIdx: idxToHez(l1.ToIdx, token.Symbol),
|
|
||||||
Amount: l1.Amount.String(),
|
|
||||||
BatchNum: l1.BatchNum,
|
|
||||||
Timestamp: getTimestamp(l1.EthBlockNum),
|
|
||||||
L1Info: &l1Info{
|
|
||||||
ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
|
|
||||||
UserOrigin: l1.UserOrigin,
|
|
||||||
FromEthAddr: ethAddrToHez(l1.FromEthAddr),
|
|
||||||
FromBJJ: bjjToString(l1.FromBJJ),
|
|
||||||
LoadAmount: l1.LoadAmount.String(),
|
|
||||||
EthBlockNum: l1.EthBlockNum,
|
|
||||||
},
|
|
||||||
Token: token,
|
|
||||||
}
|
|
||||||
if l1.FromIdx != 0 {
|
|
||||||
idxStr := idxToHez(l1.FromIdx, token.Symbol)
|
|
||||||
tx.FromIdx = &idxStr
|
|
||||||
}
|
|
||||||
if token.USD != nil {
|
|
||||||
af := new(big.Float).SetInt(l1.Amount)
|
|
||||||
amountFloat, _ := af.Float64()
|
|
||||||
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
|
|
||||||
tx.HistoricUSD = &usd
|
|
||||||
laf := new(big.Float).SetInt(l1.LoadAmount)
|
|
||||||
loadAmountFloat, _ := laf.Float64()
|
|
||||||
loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
|
|
||||||
tx.L1Info.HistoricLoadAmountUSD = &loadUSD
|
|
||||||
}
|
|
||||||
allTxs = append(allTxs, tx)
|
|
||||||
if isUsrTx(tx) {
|
|
||||||
usrTxs = append(usrTxs, tx)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add L2 tx to DB
|
|
||||||
err = h.AddL2Txs([]common.L2Tx{*l2})
|
err = h.AddL2Txs([]common.L2Tx{*l2})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
// L2Tx ==> historyTxAPI
|
} else {
|
||||||
var tokenID common.TokenID
|
panic("should be l1 or l2")
|
||||||
found := false
|
|
||||||
for _, acc := range accs {
|
|
||||||
if acc.Idx == l2.FromIdx {
|
|
||||||
found = true
|
|
||||||
tokenID = acc.TokenID
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
panic("tokenID not found")
|
|
||||||
}
|
|
||||||
token := getToken(tokenID)
|
|
||||||
tx := historyTxAPI{
|
|
||||||
IsL1: "L2",
|
|
||||||
TxID: l2.TxID,
|
|
||||||
Type: l2.Type,
|
|
||||||
Position: l2.Position,
|
|
||||||
ToIdx: idxToHez(l2.ToIdx, token.Symbol),
|
|
||||||
Amount: l2.Amount.String(),
|
|
||||||
BatchNum: &l2.BatchNum,
|
|
||||||
Timestamp: getTimestamp(l2.EthBlockNum),
|
|
||||||
L2Info: &l2Info{
|
|
||||||
Nonce: l2.Nonce,
|
|
||||||
Fee: l2.Fee,
|
|
||||||
},
|
|
||||||
Token: token,
|
|
||||||
}
|
|
||||||
if l2.FromIdx != 0 {
|
|
||||||
idxStr := idxToHez(l2.FromIdx, token.Symbol)
|
|
||||||
tx.FromIdx = &idxStr
|
|
||||||
}
|
|
||||||
if token.USD != nil {
|
|
||||||
af := new(big.Float).SetInt(l2.Amount)
|
|
||||||
amountFloat, _ := af.Float64()
|
|
||||||
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
|
|
||||||
tx.HistoricUSD = &usd
|
|
||||||
feeUSD := usd * l2.Fee.Percentage()
|
|
||||||
tx.HistoricUSD = &usd
|
|
||||||
tx.L2Info.HistoricFeeUSD = &feeUSD
|
|
||||||
}
|
|
||||||
allTxs = append(allTxs, tx)
|
|
||||||
if isUsrTx(tx) {
|
|
||||||
usrTxs = append(usrTxs, tx)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform exits to API
|
// Transform exits to API
|
||||||
exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
|
exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
|
||||||
historyExits := []historydb.HistoryExit{}
|
historyExits := []historydb.HistoryExit{}
|
||||||
for _, exit := range exits {
|
for _, exit := range exits {
|
||||||
token := getTokenByIdx(exit.AccountIdx)
|
token := getTokenByIdx(exit.AccountIdx, tokensUSD, accs)
|
||||||
historyExits = append(historyExits, historydb.HistoryExit{
|
historyExits = append(historyExits, historydb.HistoryExit{
|
||||||
BatchNum: exit.BatchNum,
|
BatchNum: exit.BatchNum,
|
||||||
AccountIdx: exit.AccountIdx,
|
AccountIdx: exit.AccountIdx,
|
||||||
@@ -521,109 +325,7 @@ 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)
|
|
||||||
}
|
|
||||||
// Coordinators
|
// Coordinators
|
||||||
const nCoords = 10
|
const nCoords = 10
|
||||||
coords := test.GenCoordinators(nCoords, blocks)
|
coords := test.GenCoordinators(nCoords, blocks)
|
||||||
@@ -647,6 +349,8 @@ func TestMain(m *testing.M) {
|
|||||||
apiAuths = append(apiAuths, *apiAuth)
|
apiAuths = append(apiAuths, *apiAuth)
|
||||||
}
|
}
|
||||||
// Set testCommon
|
// Set testCommon
|
||||||
|
usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
|
||||||
|
poolTxsToSend, poolTxsToReceive := genTestPoolTx(accs, []babyjub.PrivateKey{privK}, tokensUSD) // NOTE: pool txs are not inserted to the DB here. In the test they will be posted and getted.
|
||||||
tc = testCommon{
|
tc = testCommon{
|
||||||
blocks: blocks,
|
blocks: blocks,
|
||||||
tokens: tokensUSD,
|
tokens: tokensUSD,
|
||||||
@@ -667,8 +371,8 @@ func TestMain(m *testing.M) {
|
|||||||
// Fake server
|
// Fake server
|
||||||
if os.Getenv("FAKE_SERVER") == "yes" {
|
if os.Getenv("FAKE_SERVER") == "yes" {
|
||||||
for {
|
for {
|
||||||
log.Info("Running fake server until ^C is received")
|
log.Info("Running fake server at " + apiURL + " until ^C is received")
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Run tests
|
// Run tests
|
||||||
@@ -686,217 +390,6 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(result)
|
os.Exit(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetHistoryTxs(t *testing.T) {
|
|
||||||
endpoint := apiURL + "transactions-history"
|
|
||||||
fetchedTxs := []historyTxAPI{}
|
|
||||||
appendIter := func(intr interface{}) {
|
|
||||||
for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
|
|
||||||
tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get all (no filters)
|
|
||||||
limit := 8
|
|
||||||
path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
|
|
||||||
err := doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
|
|
||||||
// Uncomment once tx generation for tests is fixed
|
|
||||||
// // Get by ethAddr
|
|
||||||
// fetchedTxs = []historyTxAPI{}
|
|
||||||
// limit = 7
|
|
||||||
// path = fmt.Sprintf(
|
|
||||||
// "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
|
|
||||||
// endpoint, tc.usrAddr, limit,
|
|
||||||
// )
|
|
||||||
// err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
// assert.NoError(t, err)
|
|
||||||
// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
|
|
||||||
// // Get by bjj
|
|
||||||
// fetchedTxs = []historyTxAPI{}
|
|
||||||
// limit = 6
|
|
||||||
// path = fmt.Sprintf(
|
|
||||||
// "%s?BJJ=%s&limit=%d&fromItem=",
|
|
||||||
// endpoint, tc.usrBjj, limit,
|
|
||||||
// )
|
|
||||||
// err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
// assert.NoError(t, err)
|
|
||||||
// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
|
|
||||||
// Get by tokenID
|
|
||||||
fetchedTxs = []historyTxAPI{}
|
|
||||||
limit = 5
|
|
||||||
tokenID := tc.allTxs[0].Token.TokenID
|
|
||||||
path = fmt.Sprintf(
|
|
||||||
"%s?tokenId=%d&limit=%d&fromItem=",
|
|
||||||
endpoint, tokenID, limit,
|
|
||||||
)
|
|
||||||
err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
tokenIDTxs := []historyTxAPI{}
|
|
||||||
for i := 0; i < len(tc.allTxs); i++ {
|
|
||||||
if tc.allTxs[i].Token.TokenID == tokenID {
|
|
||||||
tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
|
|
||||||
// idx
|
|
||||||
fetchedTxs = []historyTxAPI{}
|
|
||||||
limit = 4
|
|
||||||
idx := tc.allTxs[0].ToIdx
|
|
||||||
path = fmt.Sprintf(
|
|
||||||
"%s?accountIndex=%s&limit=%d&fromItem=",
|
|
||||||
endpoint, idx, limit,
|
|
||||||
)
|
|
||||||
err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
idxTxs := []historyTxAPI{}
|
|
||||||
for i := 0; i < len(tc.allTxs); i++ {
|
|
||||||
if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
|
|
||||||
tc.allTxs[i].ToIdx[6:] == idx[6:] {
|
|
||||||
idxTxs = append(idxTxs, tc.allTxs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
|
|
||||||
// batchNum
|
|
||||||
fetchedTxs = []historyTxAPI{}
|
|
||||||
limit = 3
|
|
||||||
batchNum := tc.allTxs[0].BatchNum
|
|
||||||
path = fmt.Sprintf(
|
|
||||||
"%s?batchNum=%d&limit=%d&fromItem=",
|
|
||||||
endpoint, *batchNum, limit,
|
|
||||||
)
|
|
||||||
err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
batchNumTxs := []historyTxAPI{}
|
|
||||||
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{
|
|
||||||
// Uncomment once test gen is fixed
|
|
||||||
// common.TxTypeExit,
|
|
||||||
// common.TxTypeTransfer,
|
|
||||||
// common.TxTypeDeposit,
|
|
||||||
common.TxTypeCreateAccountDeposit,
|
|
||||||
// common.TxTypeCreateAccountDepositTransfer,
|
|
||||||
// common.TxTypeDepositTransfer,
|
|
||||||
common.TxTypeForceTransfer,
|
|
||||||
// common.TxTypeForceExit,
|
|
||||||
// common.TxTypeTransferToEthAddr,
|
|
||||||
// common.TxTypeTransferToBJJ,
|
|
||||||
}
|
|
||||||
for _, txType := range txTypes {
|
|
||||||
fetchedTxs = []historyTxAPI{}
|
|
||||||
limit = 2
|
|
||||||
path = fmt.Sprintf(
|
|
||||||
"%s?type=%s&limit=%d&fromItem=",
|
|
||||||
endpoint, txType, limit,
|
|
||||||
)
|
|
||||||
err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
txTypeTxs := []historyTxAPI{}
|
|
||||||
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 = []historyTxAPI{}
|
|
||||||
limit = 1
|
|
||||||
path = fmt.Sprintf(
|
|
||||||
"%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
|
|
||||||
endpoint, *batchNum, tokenID, limit,
|
|
||||||
)
|
|
||||||
err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
mixedTxs := []historyTxAPI{}
|
|
||||||
for i := 0; i < len(tc.allTxs); i++ {
|
|
||||||
if tc.allTxs[i].BatchNum != nil {
|
|
||||||
if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
|
|
||||||
mixedTxs = append(mixedTxs, tc.allTxs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
|
|
||||||
// All, in reverse order
|
|
||||||
fetchedTxs = []historyTxAPI{}
|
|
||||||
limit = 5
|
|
||||||
path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
|
|
||||||
err = doGoodReqPaginated(path, historydb.OrderDesc, &historyTxsAPI{}, appendIter)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
flipedTxs := []historyTxAPI{}
|
|
||||||
for i := 0; i < len(tc.allTxs); i++ {
|
|
||||||
flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
|
|
||||||
}
|
|
||||||
assertHistoryTxAPIs(t, flipedTxs, 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&fromItem=999999", endpoint)
|
|
||||||
err = doBadReq("GET", path, nil, 404)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetHistoryTx(t *testing.T) {
|
|
||||||
// Get all txs by their ID
|
|
||||||
endpoint := apiURL + "transactions-history/"
|
|
||||||
fetchedTxs := []historyTxAPI{}
|
|
||||||
for _, tx := range tc.allTxs {
|
|
||||||
fetchedTx := historyTxAPI{}
|
|
||||||
assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx))
|
|
||||||
fetchedTxs = append(fetchedTxs, fetchedTx)
|
|
||||||
}
|
|
||||||
assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
|
|
||||||
// 400
|
|
||||||
err := doBadReq("GET", endpoint+"0x001", nil, 400)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
// 404
|
|
||||||
err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertHistoryTxAPIs(t *testing.T, expected, actual []historyTxAPI) {
|
|
||||||
require.Equal(t, len(expected), len(actual))
|
|
||||||
for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
|
|
||||||
actual[i].ItemID = 0
|
|
||||||
assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
|
|
||||||
expected[i].Timestamp = actual[i].Timestamp
|
|
||||||
if expected[i].Token.USDUpdate == nil {
|
|
||||||
assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
|
|
||||||
expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
|
|
||||||
}
|
|
||||||
test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
|
|
||||||
if expected[i].L2Info != nil {
|
|
||||||
test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
|
|
||||||
} else {
|
|
||||||
test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
|
|
||||||
}
|
|
||||||
assert.Equal(t, expected[i], actual[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetExits(t *testing.T) {
|
func TestGetExits(t *testing.T) {
|
||||||
endpoint := apiURL + "exits"
|
endpoint := apiURL + "exits"
|
||||||
fetchedExits := []exitAPI{}
|
fetchedExits := []exitAPI{}
|
||||||
@@ -1078,89 +571,6 @@ func TestGetConfig(t *testing.T) {
|
|||||||
assert.Equal(t, cg, &configTest)
|
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)
|
|
||||||
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 TestAccountCreationAuth(t *testing.T) {
|
func TestAccountCreationAuth(t *testing.T) {
|
||||||
// POST
|
// POST
|
||||||
endpoint := apiURL + "account-creation-authorization"
|
endpoint := apiURL + "account-creation-authorization"
|
||||||
@@ -1368,3 +778,41 @@ func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int)
|
|||||||
responseValidationInput = responseValidationInput.SetBodyBytes(body)
|
responseValidationInput = responseValidationInput.SetBodyBytes(body)
|
||||||
return swagger.ValidateResponse(ctx, responseValidationInput)
|
return swagger.ValidateResponse(ctx, responseValidationInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test helpers
|
||||||
|
|
||||||
|
func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
|
||||||
|
for i := 0; i < len(blocks); i++ {
|
||||||
|
if blocks[i].EthBlockNum == blockNum {
|
||||||
|
return blocks[i].Timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("timesamp not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
|
||||||
|
for i := 0; i < len(tokens); i++ {
|
||||||
|
if tokens[i].TokenID == id {
|
||||||
|
return tokens[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("token not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
|
||||||
|
for _, acc := range accs {
|
||||||
|
if idx == acc.Idx {
|
||||||
|
return getTokenByID(acc.TokenID, tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("token not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
|
||||||
|
for _, acc := range accs {
|
||||||
|
if acc.Idx == idx {
|
||||||
|
return &acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("account not found")
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func getBatch(c *gin.Context) {
|
|||||||
|
|
||||||
type fullBatch struct {
|
type fullBatch struct {
|
||||||
Batch *historydb.BatchAPI
|
Batch *historydb.BatchAPI
|
||||||
Txs []historyTxAPI
|
Txs []historydb.TxAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFullBatch(c *gin.Context) {
|
func getFullBatch(c *gin.Context) {
|
||||||
@@ -107,7 +107,7 @@ func getFullBatch(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
// Fetch txs from historyDB
|
// Fetch txs from historyDB
|
||||||
// TODO
|
// TODO
|
||||||
txs := []historyTxAPI{}
|
txs := []historydb.TxAPI{}
|
||||||
// JSON response
|
// JSON response
|
||||||
c.JSON(http.StatusOK, fullBatch{
|
c.JSON(http.StatusOK, fullBatch{
|
||||||
Batch: batch,
|
Batch: batch,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,7 +13,6 @@ import (
|
|||||||
"github.com/hermeznetwork/hermez-node/common"
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
"github.com/hermeznetwork/hermez-node/db"
|
"github.com/hermeznetwork/hermez-node/db"
|
||||||
"github.com/hermeznetwork/hermez-node/db/historydb"
|
"github.com/hermeznetwork/hermez-node/db/historydb"
|
||||||
"github.com/hermeznetwork/hermez-node/db/l2db"
|
|
||||||
"github.com/hermeznetwork/hermez-node/eth"
|
"github.com/hermeznetwork/hermez-node/eth"
|
||||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
)
|
)
|
||||||
@@ -46,113 +44,6 @@ func idxToHez(idx common.Idx, tokenSymbol string) string {
|
|||||||
return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx))
|
return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// History Tx
|
|
||||||
|
|
||||||
type historyTxsAPI struct {
|
|
||||||
Txs []historyTxAPI `json:"transactions"`
|
|
||||||
Pagination *db.Pagination `json:"pagination"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (htx *historyTxsAPI) GetPagination() *db.Pagination {
|
|
||||||
if htx.Txs[0].ItemID < htx.Txs[len(htx.Txs)-1].ItemID {
|
|
||||||
htx.Pagination.FirstReturnedItem = htx.Txs[0].ItemID
|
|
||||||
htx.Pagination.LastReturnedItem = htx.Txs[len(htx.Txs)-1].ItemID
|
|
||||||
} else {
|
|
||||||
htx.Pagination.LastReturnedItem = htx.Txs[0].ItemID
|
|
||||||
htx.Pagination.FirstReturnedItem = htx.Txs[len(htx.Txs)-1].ItemID
|
|
||||||
}
|
|
||||||
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:"fromHezEthereumAddress"`
|
|
||||||
FromBJJ string `json:"fromBJJ"`
|
|
||||||
LoadAmount string `json:"loadAmount"`
|
|
||||||
HistoricLoadAmountUSD *float64 `json:"historicLoadAmountUSD"`
|
|
||||||
EthBlockNum int64 `json:"ethereumBlockNum"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type l2Info struct {
|
|
||||||
Fee common.FeeSelector `json:"fee"`
|
|
||||||
HistoricFeeUSD *float64 `json:"historicFeeUSD"`
|
|
||||||
Nonce common.Nonce `json:"nonce"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type historyTxAPI struct {
|
|
||||||
IsL1 string `json:"L1orL2"`
|
|
||||||
TxID common.TxID `json:"id"`
|
|
||||||
ItemID int `json:"itemId"`
|
|
||||||
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"`
|
|
||||||
HistoricUSD *float64 `json:"historicUSD"`
|
|
||||||
Timestamp time.Time `json:"timestamp"`
|
|
||||||
L1Info *l1Info `json:"L1Info"`
|
|
||||||
L2Info *l2Info `json:"L2Info"`
|
|
||||||
Token historydb.TokenWithUSD `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func historyTxsToAPI(dbTxs []historydb.HistoryTx) []historyTxAPI {
|
|
||||||
apiTxs := []historyTxAPI{}
|
|
||||||
for i := 0; i < len(dbTxs); i++ {
|
|
||||||
apiTx := historyTxAPI{
|
|
||||||
TxID: dbTxs[i].TxID,
|
|
||||||
ItemID: dbTxs[i].ItemID,
|
|
||||||
Type: dbTxs[i].Type,
|
|
||||||
Position: dbTxs[i].Position,
|
|
||||||
ToIdx: idxToHez(dbTxs[i].ToIdx, dbTxs[i].TokenSymbol),
|
|
||||||
Amount: dbTxs[i].Amount.String(),
|
|
||||||
HistoricUSD: dbTxs[i].HistoricUSD,
|
|
||||||
BatchNum: dbTxs[i].BatchNum,
|
|
||||||
Timestamp: dbTxs[i].Timestamp,
|
|
||||||
Token: historydb.TokenWithUSD{
|
|
||||||
TokenID: dbTxs[i].TokenID,
|
|
||||||
EthBlockNum: dbTxs[i].TokenEthBlockNum,
|
|
||||||
EthAddr: dbTxs[i].TokenEthAddr,
|
|
||||||
Name: dbTxs[i].TokenName,
|
|
||||||
Symbol: dbTxs[i].TokenSymbol,
|
|
||||||
Decimals: dbTxs[i].TokenDecimals,
|
|
||||||
USD: dbTxs[i].TokenUSD,
|
|
||||||
USDUpdate: dbTxs[i].TokenUSDUpdate,
|
|
||||||
},
|
|
||||||
L1Info: nil,
|
|
||||||
L2Info: nil,
|
|
||||||
}
|
|
||||||
if dbTxs[i].FromIdx != nil {
|
|
||||||
fromIdx := new(string)
|
|
||||||
*fromIdx = idxToHez(*dbTxs[i].FromIdx, dbTxs[i].TokenSymbol)
|
|
||||||
apiTx.FromIdx = fromIdx
|
|
||||||
}
|
|
||||||
if dbTxs[i].IsL1 {
|
|
||||||
apiTx.IsL1 = "L1"
|
|
||||||
apiTx.L1Info = &l1Info{
|
|
||||||
ToForgeL1TxsNum: dbTxs[i].ToForgeL1TxsNum,
|
|
||||||
UserOrigin: *dbTxs[i].UserOrigin,
|
|
||||||
FromEthAddr: ethAddrToHez(*dbTxs[i].FromEthAddr),
|
|
||||||
FromBJJ: bjjToString(dbTxs[i].FromBJJ),
|
|
||||||
LoadAmount: dbTxs[i].LoadAmount.String(),
|
|
||||||
HistoricLoadAmountUSD: dbTxs[i].HistoricLoadAmountUSD,
|
|
||||||
EthBlockNum: dbTxs[i].EthBlockNum,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
apiTx.IsL1 = "L2"
|
|
||||||
apiTx.L2Info = &l2Info{
|
|
||||||
Fee: *dbTxs[i].Fee,
|
|
||||||
HistoricFeeUSD: dbTxs[i].HistoricFeeUSD,
|
|
||||||
Nonce: *dbTxs[i].Nonce,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apiTxs = append(apiTxs, apiTx)
|
|
||||||
}
|
|
||||||
return apiTxs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit
|
// Exit
|
||||||
|
|
||||||
type exitsAPI struct {
|
type exitsAPI struct {
|
||||||
@@ -262,322 +153,6 @@ type configAPI struct {
|
|||||||
WDelayerConstants eth.WDelayerConstants `json:"withdrawalDelayer"`
|
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 historydb.TokenWithUSD `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: historydb.TokenWithUSD{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccountCreationAuth
|
// AccountCreationAuth
|
||||||
|
|
||||||
type accountCreationAuthAPI struct {
|
type accountCreationAuthAPI struct {
|
||||||
|
|||||||
@@ -68,46 +68,6 @@ func getAccountCreationAuth(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, apiAuth)
|
c.JSON(http.StatusOK, apiAuth)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func getAccounts(c *gin.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -177,24 +137,6 @@ func getExit(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, apiExits[0])
|
c.JSON(http.StatusOK, apiExits[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHistoryTx(c *gin.Context) {
|
|
||||||
// Get TxID
|
|
||||||
txID, err := parseParamTxID(c)
|
|
||||||
if err != nil {
|
|
||||||
retBadReq(err, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Fetch tx from historyDB
|
|
||||||
tx, err := h.GetHistoryTx(txID)
|
|
||||||
if err != nil {
|
|
||||||
retSQLErr(err, c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apiTxs := historyTxsToAPI([]historydb.HistoryTx{*tx})
|
|
||||||
// Build succesfull response
|
|
||||||
c.JSON(http.StatusOK, apiTxs[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSlots(c *gin.Context) {
|
func getSlots(c *gin.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1314,6 +1314,18 @@ components:
|
|||||||
$ref: '#/components/schemas/TransactionTypeL2'
|
$ref: '#/components/schemas/TransactionTypeL2'
|
||||||
fromAccountIndex:
|
fromAccountIndex:
|
||||||
$ref: '#/components/schemas/AccountIndex'
|
$ref: '#/components/schemas/AccountIndex'
|
||||||
|
fromHezEthereumAddress:
|
||||||
|
type: string
|
||||||
|
description: "Address of an Etherum account linked to the Hermez network."
|
||||||
|
pattern: "^hez:0x[a-fA-F0-9]{40}$"
|
||||||
|
example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a"
|
||||||
|
nullable: true
|
||||||
|
fromBJJ:
|
||||||
|
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}$"
|
||||||
|
example: "hez:9CK9fjQdMUTGm8KDvGLy3MB-vnP0NCcGX7Uh7OO6KRJm"
|
||||||
|
nullable: true
|
||||||
toAccountIndex:
|
toAccountIndex:
|
||||||
type: string
|
type: string
|
||||||
description: >-
|
description: >-
|
||||||
@@ -1416,6 +1428,8 @@ components:
|
|||||||
- id
|
- id
|
||||||
- type
|
- type
|
||||||
- fromAccountIndex
|
- fromAccountIndex
|
||||||
|
- fromHezEthereumAddress
|
||||||
|
- fromBJJ
|
||||||
- toAccountIndex
|
- toAccountIndex
|
||||||
- toHezEthereumAddress
|
- toHezEthereumAddress
|
||||||
- toBjj
|
- toBjj
|
||||||
@@ -1588,10 +1602,34 @@ components:
|
|||||||
The identifier is built using: `hez:` + `token symbol:` + `index`
|
The identifier is built using: `hez:` + `token symbol:` + `index`
|
||||||
example: "hez:DAI:4444"
|
example: "hez:DAI:4444"
|
||||||
nullable: true
|
nullable: true
|
||||||
|
fromHezEthereumAddress:
|
||||||
|
type: string
|
||||||
|
description: "Address of an Etherum account linked to the Hermez network."
|
||||||
|
pattern: "^hez:0x[a-fA-F0-9]{40}$"
|
||||||
|
example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a"
|
||||||
|
nullable: true
|
||||||
|
fromBJJ:
|
||||||
|
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}$"
|
||||||
|
example: "hez:9CK9fjQdMUTGm8KDvGLy3MB-vnP0NCcGX7Uh7OO6KRJm"
|
||||||
|
nullable: true
|
||||||
toAccountIndex:
|
toAccountIndex:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/AccountIndex'
|
- $ref: '#/components/schemas/AccountIndex'
|
||||||
- example: "hez:DAI:672"
|
- example: "hez:DAI:672"
|
||||||
|
toHezEthereumAddress:
|
||||||
|
type: string
|
||||||
|
description: "Address of an Etherum account linked to the Hermez network."
|
||||||
|
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."
|
||||||
|
pattern: "^hez:[A-Za-z0-9_-]{44}$"
|
||||||
|
example: "hez:f1J78_6uqTyjX6jrVCqN4RFeRBnWQAGl477ZFtOnH6Sm"
|
||||||
|
nullable: true
|
||||||
amount:
|
amount:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/BigInt'
|
- $ref: '#/components/schemas/BigInt'
|
||||||
@@ -1625,10 +1663,6 @@ components:
|
|||||||
userOrigin:
|
userOrigin:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: True if the transaction was sent by a user. False if it was sent by a coordinator.
|
description: True if the transaction was sent by a user. False if it was sent by a coordinator.
|
||||||
fromHezEthereumAddress:
|
|
||||||
$ref: '#/components/schemas/HezEthereumAddress'
|
|
||||||
fromBJJ:
|
|
||||||
$ref: '#/components/schemas/BJJ'
|
|
||||||
loadAmount:
|
loadAmount:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/BigInt'
|
- $ref: '#/components/schemas/BigInt'
|
||||||
@@ -1647,8 +1681,6 @@ components:
|
|||||||
required:
|
required:
|
||||||
- toForgeL1TransactionsNum
|
- toForgeL1TransactionsNum
|
||||||
- userOrigin
|
- userOrigin
|
||||||
- fromHezEthereumAddress
|
|
||||||
- fromBJJ
|
|
||||||
- loadAmount
|
- loadAmount
|
||||||
- historicLoadAmountUSD
|
- historicLoadAmountUSD
|
||||||
- ethereumBlockNum
|
- ethereumBlockNum
|
||||||
@@ -1680,7 +1712,11 @@ components:
|
|||||||
- type
|
- type
|
||||||
- position
|
- position
|
||||||
- fromAccountIndex
|
- fromAccountIndex
|
||||||
|
- fromHezEthereumAddress
|
||||||
|
- fromBJJ
|
||||||
- toAccountIndex
|
- toAccountIndex
|
||||||
|
- toHezEthereumAddress
|
||||||
|
- toBJJ
|
||||||
- amount
|
- amount
|
||||||
- batchNum
|
- batchNum
|
||||||
- historicUSD
|
- historicUSD
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db/historydb"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHistoryTxs(c *gin.Context) {
|
func getHistoryTxs(c *gin.Context) {
|
||||||
@@ -42,9 +44,29 @@ func getHistoryTxs(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build succesfull response
|
// Build succesfull response
|
||||||
apiTxs := historyTxsToAPI(txs)
|
type txsResponse struct {
|
||||||
c.JSON(http.StatusOK, &historyTxsAPI{
|
Txs []historydb.TxAPI `json:"transactions"`
|
||||||
Txs: apiTxs,
|
Pagination *db.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, &txsResponse{
|
||||||
|
Txs: txs,
|
||||||
Pagination: pagination,
|
Pagination: pagination,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHistoryTx(c *gin.Context) {
|
||||||
|
// Get TxID
|
||||||
|
txID, err := parseParamTxID(c)
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Fetch tx from historyDB
|
||||||
|
tx, err := h.GetHistoryTx(txID)
|
||||||
|
if err != nil {
|
||||||
|
retSQLErr(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Build succesfull response
|
||||||
|
c.JSON(http.StatusOK, tx)
|
||||||
|
}
|
||||||
|
|||||||
507
api/txshistory_test.go
Normal file
507
api/txshistory_test.go
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hermeznetwork/hermez-node/apitypes"
|
||||||
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db/historydb"
|
||||||
|
"github.com/hermeznetwork/hermez-node/test"
|
||||||
|
"github.com/mitchellh/copystructure"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testL1Info struct {
|
||||||
|
ToForgeL1TxsNum *int64 `json:"toForgeL1TransactionsNum"`
|
||||||
|
UserOrigin bool `json:"userOrigin"`
|
||||||
|
LoadAmount string `json:"loadAmount"`
|
||||||
|
HistoricLoadAmountUSD *float64 `json:"historicLoadAmountUSD"`
|
||||||
|
EthBlockNum int64 `json:"ethereumBlockNum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testL2Info struct {
|
||||||
|
Fee common.FeeSelector `json:"fee"`
|
||||||
|
HistoricFeeUSD *float64 `json:"historicFeeUSD"`
|
||||||
|
Nonce common.Nonce `json:"nonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testTx struct {
|
||||||
|
IsL1 string `json:"L1orL2"`
|
||||||
|
TxID common.TxID `json:"id"`
|
||||||
|
ItemID int `json:"itemId"`
|
||||||
|
Type common.TxType `json:"type"`
|
||||||
|
Position int `json:"position"`
|
||||||
|
FromIdx *string `json:"fromAccountIndex"`
|
||||||
|
FromEthAddr *string `json:"fromHezEthereumAddress"`
|
||||||
|
FromBJJ *string `json:"fromBJJ"`
|
||||||
|
ToIdx string `json:"toAccountIndex"`
|
||||||
|
ToEthAddr *string `json:"toHezEthereumAddress"`
|
||||||
|
ToBJJ *string `json:"toBJJ"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
BatchNum *common.BatchNum `json:"batchNum"`
|
||||||
|
HistoricUSD *float64 `json:"historicUSD"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
L1Info *testL1Info `json:"L1Info"`
|
||||||
|
L2Info *testL2Info `json:"L2Info"`
|
||||||
|
Token historydb.TokenWithUSD `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testTxsResponse struct {
|
||||||
|
Txs []testTx `json:"transactions"`
|
||||||
|
Pagination *db.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testTxsResponse) GetPagination() *db.Pagination {
|
||||||
|
if t.Txs[0].ItemID < t.Txs[len(t.Txs)-1].ItemID {
|
||||||
|
t.Pagination.FirstReturnedItem = t.Txs[0].ItemID
|
||||||
|
t.Pagination.LastReturnedItem = t.Txs[len(t.Txs)-1].ItemID
|
||||||
|
} else {
|
||||||
|
t.Pagination.LastReturnedItem = t.Txs[0].ItemID
|
||||||
|
t.Pagination.FirstReturnedItem = t.Txs[len(t.Txs)-1].ItemID
|
||||||
|
}
|
||||||
|
return t.Pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testTxsResponse) Len() int {
|
||||||
|
return len(t.Txs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxSortFields represents the fields needed to sort L1 and L2 transactions
|
||||||
|
type txSortFields struct {
|
||||||
|
BatchNum *common.BatchNum
|
||||||
|
Position int
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
|
||||||
|
type txSortFielder interface {
|
||||||
|
SortFields() txSortFields
|
||||||
|
L1() *common.L1Tx
|
||||||
|
L2() *common.L2Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxsSort array of TxSortFielder
|
||||||
|
type txsSort []txSortFielder
|
||||||
|
|
||||||
|
func (t txsSort) Len() int { return len(t) }
|
||||||
|
func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
|
func (t txsSort) Less(i, j int) bool {
|
||||||
|
// i not forged yet
|
||||||
|
isf := t[i].SortFields()
|
||||||
|
jsf := t[j].SortFields()
|
||||||
|
if isf.BatchNum == nil {
|
||||||
|
if jsf.BatchNum != nil { // j is already forged
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Both aren't forged, is i in a smaller position?
|
||||||
|
return isf.Position < jsf.Position
|
||||||
|
}
|
||||||
|
// i is forged
|
||||||
|
if jsf.BatchNum == nil {
|
||||||
|
return false // j is not forged
|
||||||
|
}
|
||||||
|
// Both are forged
|
||||||
|
if *isf.BatchNum == *jsf.BatchNum {
|
||||||
|
// At the same batch, is i in a smaller position?
|
||||||
|
return isf.Position < jsf.Position
|
||||||
|
}
|
||||||
|
// At different batches, is i in a smaller batch?
|
||||||
|
return *isf.BatchNum < *jsf.BatchNum
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappedL1 common.L1Tx
|
||||||
|
|
||||||
|
// SortFields implements TxSortFielder
|
||||||
|
func (tx *wrappedL1) SortFields() txSortFields {
|
||||||
|
return txSortFields{
|
||||||
|
BatchNum: tx.BatchNum,
|
||||||
|
Position: tx.Position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// L1 implements TxSortFielder
|
||||||
|
func (tx *wrappedL1) L1() *common.L1Tx {
|
||||||
|
l1tx := common.L1Tx(*tx)
|
||||||
|
return &l1tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// L2 implements TxSortFielder
|
||||||
|
func (tx *wrappedL1) L2() *common.L2Tx { return nil }
|
||||||
|
|
||||||
|
type wrappedL2 common.L2Tx
|
||||||
|
|
||||||
|
// SortFields implements TxSortFielder
|
||||||
|
func (tx *wrappedL2) SortFields() txSortFields {
|
||||||
|
return txSortFields{
|
||||||
|
BatchNum: &tx.BatchNum,
|
||||||
|
Position: tx.Position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// L1 implements TxSortFielder
|
||||||
|
func (tx *wrappedL2) L1() *common.L1Tx { return nil }
|
||||||
|
|
||||||
|
// L2 implements TxSortFielder
|
||||||
|
func (tx *wrappedL2) L2() *common.L2Tx {
|
||||||
|
l2tx := common.L2Tx(*tx)
|
||||||
|
return &l2tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func genTestTxs(genericTxs []txSortFielder, usrIdxs []string, accs []common.Account, tokens []historydb.TokenWithUSD, blocks []common.Block) (usrTxs []testTx, allTxs []testTx) {
|
||||||
|
usrTxs = []testTx{}
|
||||||
|
allTxs = []testTx{}
|
||||||
|
isUsrTx := func(tx testTx) bool {
|
||||||
|
for _, idx := range usrIdxs {
|
||||||
|
if tx.FromIdx != nil && *tx.FromIdx == idx {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if tx.ToIdx == idx {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, genericTx := range genericTxs {
|
||||||
|
l1 := genericTx.L1()
|
||||||
|
l2 := genericTx.L2()
|
||||||
|
if l1 != nil { // L1Tx to testTx
|
||||||
|
token := getTokenByID(l1.TokenID, tokens)
|
||||||
|
// l1.FromEthAddr and l1.FromBJJ can't be nil
|
||||||
|
fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr))
|
||||||
|
fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ))
|
||||||
|
tx := testTx{
|
||||||
|
IsL1: "L1",
|
||||||
|
TxID: l1.TxID,
|
||||||
|
Type: l1.Type,
|
||||||
|
Position: l1.Position,
|
||||||
|
FromEthAddr: &fromEthAddr,
|
||||||
|
FromBJJ: &fromBJJ,
|
||||||
|
ToIdx: idxToHez(l1.ToIdx, token.Symbol),
|
||||||
|
Amount: l1.Amount.String(),
|
||||||
|
BatchNum: l1.BatchNum,
|
||||||
|
Timestamp: getTimestamp(l1.EthBlockNum, blocks),
|
||||||
|
L1Info: &testL1Info{
|
||||||
|
ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
|
||||||
|
UserOrigin: l1.UserOrigin,
|
||||||
|
LoadAmount: l1.LoadAmount.String(),
|
||||||
|
EthBlockNum: l1.EthBlockNum,
|
||||||
|
},
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
// If FromIdx is not nil
|
||||||
|
if l1.FromIdx != 0 {
|
||||||
|
idxStr := idxToHez(l1.FromIdx, token.Symbol)
|
||||||
|
tx.FromIdx = &idxStr
|
||||||
|
}
|
||||||
|
// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
|
||||||
|
if l1.ToIdx >= common.UserThreshold {
|
||||||
|
// find account
|
||||||
|
for _, acc := range accs {
|
||||||
|
if l1.ToIdx == acc.Idx {
|
||||||
|
toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
|
||||||
|
tx.ToEthAddr = &toEthAddr
|
||||||
|
toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
|
||||||
|
tx.ToBJJ = &toBJJ
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the token has USD value setted
|
||||||
|
if token.USD != nil {
|
||||||
|
af := new(big.Float).SetInt(l1.Amount)
|
||||||
|
amountFloat, _ := af.Float64()
|
||||||
|
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
|
||||||
|
tx.HistoricUSD = &usd
|
||||||
|
laf := new(big.Float).SetInt(l1.LoadAmount)
|
||||||
|
loadAmountFloat, _ := laf.Float64()
|
||||||
|
loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
|
||||||
|
tx.L1Info.HistoricLoadAmountUSD = &loadUSD
|
||||||
|
}
|
||||||
|
allTxs = append(allTxs, tx)
|
||||||
|
if isUsrTx(tx) {
|
||||||
|
usrTxs = append(usrTxs, tx)
|
||||||
|
}
|
||||||
|
} else { // L2Tx to testTx
|
||||||
|
token := getTokenByIdx(l2.FromIdx, tokens, accs)
|
||||||
|
// l1.FromIdx can't be nil
|
||||||
|
fromIdx := idxToHez(l2.FromIdx, token.Symbol)
|
||||||
|
tx := testTx{
|
||||||
|
IsL1: "L2",
|
||||||
|
TxID: l2.TxID,
|
||||||
|
Type: l2.Type,
|
||||||
|
Position: l2.Position,
|
||||||
|
ToIdx: idxToHez(l2.ToIdx, token.Symbol),
|
||||||
|
FromIdx: &fromIdx,
|
||||||
|
Amount: l2.Amount.String(),
|
||||||
|
BatchNum: &l2.BatchNum,
|
||||||
|
Timestamp: getTimestamp(l2.EthBlockNum, blocks),
|
||||||
|
L2Info: &testL2Info{
|
||||||
|
Nonce: l2.Nonce,
|
||||||
|
Fee: l2.Fee,
|
||||||
|
},
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
// If FromIdx is not nil
|
||||||
|
if l2.FromIdx != 0 {
|
||||||
|
idxStr := idxToHez(l2.FromIdx, token.Symbol)
|
||||||
|
tx.FromIdx = &idxStr
|
||||||
|
}
|
||||||
|
// Set FromEthAddr and FromBJJ (FromIdx it's always >255)
|
||||||
|
for _, acc := range accs {
|
||||||
|
if l2.ToIdx == acc.Idx {
|
||||||
|
fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
|
||||||
|
tx.FromEthAddr = &fromEthAddr
|
||||||
|
fromBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
|
||||||
|
tx.FromBJJ = &fromBJJ
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
|
||||||
|
if l2.ToIdx >= common.UserThreshold {
|
||||||
|
// find account
|
||||||
|
for _, acc := range accs {
|
||||||
|
if l2.ToIdx == acc.Idx {
|
||||||
|
toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
|
||||||
|
tx.ToEthAddr = &toEthAddr
|
||||||
|
toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
|
||||||
|
tx.ToBJJ = &toBJJ
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the token has USD value setted
|
||||||
|
if token.USD != nil {
|
||||||
|
af := new(big.Float).SetInt(l2.Amount)
|
||||||
|
amountFloat, _ := af.Float64()
|
||||||
|
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
|
||||||
|
tx.HistoricUSD = &usd
|
||||||
|
feeUSD := usd * l2.Fee.Percentage()
|
||||||
|
tx.HistoricUSD = &usd
|
||||||
|
tx.L2Info.HistoricFeeUSD = &feeUSD
|
||||||
|
}
|
||||||
|
allTxs = append(allTxs, tx)
|
||||||
|
if isUsrTx(tx) {
|
||||||
|
usrTxs = append(usrTxs, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return usrTxs, allTxs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHistoryTxs(t *testing.T) {
|
||||||
|
endpoint := apiURL + "transactions-history"
|
||||||
|
fetchedTxs := []testTx{}
|
||||||
|
appendIter := func(intr interface{}) {
|
||||||
|
for i := 0; i < len(intr.(*testTxsResponse).Txs); i++ {
|
||||||
|
tmp, err := copystructure.Copy(intr.(*testTxsResponse).Txs[i])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fetchedTxs = append(fetchedTxs, tmp.(testTx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get all (no filters)
|
||||||
|
limit := 8
|
||||||
|
path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
|
||||||
|
err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
|
||||||
|
// Uncomment once tx generation for tests is fixed
|
||||||
|
// // Get by ethAddr
|
||||||
|
// fetchedTxs = []testTx{}
|
||||||
|
// limit = 7
|
||||||
|
// path = fmt.Sprintf(
|
||||||
|
// "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
|
||||||
|
// endpoint, tc.usrAddr, limit,
|
||||||
|
// )
|
||||||
|
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
// assert.NoError(t, err)
|
||||||
|
// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
|
||||||
|
// // Get by bjj
|
||||||
|
// fetchedTxs = []testTx{}
|
||||||
|
// limit = 6
|
||||||
|
// path = fmt.Sprintf(
|
||||||
|
// "%s?BJJ=%s&limit=%d&fromItem=",
|
||||||
|
// endpoint, tc.usrBjj, limit,
|
||||||
|
// )
|
||||||
|
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
// assert.NoError(t, err)
|
||||||
|
// assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
|
||||||
|
// Get by tokenID
|
||||||
|
fetchedTxs = []testTx{}
|
||||||
|
limit = 5
|
||||||
|
tokenID := tc.allTxs[0].Token.TokenID
|
||||||
|
path = fmt.Sprintf(
|
||||||
|
"%s?tokenId=%d&limit=%d&fromItem=",
|
||||||
|
endpoint, tokenID, limit,
|
||||||
|
)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
tokenIDTxs := []testTx{}
|
||||||
|
for i := 0; i < len(tc.allTxs); i++ {
|
||||||
|
if tc.allTxs[i].Token.TokenID == tokenID {
|
||||||
|
tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
|
||||||
|
// idx
|
||||||
|
fetchedTxs = []testTx{}
|
||||||
|
limit = 4
|
||||||
|
idx := tc.allTxs[0].ToIdx
|
||||||
|
path = fmt.Sprintf(
|
||||||
|
"%s?accountIndex=%s&limit=%d&fromItem=",
|
||||||
|
endpoint, idx, limit,
|
||||||
|
)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
idxTxs := []testTx{}
|
||||||
|
for i := 0; i < len(tc.allTxs); i++ {
|
||||||
|
if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
|
||||||
|
tc.allTxs[i].ToIdx[6:] == idx[6:] {
|
||||||
|
idxTxs = append(idxTxs, tc.allTxs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
|
||||||
|
// batchNum
|
||||||
|
fetchedTxs = []testTx{}
|
||||||
|
limit = 3
|
||||||
|
batchNum := tc.allTxs[0].BatchNum
|
||||||
|
path = fmt.Sprintf(
|
||||||
|
"%s?batchNum=%d&limit=%d&fromItem=",
|
||||||
|
endpoint, *batchNum, limit,
|
||||||
|
)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
batchNumTxs := []testTx{}
|
||||||
|
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{
|
||||||
|
// Uncomment once test gen is fixed
|
||||||
|
// common.TxTypeExit,
|
||||||
|
// common.TxTypeTransfer,
|
||||||
|
// common.TxTypeDeposit,
|
||||||
|
common.TxTypeCreateAccountDeposit,
|
||||||
|
// common.TxTypeCreateAccountDepositTransfer,
|
||||||
|
// common.TxTypeDepositTransfer,
|
||||||
|
common.TxTypeForceTransfer,
|
||||||
|
// common.TxTypeForceExit,
|
||||||
|
// common.TxTypeTransferToEthAddr,
|
||||||
|
// common.TxTypeTransferToBJJ,
|
||||||
|
}
|
||||||
|
for _, txType := range txTypes {
|
||||||
|
fetchedTxs = []testTx{}
|
||||||
|
limit = 2
|
||||||
|
path = fmt.Sprintf(
|
||||||
|
"%s?type=%s&limit=%d&fromItem=",
|
||||||
|
endpoint, txType, limit,
|
||||||
|
)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
txTypeTxs := []testTx{}
|
||||||
|
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 = []testTx{}
|
||||||
|
limit = 1
|
||||||
|
path = fmt.Sprintf(
|
||||||
|
"%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
|
||||||
|
endpoint, *batchNum, tokenID, limit,
|
||||||
|
)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
mixedTxs := []testTx{}
|
||||||
|
for i := 0; i < len(tc.allTxs); i++ {
|
||||||
|
if tc.allTxs[i].BatchNum != nil {
|
||||||
|
if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
|
||||||
|
mixedTxs = append(mixedTxs, tc.allTxs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
|
||||||
|
// All, in reverse order
|
||||||
|
fetchedTxs = []testTx{}
|
||||||
|
limit = 5
|
||||||
|
path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
flipedTxs := []testTx{}
|
||||||
|
for i := 0; i < len(tc.allTxs); i++ {
|
||||||
|
flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
|
||||||
|
}
|
||||||
|
assertHistoryTxAPIs(t, flipedTxs, 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&fromItem=999999", endpoint)
|
||||||
|
err = doBadReq("GET", path, nil, 404)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetHistoryTx(t *testing.T) {
|
||||||
|
// Get all txs by their ID
|
||||||
|
endpoint := apiURL + "transactions-history/"
|
||||||
|
fetchedTxs := []testTx{}
|
||||||
|
for _, tx := range tc.allTxs {
|
||||||
|
fetchedTx := testTx{}
|
||||||
|
err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
fetchedTxs = append(fetchedTxs, fetchedTx)
|
||||||
|
}
|
||||||
|
assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
|
||||||
|
// 400
|
||||||
|
err := doBadReq("GET", endpoint+"0x001", nil, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 404
|
||||||
|
err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertHistoryTxAPIs(t *testing.T, expected, actual []testTx) {
|
||||||
|
require.Equal(t, len(expected), len(actual))
|
||||||
|
for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
|
||||||
|
actual[i].ItemID = 0
|
||||||
|
actual[i].Token.ItemID = 0
|
||||||
|
assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
|
||||||
|
expected[i].Timestamp = actual[i].Timestamp
|
||||||
|
if expected[i].Token.USDUpdate == nil {
|
||||||
|
assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
|
||||||
|
expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
|
||||||
|
}
|
||||||
|
test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
|
||||||
|
if expected[i].L2Info != nil {
|
||||||
|
test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
|
||||||
|
} else {
|
||||||
|
test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected[i], actual[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
171
api/txspool.go
Normal file
171
api/txspool.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/hermeznetwork/hermez-node/apitypes"
|
||||||
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db/l2db"
|
||||||
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 := receivedTx.toPoolL2TxWrite()
|
||||||
|
if err := verifyPoolL2TxWrite(writeTx); 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
|
||||||
|
tx, err := l2.GetTxAPI(txID)
|
||||||
|
if err != nil {
|
||||||
|
retSQLErr(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Build succesfull response
|
||||||
|
c.JSON(http.StatusOK, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
type receivedPoolTx struct {
|
||||||
|
TxID common.TxID `json:"id" binding:"required"`
|
||||||
|
Type common.TxType `json:"type" binding:"required"`
|
||||||
|
TokenID common.TokenID `json:"tokenId"`
|
||||||
|
FromIdx apitypes.StrHezIdx `json:"fromAccountIndex" binding:"required"`
|
||||||
|
ToIdx *apitypes.StrHezIdx `json:"toAccountIndex"`
|
||||||
|
ToEthAddr *apitypes.StrHezEthAddr `json:"toHezEthereumAddress"`
|
||||||
|
ToBJJ *apitypes.StrHezBJJ `json:"toBjj"`
|
||||||
|
Amount apitypes.StrBigInt `json:"amount" binding:"required"`
|
||||||
|
Fee common.FeeSelector `json:"fee"`
|
||||||
|
Nonce common.Nonce `json:"nonce"`
|
||||||
|
Signature babyjub.SignatureComp `json:"signature" binding:"required"`
|
||||||
|
RqFromIdx *apitypes.StrHezIdx `json:"requestFromAccountIndex"`
|
||||||
|
RqToIdx *apitypes.StrHezIdx `json:"requestToAccountIndex"`
|
||||||
|
RqToEthAddr *apitypes.StrHezEthAddr `json:"requestToHezEthereumAddress"`
|
||||||
|
RqToBJJ *apitypes.StrHezBJJ `json:"requestToBjj"`
|
||||||
|
RqTokenID *common.TokenID `json:"requestTokenId"`
|
||||||
|
RqAmount *apitypes.StrBigInt `json:"requestAmount"`
|
||||||
|
RqFee *common.FeeSelector `json:"requestFee"`
|
||||||
|
RqNonce *common.Nonce `json:"requestNonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tx *receivedPoolTx) toPoolL2TxWrite() *l2db.PoolL2TxWrite {
|
||||||
|
f := new(big.Float).SetInt((*big.Int)(&tx.Amount))
|
||||||
|
amountF, _ := f.Float64()
|
||||||
|
return &l2db.PoolL2TxWrite{
|
||||||
|
TxID: tx.TxID,
|
||||||
|
FromIdx: common.Idx(tx.FromIdx),
|
||||||
|
ToIdx: (*common.Idx)(tx.ToIdx),
|
||||||
|
ToEthAddr: (*ethCommon.Address)(tx.ToEthAddr),
|
||||||
|
ToBJJ: (*babyjub.PublicKey)(tx.ToBJJ),
|
||||||
|
TokenID: tx.TokenID,
|
||||||
|
Amount: (*big.Int)(&tx.Amount),
|
||||||
|
AmountFloat: amountF,
|
||||||
|
Fee: tx.Fee,
|
||||||
|
Nonce: tx.Nonce,
|
||||||
|
State: common.PoolL2TxStatePending,
|
||||||
|
Signature: tx.Signature,
|
||||||
|
RqFromIdx: (*common.Idx)(tx.RqFromIdx),
|
||||||
|
RqToIdx: (*common.Idx)(tx.RqToIdx),
|
||||||
|
RqToEthAddr: (*ethCommon.Address)(tx.RqToEthAddr),
|
||||||
|
RqToBJJ: (*babyjub.PublicKey)(tx.RqToBJJ),
|
||||||
|
RqTokenID: tx.RqTokenID,
|
||||||
|
RqAmount: (*big.Int)(tx.RqAmount),
|
||||||
|
RqFee: tx.RqFee,
|
||||||
|
RqNonce: tx.RqNonce,
|
||||||
|
Type: tx.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyPoolL2TxWrite(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
|
||||||
|
}
|
||||||
|
// Get public key
|
||||||
|
account, err := s.GetAccount(poolTx.FromIdx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check signature
|
||||||
|
if !poolTx.VerifySignature(account.PublicKey) {
|
||||||
|
return errors.New("wrong signature")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
272
api/txspool_test.go
Normal file
272
api/txspool_test.go
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db/historydb"
|
||||||
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testPoolTxReceive is a struct to be used to assert the response
|
||||||
|
// of GET /transactions-pool/:id
|
||||||
|
type testPoolTxReceive struct {
|
||||||
|
TxID common.TxID `json:"id"`
|
||||||
|
Type common.TxType `json:"type"`
|
||||||
|
FromIdx string `json:"fromAccountIndex"`
|
||||||
|
FromEthAddr *string `json:"fromHezEthereumAddress"`
|
||||||
|
FromBJJ *string `json:"fromBJJ"`
|
||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
BatchNum *common.BatchNum `json:"batchNum"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Token historydb.TokenWithUSD `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// testPoolTxSend is a struct to be used as a JSON body
|
||||||
|
// when testing POST /transactions-pool
|
||||||
|
type testPoolTxSend 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 genTestPoolTx(accs []common.Account, privKs []babyjub.PrivateKey, tokens []historydb.TokenWithUSD) (poolTxsToSend []testPoolTxSend, poolTxsToReceive []testPoolTxReceive) {
|
||||||
|
// Generate common.PoolL2Tx
|
||||||
|
// WARNING: this should be replaced once til 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 = privKs[0].SignPoseidon(h).Compress()
|
||||||
|
poolTxs = append(poolTxs, poolTx)
|
||||||
|
// Transform to API formats
|
||||||
|
poolTxsToSend = []testPoolTxSend{}
|
||||||
|
poolTxsToReceive = []testPoolTxReceive{}
|
||||||
|
for _, poolTx := range poolTxs {
|
||||||
|
fmt.Println(poolTx)
|
||||||
|
// common.PoolL2Tx ==> testPoolTxSend
|
||||||
|
token := getTokenByID(poolTx.TokenID, tokens)
|
||||||
|
genSendTx := testPoolTxSend{
|
||||||
|
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 ==> testPoolTxReceive
|
||||||
|
genReceiveTx := testPoolTxReceive{
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
fromAcc := getAccountByIdx(poolTx.ToIdx, accs)
|
||||||
|
fromAddr := ethAddrToHez(fromAcc.EthAddr)
|
||||||
|
genReceiveTx.FromEthAddr = &fromAddr
|
||||||
|
fromBjj := bjjToString(fromAcc.PublicKey)
|
||||||
|
genReceiveTx.FromBJJ = &fromBjj
|
||||||
|
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
|
||||||
|
} else if poolTx.ToIdx > 255 {
|
||||||
|
acc := getAccountByIdx(poolTx.ToIdx, accs)
|
||||||
|
addr := ethAddrToHez(acc.EthAddr)
|
||||||
|
genReceiveTx.ToEthAddr = &addr
|
||||||
|
}
|
||||||
|
if poolTx.ToBJJ != nil {
|
||||||
|
toBJJ := bjjToString(poolTx.ToBJJ)
|
||||||
|
genSendTx.ToBJJ = &toBJJ
|
||||||
|
genReceiveTx.ToBJJ = &toBJJ
|
||||||
|
} else if poolTx.ToIdx > 255 {
|
||||||
|
acc := getAccountByIdx(poolTx.ToIdx, accs)
|
||||||
|
bjj := bjjToString(acc.PublicKey)
|
||||||
|
genReceiveTx.ToBJJ = &bjj
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return poolTxsToSend, poolTxsToReceive
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 := testPoolTxReceive{}
|
||||||
|
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 testPoolTxReceive) {
|
||||||
|
// state should be pending
|
||||||
|
assert.Equal(t, common.PoolL2TxStatePending, actual.State)
|
||||||
|
expected.State = actual.State
|
||||||
|
actual.Token.ItemID = 0
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
@@ -71,6 +72,19 @@ func (b BigIntStr) Value() (driver.Value, error) {
|
|||||||
return base64.StdEncoding.EncodeToString(bigInt.Bytes()), nil
|
return base64.StdEncoding.EncodeToString(bigInt.Bytes()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrBigInt is used to unmarshal BigIntStr directly into an alias of big.Int
|
||||||
|
type StrBigInt big.Int
|
||||||
|
|
||||||
|
// UnmarshalText unmarshals a StrBigInt
|
||||||
|
func (s *StrBigInt) UnmarshalText(text []byte) error {
|
||||||
|
bi, ok := (*big.Int)(s).SetString(string(text), 10)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("could not unmarshal %s into a StrBigInt", text)
|
||||||
|
}
|
||||||
|
*s = StrBigInt(*bi)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CollectedFees is used to retrieve common.batch.CollectedFee from the DB
|
// CollectedFees is used to retrieve common.batch.CollectedFee from the DB
|
||||||
type CollectedFees map[common.TokenID]BigIntStr
|
type CollectedFees map[common.TokenID]BigIntStr
|
||||||
|
|
||||||
@@ -127,6 +141,20 @@ func (a HezEthAddr) Value() (driver.Value, error) {
|
|||||||
return ethAddr.Value()
|
return ethAddr.Value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrHezEthAddr is used to unmarshal HezEthAddr directly into an alias of ethCommon.Address
|
||||||
|
type StrHezEthAddr ethCommon.Address
|
||||||
|
|
||||||
|
// UnmarshalText unmarshals a StrHezEthAddr
|
||||||
|
func (s *StrHezEthAddr) UnmarshalText(text []byte) error {
|
||||||
|
withoutHez := strings.TrimPrefix(string(text), "hez:")
|
||||||
|
var addr ethCommon.Address
|
||||||
|
if err := addr.UnmarshalText([]byte(withoutHez)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*s = StrHezEthAddr(addr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// HezBJJ is used to scan/value *babyjub.PublicKey directly into strings that follow the BJJ public key hez fotmat (^hez:[A-Za-z0-9_-]{44}$) from/to sql DBs.
|
// HezBJJ is used to scan/value *babyjub.PublicKey directly into strings that follow the BJJ public key hez fotmat (^hez:[A-Za-z0-9_-]{44}$) from/to sql DBs.
|
||||||
// It assumes that *babyjub.PublicKey are inserted/fetched to/from the DB using the default Scan/Value interface
|
// It assumes that *babyjub.PublicKey are inserted/fetched to/from the DB using the default Scan/Value interface
|
||||||
type HezBJJ string
|
type HezBJJ string
|
||||||
@@ -143,12 +171,11 @@ func NewHezBJJ(bjj *babyjub.PublicKey) HezBJJ {
|
|||||||
return HezBJJ("hez:" + base64.RawURLEncoding.EncodeToString(bjjSum))
|
return HezBJJ("hez:" + base64.RawURLEncoding.EncodeToString(bjjSum))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToBJJ returns a *babyjub.PublicKey created from HezBJJ
|
func hezStrToBJJ(s string) (*babyjub.PublicKey, error) {
|
||||||
func (b HezBJJ) ToBJJ() (*babyjub.PublicKey, error) {
|
|
||||||
const decodedLen = 33
|
const decodedLen = 33
|
||||||
const encodedLen = 44
|
const encodedLen = 44
|
||||||
formatErr := errors.New("invalid BJJ format. Must follow this regex: ^hez:[A-Za-z0-9_-]{44}$")
|
formatErr := errors.New("invalid BJJ format. Must follow this regex: ^hez:[A-Za-z0-9_-]{44}$")
|
||||||
encoded := strings.TrimPrefix(string(b), "hez:")
|
encoded := strings.TrimPrefix(s, "hez:")
|
||||||
if len(encoded) != encodedLen {
|
if len(encoded) != encodedLen {
|
||||||
return nil, formatErr
|
return nil, formatErr
|
||||||
}
|
}
|
||||||
@@ -172,6 +199,11 @@ func (b HezBJJ) ToBJJ() (*babyjub.PublicKey, error) {
|
|||||||
return bjjComp.Decompress()
|
return bjjComp.Decompress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToBJJ returns a *babyjub.PublicKey created from HezBJJ
|
||||||
|
func (b HezBJJ) ToBJJ() (*babyjub.PublicKey, error) {
|
||||||
|
return hezStrToBJJ(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
// Scan implements Scanner for database/sql
|
// Scan implements Scanner for database/sql
|
||||||
func (b *HezBJJ) Scan(src interface{}) error {
|
func (b *HezBJJ) Scan(src interface{}) error {
|
||||||
bjj := &babyjub.PublicKey{}
|
bjj := &babyjub.PublicKey{}
|
||||||
@@ -193,3 +225,39 @@ func (b HezBJJ) Value() (driver.Value, error) {
|
|||||||
}
|
}
|
||||||
return bjj.Value()
|
return bjj.Value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrHezBJJ is used to unmarshal HezBJJ directly into an alias of babyjub.PublicKey
|
||||||
|
type StrHezBJJ babyjub.PublicKey
|
||||||
|
|
||||||
|
// UnmarshalText unmarshals a StrHezBJJ
|
||||||
|
func (s *StrHezBJJ) UnmarshalText(text []byte) error {
|
||||||
|
bjj, err := hezStrToBJJ(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*s = StrHezBJJ(*bjj)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HezIdx is used to value common.Idx directly into strings that follow the Idx key hez fotmat (hez:tokenSymbol:idx) to sql DBs.
|
||||||
|
// Note that this can only be used to insert to DB since there is no way to automaticaly read from the DB since it needs the tokenSymbol
|
||||||
|
type HezIdx string
|
||||||
|
|
||||||
|
// StrHezIdx is used to unmarshal HezIdx directly into an alias of common.Idx
|
||||||
|
type StrHezIdx common.Idx
|
||||||
|
|
||||||
|
// UnmarshalText unmarshals a StrHezIdx
|
||||||
|
func (s *StrHezIdx) UnmarshalText(text []byte) error {
|
||||||
|
withoutHez := strings.TrimPrefix(string(text), "hez:")
|
||||||
|
splitted := strings.Split(withoutHez, ":")
|
||||||
|
const expectedLen = 2
|
||||||
|
if len(splitted) != expectedLen {
|
||||||
|
return fmt.Errorf("can not unmarshal %s into StrHezIdx", text)
|
||||||
|
}
|
||||||
|
idxInt, err := strconv.Atoi(splitted[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*s = StrHezIdx(common.Idx(idxInt))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package apitypes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
dbUtils "github.com/hermeznetwork/hermez-node/db"
|
dbUtils "github.com/hermeznetwork/hermez-node/db"
|
||||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
_ "github.com/mattn/go-sqlite3" //nolint sqlite driver
|
_ "github.com/mattn/go-sqlite3" //nolint sqlite driver
|
||||||
@@ -123,6 +125,53 @@ func TestBigIntStrScannerValuer(t *testing.T) {
|
|||||||
assert.Nil(t, toMeddlerNil.I)
|
assert.Nil(t, toMeddlerNil.I)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStrBigInt(t *testing.T) {
|
||||||
|
type testStrBigInt struct {
|
||||||
|
I StrBigInt
|
||||||
|
}
|
||||||
|
from := []byte(`{"I":"4"}`)
|
||||||
|
to := &testStrBigInt{}
|
||||||
|
assert.NoError(t, json.Unmarshal(from, to))
|
||||||
|
assert.Equal(t, big.NewInt(4), (*big.Int)(&to.I))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStrHezEthAddr(t *testing.T) {
|
||||||
|
type testStrHezEthAddr struct {
|
||||||
|
I StrHezEthAddr
|
||||||
|
}
|
||||||
|
withoutHez := "0xaa942cfcd25ad4d90a62358b0dd84f33b398262a"
|
||||||
|
from := []byte(`{"I":"hez:` + withoutHez + `"}`)
|
||||||
|
var addr ethCommon.Address
|
||||||
|
if err := addr.UnmarshalText([]byte(withoutHez)); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
to := &testStrHezEthAddr{}
|
||||||
|
assert.NoError(t, json.Unmarshal(from, to))
|
||||||
|
assert.Equal(t, addr, ethCommon.Address(to.I))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStrHezBJJ(t *testing.T) {
|
||||||
|
type testStrHezBJJ struct {
|
||||||
|
I StrHezBJJ
|
||||||
|
}
|
||||||
|
priv := babyjub.NewRandPrivKey()
|
||||||
|
hezBjj := NewHezBJJ(priv.Public())
|
||||||
|
from := []byte(`{"I":"` + hezBjj + `"}`)
|
||||||
|
to := &testStrHezBJJ{}
|
||||||
|
assert.NoError(t, json.Unmarshal(from, to))
|
||||||
|
assert.Equal(t, priv.Public(), (*babyjub.PublicKey)(&to.I))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStrHezIdx(t *testing.T) {
|
||||||
|
type testStrHezIdx struct {
|
||||||
|
I StrHezIdx
|
||||||
|
}
|
||||||
|
from := []byte(`{"I":"hez:foo:4"}`)
|
||||||
|
to := &testStrHezIdx{}
|
||||||
|
assert.NoError(t, json.Unmarshal(from, to))
|
||||||
|
assert.Equal(t, common.Idx(4), common.Idx(to.I))
|
||||||
|
}
|
||||||
|
|
||||||
func TestHezEthAddr(t *testing.T) {
|
func TestHezEthAddr(t *testing.T) {
|
||||||
// Clean DB
|
// Clean DB
|
||||||
_, err := db.Exec("delete from test")
|
_, err := db.Exec("delete from test")
|
||||||
|
|||||||
@@ -651,15 +651,16 @@ func (hdb *HistoryDB) addTxs(d meddler.DB, txs []txWrite) error {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// GetHistoryTx returns a tx from the DB given a TxID
|
// GetHistoryTx returns a tx from the DB given a TxID
|
||||||
func (hdb *HistoryDB) GetHistoryTx(txID common.TxID) (*HistoryTx, error) {
|
func (hdb *HistoryDB) GetHistoryTx(txID common.TxID) (*TxAPI, error) {
|
||||||
tx := &HistoryTx{}
|
tx := &TxAPI{}
|
||||||
err := meddler.QueryRow(
|
err := meddler.QueryRow(
|
||||||
hdb.db, tx, `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position,
|
hdb.db, tx, `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position,
|
||||||
tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd,
|
hez_idx(tx.from_idx, token.symbol) AS from_idx, tx.from_eth_addr, tx.from_bjj,
|
||||||
|
hez_idx(tx.to_idx, token.symbol) AS to_idx, tx.to_eth_addr, tx.to_bjj,
|
||||||
|
tx.amount, tx.token_id, tx.amount_usd,
|
||||||
tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin,
|
tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin,
|
||||||
tx.from_eth_addr, tx.from_bjj, tx.load_amount,
|
tx.load_amount, tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
|
||||||
tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
|
token.token_id, token.item_id AS token_item_id, token.eth_block_num AS token_block,
|
||||||
token.token_id, token.eth_block_num AS token_block,
|
|
||||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
||||||
token.usd_update, block.timestamp
|
token.usd_update, block.timestamp
|
||||||
FROM tx INNER JOIN token ON tx.token_id = token.token_id
|
FROM tx INNER JOIN token ON tx.token_id = token.token_id
|
||||||
@@ -675,18 +676,19 @@ func (hdb *HistoryDB) GetHistoryTxs(
|
|||||||
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
|
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
|
||||||
tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType,
|
tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType,
|
||||||
fromItem, limit *uint, order string,
|
fromItem, limit *uint, order string,
|
||||||
) ([]HistoryTx, *db.Pagination, error) {
|
) ([]TxAPI, *db.Pagination, error) {
|
||||||
if ethAddr != nil && bjj != nil {
|
if ethAddr != nil && bjj != nil {
|
||||||
return nil, nil, errors.New("ethAddr and bjj are incompatible")
|
return nil, nil, errors.New("ethAddr and bjj are incompatible")
|
||||||
}
|
}
|
||||||
var query string
|
var query string
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
queryStr := `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position,
|
queryStr := `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position,
|
||||||
tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd,
|
hez_idx(tx.from_idx, token.symbol) AS from_idx, tx.from_eth_addr, tx.from_bjj,
|
||||||
|
hez_idx(tx.to_idx, token.symbol) AS to_idx, tx.to_eth_addr, tx.to_bjj,
|
||||||
|
tx.amount, tx.token_id, tx.amount_usd,
|
||||||
tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin,
|
tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin,
|
||||||
tx.from_eth_addr, tx.from_bjj, tx.load_amount,
|
tx.load_amount, tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
|
||||||
tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
|
token.token_id, token.item_id AS token_item_id, token.eth_block_num AS token_block,
|
||||||
token.token_id, token.eth_block_num AS token_block,
|
|
||||||
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
|
||||||
token.usd_update, block.timestamp, count(*) OVER() AS total_items,
|
token.usd_update, block.timestamp, count(*) OVER() AS total_items,
|
||||||
MIN(tx.item_id) OVER() AS first_item, MAX(tx.item_id) OVER() AS last_item
|
MIN(tx.item_id) OVER() AS first_item, MAX(tx.item_id) OVER() AS last_item
|
||||||
@@ -783,11 +785,11 @@ func (hdb *HistoryDB) GetHistoryTxs(
|
|||||||
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
||||||
query = hdb.db.Rebind(queryStr)
|
query = hdb.db.Rebind(queryStr)
|
||||||
log.Debug(query)
|
log.Debug(query)
|
||||||
txsPtrs := []*HistoryTx{}
|
txsPtrs := []*TxAPI{}
|
||||||
if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
|
if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
txs := db.SlicePtrsToSlice(txsPtrs).([]HistoryTx)
|
txs := db.SlicePtrsToSlice(txsPtrs).([]TxAPI)
|
||||||
if len(txs) == 0 {
|
if len(txs) == 0 {
|
||||||
return nil, nil, sql.ErrNoRows
|
return nil, nil, sql.ErrNoRows
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package historydb
|
package historydb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,28 +12,29 @@ import (
|
|||||||
"github.com/iden3/go-merkletree"
|
"github.com/iden3/go-merkletree"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HistoryTx is a representation of a generic Tx with additional information
|
// TxAPI is a representation of a generic Tx with additional information
|
||||||
// required by the API, and extracted by joining block and token tables
|
// required by the API, and extracted by joining block and token tables
|
||||||
type HistoryTx struct {
|
type TxAPI struct {
|
||||||
// Generic
|
// Generic
|
||||||
IsL1 bool `meddler:"is_l1"`
|
IsL1 bool `meddler:"is_l1"`
|
||||||
TxID common.TxID `meddler:"id"`
|
TxID common.TxID `meddler:"id"`
|
||||||
ItemID int `meddler:"item_id"`
|
ItemID int `meddler:"item_id"`
|
||||||
Type common.TxType `meddler:"type"`
|
Type common.TxType `meddler:"type"`
|
||||||
Position int `meddler:"position"`
|
Position int `meddler:"position"`
|
||||||
FromIdx *common.Idx `meddler:"from_idx"`
|
FromIdx *apitypes.HezIdx `meddler:"from_idx"`
|
||||||
ToIdx common.Idx `meddler:"to_idx"`
|
FromEthAddr *apitypes.HezEthAddr `meddler:"from_eth_addr"`
|
||||||
Amount *big.Int `meddler:"amount,bigint"`
|
FromBJJ *apitypes.HezBJJ `meddler:"from_bjj"`
|
||||||
|
ToIdx apitypes.HezIdx `meddler:"to_idx"`
|
||||||
|
ToEthAddr *apitypes.HezEthAddr `meddler:"to_eth_addr"`
|
||||||
|
ToBJJ *apitypes.HezBJJ `meddler:"to_bjj"`
|
||||||
|
Amount apitypes.BigIntStr `meddler:"amount"`
|
||||||
HistoricUSD *float64 `meddler:"amount_usd"`
|
HistoricUSD *float64 `meddler:"amount_usd"`
|
||||||
BatchNum *common.BatchNum `meddler:"batch_num"` // batchNum in which this tx was forged. If the tx is L2, this must be != 0
|
BatchNum *common.BatchNum `meddler:"batch_num"` // 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
|
EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum Block Number in which this L1Tx was added to the queue
|
||||||
// L1
|
// L1
|
||||||
ToForgeL1TxsNum *int64 `meddler:"to_forge_l1_txs_num"` // toForgeL1TxsNum in which the tx was forged / will be forged
|
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
|
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"`
|
LoadAmount *apitypes.BigIntStr `meddler:"load_amount"`
|
||||||
FromBJJ *babyjub.PublicKey `meddler:"from_bjj"`
|
|
||||||
LoadAmount *big.Int `meddler:"load_amount,bigintnull"`
|
|
||||||
// LoadAmountFloat *float64 `meddler:"load_amount_f"`
|
|
||||||
HistoricLoadAmountUSD *float64 `meddler:"load_amount_usd"`
|
HistoricLoadAmountUSD *float64 `meddler:"load_amount_usd"`
|
||||||
// L2
|
// L2
|
||||||
Fee *common.FeeSelector `meddler:"fee"`
|
Fee *common.FeeSelector `meddler:"fee"`
|
||||||
@@ -44,6 +46,7 @@ type HistoryTx struct {
|
|||||||
FirstItem int `meddler:"first_item"`
|
FirstItem int `meddler:"first_item"`
|
||||||
LastItem int `meddler:"last_item"`
|
LastItem int `meddler:"last_item"`
|
||||||
TokenID common.TokenID `meddler:"token_id"`
|
TokenID common.TokenID `meddler:"token_id"`
|
||||||
|
TokenItemID int `meddler:"token_item_id"`
|
||||||
TokenEthBlockNum int64 `meddler:"token_block"`
|
TokenEthBlockNum int64 `meddler:"token_block"`
|
||||||
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
||||||
TokenName string `meddler:"name"`
|
TokenName string `meddler:"name"`
|
||||||
@@ -53,6 +56,58 @@ type HistoryTx struct {
|
|||||||
TokenUSDUpdate *time.Time `meddler:"usd_update"`
|
TokenUSDUpdate *time.Time `meddler:"usd_update"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON is used to neast some of the fields of TxAPI
|
||||||
|
// without the need of auxiliar structs
|
||||||
|
func (tx TxAPI) MarshalJSON() ([]byte, error) {
|
||||||
|
jsonTx := map[string]interface{}{
|
||||||
|
"id": tx.TxID,
|
||||||
|
"itemId": tx.ItemID,
|
||||||
|
"type": tx.Type,
|
||||||
|
"position": tx.Position,
|
||||||
|
"fromAccountIndex": tx.FromIdx,
|
||||||
|
"fromHezEthereumAddress": tx.FromEthAddr,
|
||||||
|
"fromBJJ": tx.FromBJJ,
|
||||||
|
"toAccountIndex": tx.ToIdx,
|
||||||
|
"toHezEthereumAddress": tx.ToEthAddr,
|
||||||
|
"toBJJ": tx.ToBJJ,
|
||||||
|
"amount": tx.Amount,
|
||||||
|
"batchNum": tx.BatchNum,
|
||||||
|
"historicUSD": tx.HistoricUSD,
|
||||||
|
"timestamp": tx.Timestamp,
|
||||||
|
"L1Info": nil,
|
||||||
|
"L2Info": nil,
|
||||||
|
"token": map[string]interface{}{
|
||||||
|
"id": tx.TokenID,
|
||||||
|
"itemId": tx.TokenItemID,
|
||||||
|
"ethereumBlockNum": tx.TokenEthBlockNum,
|
||||||
|
"ethereumAddress": tx.TokenEthAddr,
|
||||||
|
"name": tx.TokenName,
|
||||||
|
"symbol": tx.TokenSymbol,
|
||||||
|
"decimals": tx.TokenDecimals,
|
||||||
|
"USD": tx.TokenUSD,
|
||||||
|
"fiatUpdate": tx.TokenUSDUpdate,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if tx.IsL1 {
|
||||||
|
jsonTx["L1orL2"] = "L1"
|
||||||
|
jsonTx["L1Info"] = map[string]interface{}{
|
||||||
|
"toForgeL1TransactionsNum": tx.ToForgeL1TxsNum,
|
||||||
|
"userOrigin": tx.UserOrigin,
|
||||||
|
"loadAmount": tx.LoadAmount,
|
||||||
|
"historicLoadAmountUSD": tx.HistoricLoadAmountUSD,
|
||||||
|
"ethereumBlockNum": tx.EthBlockNum,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jsonTx["L1orL2"] = "L2"
|
||||||
|
jsonTx["L2Info"] = map[string]interface{}{
|
||||||
|
"fee": tx.Fee,
|
||||||
|
"historicFeeUSD": tx.HistoricFeeUSD,
|
||||||
|
"nonce": tx.Nonce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(jsonTx)
|
||||||
|
}
|
||||||
|
|
||||||
// txWrite is an representatiion that merges common.L1Tx and common.L2Tx
|
// txWrite is an representatiion that merges common.L1Tx and common.L2Tx
|
||||||
// in order to perform inserts into tx table
|
// in order to perform inserts into tx table
|
||||||
type txWrite struct {
|
type txWrite struct {
|
||||||
|
|||||||
@@ -114,30 +114,41 @@ func (l2db *L2DB) AddTxTest(tx *common.PoolL2Tx) error {
|
|||||||
return meddler.Insert(l2db.db, "tx_pool", insertTx)
|
return meddler.Insert(l2db.db, "tx_pool", insertTx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectPoolTxRead select part of queries to get PoolL2TxRead
|
// selectPoolTxAPI select part of queries to get PoolL2TxRead
|
||||||
const selectPoolTxRead = `SELECT tx_pool.tx_id, tx_pool.from_idx, tx_pool.to_idx, tx_pool.to_eth_addr,
|
const selectPoolTxAPI = `SELECT tx_pool.tx_id, hez_idx(tx_pool.from_idx, token.symbol) AS from_idx, tx_pool.from_eth_addr,
|
||||||
|
tx_pool.from_bjj, hez_idx(tx_pool.to_idx, token.symbol) AS to_idx, tx_pool.to_eth_addr,
|
||||||
tx_pool.to_bjj, tx_pool.token_id, tx_pool.amount, tx_pool.fee, tx_pool.nonce,
|
tx_pool.to_bjj, tx_pool.token_id, tx_pool.amount, tx_pool.fee, tx_pool.nonce,
|
||||||
tx_pool.state, tx_pool.signature, tx_pool.timestamp, tx_pool.batch_num, tx_pool.rq_from_idx,
|
tx_pool.state, tx_pool.signature, tx_pool.timestamp, tx_pool.batch_num, hez_idx(tx_pool.rq_from_idx, token.symbol) AS rq_from_idx,
|
||||||
tx_pool.rq_to_idx, tx_pool.rq_to_eth_addr, tx_pool.rq_to_bjj, tx_pool.rq_token_id, tx_pool.rq_amount,
|
hez_idx(tx_pool.rq_to_idx, token.symbol) AS rq_to_idx, tx_pool.rq_to_eth_addr, tx_pool.rq_to_bjj, tx_pool.rq_token_id, tx_pool.rq_amount,
|
||||||
tx_pool.rq_fee, tx_pool.rq_nonce, tx_pool.tx_type,
|
tx_pool.rq_fee, tx_pool.rq_nonce, tx_pool.tx_type,
|
||||||
token.eth_block_num, token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update
|
token.item_id AS token_item_id, token.eth_block_num, token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update
|
||||||
FROM tx_pool INNER JOIN token ON tx_pool.token_id = token.token_id `
|
FROM tx_pool INNER JOIN token ON tx_pool.token_id = token.token_id `
|
||||||
|
|
||||||
// selectPoolTxCommon select part of queries to get common.PoolL2Tx
|
// selectPoolTxCommon select part of queries to get common.PoolL2Tx
|
||||||
const selectPoolTxCommon = `SELECT tx_pool.tx_id, tx_pool.from_idx, tx_pool.to_idx, tx_pool.to_eth_addr,
|
const selectPoolTxCommon = `SELECT tx_pool.tx_id, from_idx, to_idx, tx_pool.to_eth_addr,
|
||||||
tx_pool.to_bjj, tx_pool.token_id, tx_pool.amount, tx_pool.fee, tx_pool.nonce,
|
tx_pool.to_bjj, tx_pool.token_id, tx_pool.amount, tx_pool.fee, tx_pool.nonce,
|
||||||
tx_pool.state, tx_pool.signature, tx_pool.timestamp, tx_pool.rq_from_idx,
|
tx_pool.state, tx_pool.signature, tx_pool.timestamp, rq_from_idx,
|
||||||
tx_pool.rq_to_idx, tx_pool.rq_to_eth_addr, tx_pool.rq_to_bjj, tx_pool.rq_token_id, tx_pool.rq_amount,
|
rq_to_idx, tx_pool.rq_to_eth_addr, tx_pool.rq_to_bjj, tx_pool.rq_token_id, tx_pool.rq_amount,
|
||||||
tx_pool.rq_fee, tx_pool.rq_nonce, tx_pool.tx_type,
|
tx_pool.rq_fee, tx_pool.rq_nonce, tx_pool.tx_type,
|
||||||
fee_percentage(tx_pool.fee::NUMERIC) * token.usd * tx_pool.amount_f AS fee_usd, token.usd_update
|
fee_percentage(tx_pool.fee::NUMERIC) * token.usd * tx_pool.amount_f AS fee_usd, token.usd_update
|
||||||
FROM tx_pool INNER JOIN token ON tx_pool.token_id = token.token_id `
|
FROM tx_pool INNER JOIN token ON tx_pool.token_id = token.token_id `
|
||||||
|
|
||||||
// GetTx return the specified Tx
|
// GetTx return the specified Tx in common.PoolL2Tx format
|
||||||
func (l2db *L2DB) GetTx(txID common.TxID) (*PoolL2TxRead, error) {
|
func (l2db *L2DB) GetTx(txID common.TxID) (*common.PoolL2Tx, error) {
|
||||||
tx := new(PoolL2TxRead)
|
tx := new(common.PoolL2Tx)
|
||||||
return tx, meddler.QueryRow(
|
return tx, meddler.QueryRow(
|
||||||
l2db.db, tx,
|
l2db.db, tx,
|
||||||
selectPoolTxRead+"WHERE tx_id = $1;",
|
selectPoolTxCommon+"WHERE tx_id = $1;",
|
||||||
|
txID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxAPI return the specified Tx in PoolTxAPI format
|
||||||
|
func (l2db *L2DB) GetTxAPI(txID common.TxID) (*PoolTxAPI, error) {
|
||||||
|
tx := new(PoolTxAPI)
|
||||||
|
return tx, meddler.QueryRow(
|
||||||
|
l2db.db, tx,
|
||||||
|
selectPoolTxAPI+"WHERE tx_id = $1;",
|
||||||
txID,
|
txID,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/hermeznetwork/hermez-node/common"
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
dbUtils "github.com/hermeznetwork/hermez-node/db"
|
dbUtils "github.com/hermeznetwork/hermez-node/db"
|
||||||
"github.com/hermeznetwork/hermez-node/db/historydb"
|
"github.com/hermeznetwork/hermez-node/db/historydb"
|
||||||
@@ -94,81 +93,11 @@ func TestAddTxTest(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
fetchedTx, err := l2DB.GetTx(tx.TxID)
|
fetchedTx, err := l2DB.GetTx(tx.TxID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assertReadTx(t, commonToRead(tx, tokens), fetchedTx)
|
// assertReadTx(t, commonToRead(tx, tokens), fetchedTx)
|
||||||
|
assertTx(t, tx, fetchedTx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonToRead(commonTx *common.PoolL2Tx, tokens []common.Token) *PoolL2TxRead {
|
|
||||||
readTx := &PoolL2TxRead{
|
|
||||||
TxID: commonTx.TxID,
|
|
||||||
FromIdx: commonTx.FromIdx,
|
|
||||||
ToBJJ: commonTx.ToBJJ,
|
|
||||||
Amount: commonTx.Amount,
|
|
||||||
Fee: commonTx.Fee,
|
|
||||||
Nonce: commonTx.Nonce,
|
|
||||||
State: commonTx.State,
|
|
||||||
Signature: commonTx.Signature,
|
|
||||||
RqToBJJ: commonTx.RqToBJJ,
|
|
||||||
RqAmount: commonTx.RqAmount,
|
|
||||||
Type: commonTx.Type,
|
|
||||||
Timestamp: commonTx.Timestamp,
|
|
||||||
TokenID: commonTx.TokenID,
|
|
||||||
}
|
|
||||||
// token related fields
|
|
||||||
// find token
|
|
||||||
token := historydb.TokenWithUSD{}
|
|
||||||
for _, tkn := range tokensUSD {
|
|
||||||
if tkn.TokenID == readTx.TokenID {
|
|
||||||
token = tkn
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set token related fields
|
|
||||||
readTx.TokenEthBlockNum = token.EthBlockNum
|
|
||||||
readTx.TokenEthAddr = token.EthAddr
|
|
||||||
readTx.TokenName = token.Name
|
|
||||||
readTx.TokenSymbol = token.Symbol
|
|
||||||
readTx.TokenDecimals = token.Decimals
|
|
||||||
readTx.TokenUSD = token.USD
|
|
||||||
readTx.TokenUSDUpdate = token.USDUpdate
|
|
||||||
// nullable fields
|
|
||||||
if commonTx.ToIdx != 0 {
|
|
||||||
readTx.ToIdx = &commonTx.ToIdx
|
|
||||||
}
|
|
||||||
nilAddr := ethCommon.BigToAddress(big.NewInt(0))
|
|
||||||
if commonTx.ToEthAddr != nilAddr {
|
|
||||||
readTx.ToEthAddr = &commonTx.ToEthAddr
|
|
||||||
}
|
|
||||||
if commonTx.RqFromIdx != 0 {
|
|
||||||
readTx.RqFromIdx = &commonTx.RqFromIdx
|
|
||||||
}
|
|
||||||
if commonTx.RqToIdx != 0 { // if true, all Rq... fields must be different to nil
|
|
||||||
readTx.RqToIdx = &commonTx.RqToIdx
|
|
||||||
readTx.RqTokenID = &commonTx.RqTokenID
|
|
||||||
readTx.RqFee = &commonTx.RqFee
|
|
||||||
readTx.RqNonce = &commonTx.RqNonce
|
|
||||||
}
|
|
||||||
if commonTx.RqToEthAddr != nilAddr {
|
|
||||||
readTx.RqToEthAddr = &commonTx.RqToEthAddr
|
|
||||||
}
|
|
||||||
return readTx
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertReadTx(t *testing.T, expected, actual *PoolL2TxRead) {
|
|
||||||
// Check that timestamp has been set within the last 3 seconds
|
|
||||||
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
|
||||||
assert.GreaterOrEqual(t, time.Now().UTC().Unix(), actual.Timestamp.Unix())
|
|
||||||
expected.Timestamp = actual.Timestamp
|
|
||||||
// Check token related stuff
|
|
||||||
if expected.TokenUSDUpdate != nil {
|
|
||||||
// Check that TokenUSDUpdate has been set within the last 3 seconds
|
|
||||||
assert.Less(t, time.Now().UTC().Unix()-3, actual.TokenUSDUpdate.Unix())
|
|
||||||
assert.GreaterOrEqual(t, time.Now().UTC().Unix(), actual.TokenUSDUpdate.Unix())
|
|
||||||
expected.TokenUSDUpdate = actual.TokenUSDUpdate
|
|
||||||
}
|
|
||||||
assert.Equal(t, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertTx(t *testing.T, expected, actual *common.PoolL2Tx) {
|
func assertTx(t *testing.T, expected, actual *common.PoolL2Tx) {
|
||||||
// Check that timestamp has been set within the last 3 seconds
|
// Check that timestamp has been set within the last 3 seconds
|
||||||
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
||||||
@@ -400,13 +329,13 @@ func TestReorg(t *testing.T) {
|
|||||||
err := l2DB.Reorg(lastValidBatch)
|
err := l2DB.Reorg(lastValidBatch)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
for _, id := range reorgedTxIDs {
|
for _, id := range reorgedTxIDs {
|
||||||
tx, err := l2DB.GetTx(id)
|
tx, err := l2DB.GetTxAPI(id)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, tx.BatchNum)
|
assert.Nil(t, tx.BatchNum)
|
||||||
assert.Equal(t, common.PoolL2TxStatePending, tx.State)
|
assert.Equal(t, common.PoolL2TxStatePending, tx.State)
|
||||||
}
|
}
|
||||||
for _, id := range nonReorgedTxIDs {
|
for _, id := range nonReorgedTxIDs {
|
||||||
fetchedTx, err := l2DB.GetTx(id)
|
fetchedTx, err := l2DB.GetTxAPI(id)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, lastValidBatch, *fetchedTx.BatchNum)
|
assert.Equal(t, lastValidBatch, *fetchedTx.BatchNum)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package l2db
|
package l2db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/apitypes"
|
||||||
"github.com/hermeznetwork/hermez-node/common"
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
)
|
)
|
||||||
@@ -34,24 +36,26 @@ type PoolL2TxWrite struct {
|
|||||||
Type common.TxType `meddler:"tx_type"`
|
Type common.TxType `meddler:"tx_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PoolL2TxRead represents a L2 Tx pool with extra metadata used by the API
|
// PoolTxAPI represents a L2 Tx pool with extra metadata used by the API
|
||||||
type PoolL2TxRead struct {
|
type PoolTxAPI struct {
|
||||||
TxID common.TxID `meddler:"tx_id"`
|
TxID common.TxID `meddler:"tx_id"`
|
||||||
FromIdx common.Idx `meddler:"from_idx"`
|
FromIdx apitypes.HezIdx `meddler:"from_idx"`
|
||||||
ToIdx *common.Idx `meddler:"to_idx"`
|
FromEthAddr *apitypes.HezEthAddr `meddler:"from_eth_addr"`
|
||||||
ToEthAddr *ethCommon.Address `meddler:"to_eth_addr"`
|
FromBJJ *apitypes.HezBJJ `meddler:"from_bjj"`
|
||||||
ToBJJ *babyjub.PublicKey `meddler:"to_bjj"`
|
ToIdx *apitypes.HezIdx `meddler:"to_idx"`
|
||||||
Amount *big.Int `meddler:"amount,bigint"`
|
ToEthAddr *apitypes.HezEthAddr `meddler:"to_eth_addr"`
|
||||||
|
ToBJJ *apitypes.HezBJJ `meddler:"to_bjj"`
|
||||||
|
Amount apitypes.BigIntStr `meddler:"amount"`
|
||||||
Fee common.FeeSelector `meddler:"fee"`
|
Fee common.FeeSelector `meddler:"fee"`
|
||||||
Nonce common.Nonce `meddler:"nonce"`
|
Nonce common.Nonce `meddler:"nonce"`
|
||||||
State common.PoolL2TxState `meddler:"state"`
|
State common.PoolL2TxState `meddler:"state"`
|
||||||
Signature babyjub.SignatureComp `meddler:"signature"`
|
Signature babyjub.SignatureComp `meddler:"signature"`
|
||||||
RqFromIdx *common.Idx `meddler:"rq_from_idx"`
|
RqFromIdx *apitypes.HezIdx `meddler:"rq_from_idx"`
|
||||||
RqToIdx *common.Idx `meddler:"rq_to_idx"`
|
RqToIdx *apitypes.HezIdx `meddler:"rq_to_idx"`
|
||||||
RqToEthAddr *ethCommon.Address `meddler:"rq_to_eth_addr"`
|
RqToEthAddr *apitypes.HezEthAddr `meddler:"rq_to_eth_addr"`
|
||||||
RqToBJJ *babyjub.PublicKey `meddler:"rq_to_bjj"`
|
RqToBJJ *apitypes.HezBJJ `meddler:"rq_to_bjj"`
|
||||||
RqTokenID *common.TokenID `meddler:"rq_token_id"`
|
RqTokenID *common.TokenID `meddler:"rq_token_id"`
|
||||||
RqAmount *big.Int `meddler:"rq_amount,bigintnull"`
|
RqAmount *apitypes.BigIntStr `meddler:"rq_amount"`
|
||||||
RqFee *common.FeeSelector `meddler:"rq_fee"`
|
RqFee *common.FeeSelector `meddler:"rq_fee"`
|
||||||
RqNonce *common.Nonce `meddler:"rq_nonce"`
|
RqNonce *common.Nonce `meddler:"rq_nonce"`
|
||||||
Type common.TxType `meddler:"tx_type"`
|
Type common.TxType `meddler:"tx_type"`
|
||||||
@@ -60,6 +64,7 @@ type PoolL2TxRead struct {
|
|||||||
Timestamp time.Time `meddler:"timestamp,utctime"`
|
Timestamp time.Time `meddler:"timestamp,utctime"`
|
||||||
TotalItems int `meddler:"total_items"`
|
TotalItems int `meddler:"total_items"`
|
||||||
TokenID common.TokenID `meddler:"token_id"`
|
TokenID common.TokenID `meddler:"token_id"`
|
||||||
|
TokenItemID int `meddler:"token_item_id"`
|
||||||
TokenEthBlockNum int64 `meddler:"eth_block_num"`
|
TokenEthBlockNum int64 `meddler:"eth_block_num"`
|
||||||
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
|
||||||
TokenName string `meddler:"name"`
|
TokenName string `meddler:"name"`
|
||||||
@@ -68,3 +73,44 @@ type PoolL2TxRead struct {
|
|||||||
TokenUSD *float64 `meddler:"usd"`
|
TokenUSD *float64 `meddler:"usd"`
|
||||||
TokenUSDUpdate *time.Time `meddler:"usd_update"`
|
TokenUSDUpdate *time.Time `meddler:"usd_update"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON is used to neast some of the fields of PoolTxAPI
|
||||||
|
// without the need of auxiliar structs
|
||||||
|
func (tx PoolTxAPI) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"id": tx.TxID,
|
||||||
|
"type": tx.Type,
|
||||||
|
"fromAccountIndex": tx.FromIdx,
|
||||||
|
"fromHezEthereumAddress": tx.FromEthAddr,
|
||||||
|
"fromBJJ": tx.FromBJJ,
|
||||||
|
"toAccountIndex": tx.ToIdx,
|
||||||
|
"toHezEthereumAddress": tx.ToEthAddr,
|
||||||
|
"toBjj": tx.ToBJJ,
|
||||||
|
"amount": tx.Amount,
|
||||||
|
"fee": tx.Fee,
|
||||||
|
"nonce": tx.Nonce,
|
||||||
|
"state": tx.State,
|
||||||
|
"signature": tx.Signature,
|
||||||
|
"timestamp": tx.Timestamp,
|
||||||
|
"batchNum": tx.BatchNum,
|
||||||
|
"requestFromAccountIndex": tx.RqFromIdx,
|
||||||
|
"requestToAccountIndex": tx.RqToIdx,
|
||||||
|
"requestToHezEthereumAddress": tx.RqToEthAddr,
|
||||||
|
"requestToBJJ": tx.RqToBJJ,
|
||||||
|
"requestTokenId": tx.RqTokenID,
|
||||||
|
"requestAmount": tx.RqAmount,
|
||||||
|
"requestFee": tx.RqFee,
|
||||||
|
"requestNonce": tx.RqNonce,
|
||||||
|
"token": map[string]interface{}{
|
||||||
|
"id": tx.TokenID,
|
||||||
|
"itemId": tx.TokenItemID,
|
||||||
|
"ethereumBlockNum": tx.TokenEthBlockNum,
|
||||||
|
"ethereumAddress": tx.TokenEthAddr,
|
||||||
|
"name": tx.TokenName,
|
||||||
|
"symbol": tx.TokenSymbol,
|
||||||
|
"decimals": tx.TokenDecimals,
|
||||||
|
"USD": tx.TokenUSD,
|
||||||
|
"fiatUpdate": tx.TokenUSDUpdate,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,22 @@ CREATE TABLE token (
|
|||||||
usd_update TIMESTAMP WITHOUT TIME ZONE
|
usd_update TIMESTAMP WITHOUT TIME ZONE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
-- +migrate StatementBegin
|
||||||
|
CREATE FUNCTION hez_idx(BIGINT, VARCHAR)
|
||||||
|
RETURNS VARCHAR
|
||||||
|
AS
|
||||||
|
$BODY$
|
||||||
|
BEGIN
|
||||||
|
IF $1 = 1 THEN
|
||||||
|
RETURN 'hez:EXIT:1';
|
||||||
|
END IF;
|
||||||
|
RETURN 'hez:' || $2 || ':' || $1;
|
||||||
|
END;
|
||||||
|
$BODY$
|
||||||
|
LANGUAGE plpgsql;
|
||||||
|
-- +migrate StatementEnd
|
||||||
|
|
||||||
CREATE TABLE account (
|
CREATE TABLE account (
|
||||||
idx BIGINT PRIMARY KEY,
|
idx BIGINT PRIMARY KEY,
|
||||||
token_id INT NOT NULL REFERENCES token (token_id) ON DELETE CASCADE,
|
token_id INT NOT NULL REFERENCES token (token_id) ON DELETE CASCADE,
|
||||||
@@ -96,7 +112,11 @@ CREATE TABLE tx (
|
|||||||
type VARCHAR(40) NOT NULL,
|
type VARCHAR(40) NOT NULL,
|
||||||
position INT NOT NULL,
|
position INT NOT NULL,
|
||||||
from_idx BIGINT,
|
from_idx BIGINT,
|
||||||
|
from_eth_addr BYTEA,
|
||||||
|
from_bjj BYTEA,
|
||||||
to_idx BIGINT NOT NULL,
|
to_idx BIGINT NOT NULL,
|
||||||
|
to_eth_addr BYTEA,
|
||||||
|
to_bjj BYTEA,
|
||||||
amount BYTEA NOT NULL,
|
amount BYTEA NOT NULL,
|
||||||
amount_f NUMERIC NOT NULL,
|
amount_f NUMERIC NOT NULL,
|
||||||
token_id INT NOT NULL REFERENCES token (token_id),
|
token_id INT NOT NULL REFERENCES token (token_id),
|
||||||
@@ -106,8 +126,6 @@ CREATE TABLE tx (
|
|||||||
-- L1
|
-- L1
|
||||||
to_forge_l1_txs_num BIGINT,
|
to_forge_l1_txs_num BIGINT,
|
||||||
user_origin BOOLEAN,
|
user_origin BOOLEAN,
|
||||||
from_eth_addr BYTEA,
|
|
||||||
from_bjj BYTEA,
|
|
||||||
load_amount BYTEA,
|
load_amount BYTEA,
|
||||||
load_amount_f NUMERIC,
|
load_amount_f NUMERIC,
|
||||||
load_amount_usd NUMERIC,
|
load_amount_usd NUMERIC,
|
||||||
@@ -398,28 +416,31 @@ DECLARE
|
|||||||
_usd_update TIMESTAMP;
|
_usd_update TIMESTAMP;
|
||||||
_tx_timestamp TIMESTAMP;
|
_tx_timestamp TIMESTAMP;
|
||||||
BEGIN
|
BEGIN
|
||||||
-- Validate L1/L2 constrains
|
IF NEW.is_l1 THEN
|
||||||
IF NEW.is_l1 AND (( -- L1 mandatory fields
|
-- Validate
|
||||||
NEW.user_origin IS NULL OR
|
IF NEW.user_origin IS NULL OR
|
||||||
NEW.from_eth_addr IS NULL OR
|
NEW.from_eth_addr IS NULL OR
|
||||||
NEW.from_bjj IS NULL OR
|
NEW.from_bjj IS NULL OR
|
||||||
NEW.load_amount IS NULL OR
|
NEW.load_amount IS NULL OR
|
||||||
NEW.load_amount_f IS NULL
|
NEW.load_amount_f IS NULL OR
|
||||||
) OR (NOT NEW.user_origin AND NEW.batch_num IS NULL)) THEN -- If is Coordinator L1, must include batch_num
|
(NOT NEW.user_origin AND NEW.batch_num IS NULL) THEN -- If is Coordinator L1, must include batch_num
|
||||||
RAISE EXCEPTION 'Invalid L1 tx.';
|
RAISE EXCEPTION 'Invalid L1 tx.';
|
||||||
ELSIF NOT NEW.is_l1 THEN
|
|
||||||
IF NEW.fee IS NULL THEN
|
|
||||||
NEW.fee = (SELECT 0);
|
|
||||||
END IF;
|
END IF;
|
||||||
|
ELSE
|
||||||
|
-- Validate
|
||||||
IF NEW.batch_num IS NULL OR NEW.nonce IS NULL THEN
|
IF NEW.batch_num IS NULL OR NEW.nonce IS NULL THEN
|
||||||
RAISE EXCEPTION 'Invalid L2 tx.';
|
RAISE EXCEPTION 'Invalid L2 tx.';
|
||||||
END IF;
|
END IF;
|
||||||
|
-- Set fee if it's null
|
||||||
|
IF NEW.fee IS NULL THEN
|
||||||
|
NEW.fee = (SELECT 0);
|
||||||
END IF;
|
END IF;
|
||||||
-- If is L2, add token_id
|
-- Set token_id
|
||||||
IF NOT NEW.is_l1 THEN
|
|
||||||
NEW."token_id" = (SELECT token_id FROM account WHERE idx = NEW."from_idx");
|
NEW."token_id" = (SELECT token_id FROM account WHERE idx = NEW."from_idx");
|
||||||
|
-- Set from_{eth_addr,bjj}
|
||||||
|
SELECT INTO NEW."from_eth_addr", NEW."from_bjj" eth_addr, bjj FROM account WHERE idx = NEW.from_idx;
|
||||||
END IF;
|
END IF;
|
||||||
-- Set value_usd
|
-- Set USD related
|
||||||
SELECT INTO _value, _usd_update, _tx_timestamp
|
SELECT INTO _value, _usd_update, _tx_timestamp
|
||||||
usd / POWER(10, decimals), usd_update, timestamp FROM token INNER JOIN block on token.eth_block_num = block.eth_block_num WHERE token_id = NEW.token_id;
|
usd / POWER(10, decimals), usd_update, timestamp FROM token INNER JOIN block on token.eth_block_num = block.eth_block_num WHERE token_id = NEW.token_id;
|
||||||
IF _tx_timestamp - interval '24 hours' < _usd_update AND _tx_timestamp + interval '24 hours' > _usd_update THEN
|
IF _tx_timestamp - interval '24 hours' < _usd_update AND _tx_timestamp + interval '24 hours' > _usd_update THEN
|
||||||
@@ -430,6 +451,10 @@ BEGIN
|
|||||||
NEW."load_amount_usd" = (SELECT _value * NEW.load_amount_f);
|
NEW."load_amount_usd" = (SELECT _value * NEW.load_amount_f);
|
||||||
END IF;
|
END IF;
|
||||||
END IF;
|
END IF;
|
||||||
|
-- Set to_{eth_addr,bjj}
|
||||||
|
IF NEW.to_idx > 255 THEN
|
||||||
|
SELECT INTO NEW."to_eth_addr", NEW."to_bjj" eth_addr, bjj FROM account WHERE idx = NEW.to_idx;
|
||||||
|
END IF;
|
||||||
RETURN NEW;
|
RETURN NEW;
|
||||||
END;
|
END;
|
||||||
$BODY$
|
$BODY$
|
||||||
@@ -487,6 +512,8 @@ CREATE TABLE consensus_vars (
|
|||||||
CREATE TABLE tx_pool (
|
CREATE TABLE tx_pool (
|
||||||
tx_id BYTEA PRIMARY KEY,
|
tx_id BYTEA PRIMARY KEY,
|
||||||
from_idx BIGINT NOT NULL,
|
from_idx BIGINT NOT NULL,
|
||||||
|
from_eth_addr BYTEA,
|
||||||
|
from_bjj BYTEA,
|
||||||
to_idx BIGINT,
|
to_idx BIGINT,
|
||||||
to_eth_addr BYTEA,
|
to_eth_addr BYTEA,
|
||||||
to_bjj BYTEA,
|
to_bjj BYTEA,
|
||||||
@@ -510,6 +537,25 @@ CREATE TABLE tx_pool (
|
|||||||
tx_type VARCHAR(40) NOT NULL
|
tx_type VARCHAR(40) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- +migrate StatementBegin
|
||||||
|
CREATE FUNCTION set_pool_tx()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
AS
|
||||||
|
$BODY$
|
||||||
|
BEGIN
|
||||||
|
SELECT INTO NEW."from_eth_addr", NEW."from_bjj" eth_addr, bjj FROM account WHERE idx = NEW."from_idx";
|
||||||
|
-- Set to_{eth_addr,bjj}
|
||||||
|
IF NEW.to_idx > 255 THEN
|
||||||
|
SELECT INTO NEW."to_eth_addr", NEW."to_bjj" eth_addr, bjj FROM account WHERE idx = NEW."to_idx";
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$BODY$
|
||||||
|
LANGUAGE plpgsql;
|
||||||
|
-- +migrate StatementEnd
|
||||||
|
CREATE TRIGGER trigger_set_pool_tx BEFORE INSERT ON tx_pool
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE set_pool_tx();
|
||||||
|
|
||||||
CREATE TABLE account_creation_auth (
|
CREATE TABLE account_creation_auth (
|
||||||
eth_addr BYTEA PRIMARY KEY,
|
eth_addr BYTEA PRIMARY KEY,
|
||||||
bjj BYTEA NOT NULL,
|
bjj BYTEA NOT NULL,
|
||||||
|
|||||||
1
go.sum
1
go.sum
@@ -160,6 +160,7 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqG
|
|||||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
|
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 h1:J5IFyKd/5yuB6AZAgwK0CMBKnabWcmkowtsl6bRkz4s=
|
||||||
github.com/getkin/kin-openapi v0.22.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
github.com/getkin/kin-openapi v0.22.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||||
|
github.com/getkin/kin-openapi v0.23.0 h1:RKtVNKk8kxcTIWEswgZ3Olvn1RxWOJ0zz8cP3d9aHIA=
|
||||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
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/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/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs=
|
||||||
|
|||||||
Reference in New Issue
Block a user