Browse Source

Feature/merge history l2 tables (#156)

* WIP rebase

* Combine both SQL DBs

* API and DB refactor
feature/sql-semaphore1
a_bennassar 4 years ago
committed by GitHub
parent
commit
c6f70f3177
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1485 additions and 982 deletions
  1. +1
    -1
      .github/workflows/test.yml
  2. +1
    -1
      README.md
  3. +5
    -7
      api/api.go
  4. +91
    -61
      api/api_test.go
  5. +47
    -39
      api/dbtoapistructs.go
  6. +181
    -115
      api/swagger.yml
  7. +1
    -4
      cli/node/cfg.example.toml
  8. +6
    -2
      common/l1tx.go
  9. +13
    -3
      common/l2tx.go
  10. +6
    -5
      common/pooll2tx.go
  11. +8
    -8
      common/token.go
  12. +17
    -17
      common/tx.go
  13. +1
    -4
      config/config.go
  14. +3
    -1
      coordinator/coordinator_test.go
  15. +35
    -67
      db/historydb/historydb.go
  16. +85
    -81
      db/historydb/historydb_test.go
  17. +0
    -209
      db/historydb/migrations/001_init.sql
  18. +31
    -27
      db/historydb/views.go
  19. +66
    -78
      db/l2db/l2db.go
  20. +70
    -99
      db/l2db/l2db_test.go
  21. +0
    -40
      db/l2db/migrations/001_init.sql
  22. +515
    -0
      db/migrations/0001.sql
  23. +3
    -3
      db/statedb/txprocessors_test.go
  24. +36
    -2
      db/utils.go
  25. +13
    -13
      node/node.go
  26. +3
    -2
      synchronizer/synchronizer.go
  27. +3
    -7
      synchronizer/synchronizer_test.go
  28. +23
    -0
      test/dbUtils.go
  29. +151
    -60
      test/historydb.go
  30. +27
    -17
      test/l2db.go
  31. +23
    -4
      test/txs.go
  32. +1
    -1
      test/txs_test.go
  33. +1
    -1
      txselector/txselector.go
  34. +18
    -3
      txselector/txselector_test.go

+ 1
- 1
.github/workflows/test.yml

@ -17,7 +17,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Postgres - name: Postgres
run: docker run --rm --name hermez-db-test -p 5432:5432 -e POSTGRES_DB=history -e POSTGRES_USER=hermez -e POSTGRES_PASSWORD="${{ secrets.POSTGRES_PASS }}" -d postgres && sleep 2s && docker exec hermez-db-test psql -a history -U hermez -c "CREATE DATABASE l2;"
run: docker run --rm --name hermez-db-test -p 5432:5432 -e POSTGRES_DB=hermez -e POSTGRES_USER=hermez -e POSTGRES_PASSWORD="${{ secrets.POSTGRES_PASS }}" -d postgres
- name: Test - name: Test
env: env:
POSTGRES_PASS: ${{ secrets.POSTGRES_PASS }} POSTGRES_PASS: ${{ secrets.POSTGRES_PASS }}

+ 1
- 1
README.md

@ -7,7 +7,7 @@ Go implementation of the Hermez node.
- First run a docker instance of the PostgresSQL (where `yourpasswordhere` should be your password) - First run a docker instance of the PostgresSQL (where `yourpasswordhere` should be your password)
``` ```
POSTGRES_PASS=yourpasswordhere; sudo docker run --rm --name hermez-db-test -p 5432:5432 -e POSTGRES_DB=history -e POSTGRES_USER=hermez -e POSTGRES_PASSWORD="$POSTGRES_PASS" -d postgres && sleep 2s && sudo docker exec hermez-db-test psql -a history -U hermez -c "CREATE DATABASE l2;"
POSTGRES_PASS=yourpasswordhere; sudo docker run --rm --name hermez-db-test -p 5432:5432 -e POSTGRES_DB=hermez -e POSTGRES_USER=hermez -e POSTGRES_PASSWORD="$POSTGRES_PASS" -d postgres
``` ```
- Then, run the tests with the password as env var - Then, run the tests with the password as env var

+ 5
- 7
api/api.go

@ -2,7 +2,6 @@ package api
import ( import (
"errors" "errors"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/db/historydb"
@ -11,8 +10,9 @@ import (
) )
var h *historydb.HistoryDB var h *historydb.HistoryDB
var s *statedb.StateDB // Not 100% sure if this is needed
var l2 *l2db.L2DB
// var s *statedb.StateDB // Not 100% sure if this is needed
// var l2 *l2db.L2DB
// SetAPIEndpoints sets the endpoints and the appropriate handlers, but doesn't start the server // SetAPIEndpoints sets the endpoints and the appropriate handlers, but doesn't start the server
func SetAPIEndpoints( func SetAPIEndpoints(
@ -32,11 +32,9 @@ func SetAPIEndpoints(
} }
h = hdb h = hdb
s = sdb
l2 = l2db
// s = sdb
// l2 = l2db
// tmp
fmt.Println(h, s, l2)
// Add coordinator endpoints // Add coordinator endpoints
if coordinatorEndpoints { if coordinatorEndpoints {
// Account // Account

+ 91
- 61
api/api_test.go

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math"
"math/big" "math/big"
"net/http" "net/http"
"os" "os"
@ -19,13 +20,14 @@ import (
swagger "github.com/getkin/kin-openapi/openapi3filter" swagger "github.com/getkin/kin-openapi/openapi3filter"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
dbUtils "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/db/l2db"
"github.com/hermeznetwork/hermez-node/db/statedb" "github.com/hermeznetwork/hermez-node/db/statedb"
"github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/hermez-node/log"
"github.com/hermeznetwork/hermez-node/test" "github.com/hermeznetwork/hermez-node/test"
"github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/babyjub"
"github.com/jinzhu/copier"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -77,16 +79,18 @@ func TestMain(m *testing.M) {
// Init swagger // Init swagger
router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml") router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
// Init DBs // Init DBs
// HistoryDB
pass := os.Getenv("POSTGRES_PASS") pass := os.Getenv("POSTGRES_PASS")
hdb, err := historydb.NewHistoryDB(5432, "localhost", "hermez", pass, "history")
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Reset DB
hdb := historydb.NewHistoryDB(db)
err = hdb.Reorg(-1) err = hdb.Reorg(-1)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// StateDB
dir, err := ioutil.TempDir("", "tmpdb") dir, err := ioutil.TempDir("", "tmpdb")
if err != nil { if err != nil {
panic(err) panic(err)
@ -95,11 +99,10 @@ func TestMain(m *testing.M) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
l2db, err := l2db.NewL2DB(5432, "localhost", "hermez", pass, "l2", 10, 512, 24*time.Hour)
if err != nil {
panic(err)
}
test.CleanL2DB(l2db.DB())
// L2DB
l2DB := l2db.NewL2DB(db, 10, 100, 24*time.Hour)
test.CleanL2DB(l2DB.DB())
// Init API // Init API
api := gin.Default() api := gin.Default()
if err := SetAPIEndpoints( if err := SetAPIEndpoints(
@ -108,7 +111,7 @@ func TestMain(m *testing.M) {
api, api,
hdb, hdb,
sdb, sdb,
l2db,
l2DB,
); err != nil { ); err != nil {
panic(err) panic(err)
} }
@ -120,6 +123,7 @@ func TestMain(m *testing.M) {
panic(err) panic(err)
} }
}() }()
// Populate DBs // Populate DBs
// Clean DB // Clean DB
err = h.Reorg(0) err = h.Reorg(0)
@ -203,40 +207,67 @@ func TestMain(m *testing.M) {
} }
} }
// find token // find token
token := common.Token{}
for i := 0; i < len(tokens); i++ {
if tokens[i].TokenID == genericTx.TokenID {
token = tokens[i]
break
var token common.Token
if genericTx.IsL1 {
tokenID := genericTx.TokenID
found := false
for i := 0; i < len(tokens); i++ {
if tokens[i].TokenID == tokenID {
token = tokens[i]
found = true
break
}
}
if !found {
panic("Token not found")
}
} else {
token = test.GetToken(genericTx.FromIdx, accs, tokens)
}
var usd, loadUSD, feeUSD *float64
if token.USD != nil {
noDecimalsUSD := *token.USD / math.Pow(10, float64(token.Decimals))
usd = new(float64)
*usd = noDecimalsUSD * genericTx.AmountFloat
if genericTx.IsL1 {
loadUSD = new(float64)
*loadUSD = noDecimalsUSD * *genericTx.LoadAmountFloat
} else {
feeUSD = new(float64)
*feeUSD = *usd * genericTx.Fee.Percentage()
} }
} }
historyTxs = append(historyTxs, &historydb.HistoryTx{ historyTxs = append(historyTxs, &historydb.HistoryTx{
IsL1: genericTx.IsL1,
TxID: genericTx.TxID,
Type: genericTx.Type,
Position: genericTx.Position,
FromIdx: genericTx.FromIdx,
ToIdx: genericTx.ToIdx,
Amount: genericTx.Amount,
AmountFloat: genericTx.AmountFloat,
TokenID: genericTx.TokenID,
USD: token.USD * genericTx.AmountFloat,
BatchNum: genericTx.BatchNum,
EthBlockNum: genericTx.EthBlockNum,
ToForgeL1TxsNum: genericTx.ToForgeL1TxsNum,
UserOrigin: genericTx.UserOrigin,
FromEthAddr: genericTx.FromEthAddr,
FromBJJ: genericTx.FromBJJ,
LoadAmount: genericTx.LoadAmount,
LoadAmountFloat: genericTx.LoadAmountFloat,
LoadAmountUSD: token.USD * genericTx.LoadAmountFloat,
Fee: genericTx.Fee,
FeeUSD: genericTx.Fee.Percentage() * token.USD * genericTx.AmountFloat,
Nonce: genericTx.Nonce,
Timestamp: timestamp,
TokenSymbol: token.Symbol,
CurrentUSD: token.USD * genericTx.AmountFloat,
USDUpdate: token.USDUpdate,
IsL1: genericTx.IsL1,
TxID: genericTx.TxID,
Type: genericTx.Type,
Position: genericTx.Position,
FromIdx: genericTx.FromIdx,
ToIdx: genericTx.ToIdx,
Amount: genericTx.Amount,
AmountFloat: genericTx.AmountFloat,
HistoricUSD: usd,
BatchNum: genericTx.BatchNum,
EthBlockNum: genericTx.EthBlockNum,
ToForgeL1TxsNum: genericTx.ToForgeL1TxsNum,
UserOrigin: genericTx.UserOrigin,
FromEthAddr: genericTx.FromEthAddr,
FromBJJ: genericTx.FromBJJ,
LoadAmount: genericTx.LoadAmount,
LoadAmountFloat: genericTx.LoadAmountFloat,
HistoricLoadAmountUSD: loadUSD,
Fee: genericTx.Fee,
HistoricFeeUSD: feeUSD,
Nonce: genericTx.Nonce,
Timestamp: timestamp,
TokenID: token.TokenID,
TokenEthBlockNum: token.EthBlockNum,
TokenEthAddr: token.EthAddr,
TokenName: token.Name,
TokenSymbol: token.Symbol,
TokenDecimals: token.Decimals,
TokenUSD: token.USD,
TokenUSDUpdate: token.USDUpdate,
}) })
} }
return historyTxAPIs(historyTxsToAPI(historyTxs)) return historyTxAPIs(historyTxsToAPI(historyTxs))
@ -265,10 +296,7 @@ func TestMain(m *testing.M) {
if err := server.Shutdown(context.Background()); err != nil { if err := server.Shutdown(context.Background()); err != nil {
panic(err) panic(err)
} }
if err := h.Close(); err != nil {
panic(err)
}
if err := l2.Close(); err != nil {
if err := db.Close(); err != nil {
panic(err) panic(err)
} }
os.Exit(result) os.Exit(result)
@ -279,11 +307,11 @@ func TestGetHistoryTxs(t *testing.T) {
fetchedTxs := historyTxAPIs{} fetchedTxs := historyTxAPIs{}
appendIter := func(intr interface{}) { appendIter := func(intr interface{}) {
for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ { for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
tmp := &historyTxAPI{}
if err := copier.Copy(tmp, &intr.(*historyTxsAPI).Txs[i]); err != nil {
tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
if err != nil {
panic(err) panic(err)
} }
fetchedTxs = append(fetchedTxs, *tmp)
fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
} }
} }
// Get all (no filters) // Get all (no filters)
@ -315,7 +343,7 @@ func TestGetHistoryTxs(t *testing.T) {
// Get by tokenID // Get by tokenID
fetchedTxs = historyTxAPIs{} fetchedTxs = historyTxAPIs{}
limit = 5 limit = 5
tokenID := tc.allTxs[0].TokenID
tokenID := tc.allTxs[0].Token.TokenID
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?tokenId=%d&limit=%d&offset=", "%s?tokenId=%d&limit=%d&offset=",
endpoint, tokenID, limit, endpoint, tokenID, limit,
@ -324,7 +352,7 @@ func TestGetHistoryTxs(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
tokenIDTxs := historyTxAPIs{} tokenIDTxs := historyTxAPIs{}
for i := 0; i < len(tc.allTxs); i++ { for i := 0; i < len(tc.allTxs); i++ {
if tc.allTxs[i].TokenID == tokenID {
if tc.allTxs[i].Token.TokenID == tokenID {
tokenIDTxs = append(tokenIDTxs, tc.allTxs[i]) tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
} }
} }
@ -407,7 +435,7 @@ func TestGetHistoryTxs(t *testing.T) {
mixedTxs := historyTxAPIs{} mixedTxs := historyTxAPIs{}
for i := 0; i < len(tc.allTxs); i++ { for i := 0; i < len(tc.allTxs); i++ {
if tc.allTxs[i].BatchNum != nil { if tc.allTxs[i].BatchNum != nil {
if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].TokenID == tokenID {
if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
mixedTxs = append(mixedTxs, tc.allTxs[i]) mixedTxs = append(mixedTxs, tc.allTxs[i])
} }
} }
@ -420,11 +448,11 @@ func TestGetHistoryTxs(t *testing.T) {
appendIterRev := func(intr interface{}) { appendIterRev := func(intr interface{}) {
tmpAll := historyTxAPIs{} tmpAll := historyTxAPIs{}
for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ { for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
tmpItem := &historyTxAPI{}
if err := copier.Copy(tmpItem, &intr.(*historyTxsAPI).Txs[i]); err != nil {
tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
if err != nil {
panic(err) panic(err)
} }
tmpAll = append(tmpAll, *tmpItem)
tmpAll = append(tmpAll, tmp.(historyTxAPI))
} }
fetchedTxs = append(tmpAll, fetchedTxs...) fetchedTxs = append(tmpAll, fetchedTxs...)
} }
@ -455,15 +483,17 @@ func assertHistoryTxAPIs(t *testing.T, expected, actual historyTxAPIs) {
for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix()) assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
expected[i].Timestamp = actual[i].Timestamp expected[i].Timestamp = actual[i].Timestamp
assert.Equal(t, expected[i].USDUpdate.Unix(), actual[i].USDUpdate.Unix())
expected[i].USDUpdate = actual[i].USDUpdate
if expected[i].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 { if expected[i].L2Info != nil {
if expected[i].L2Info.FeeUSD > actual[i].L2Info.FeeUSD {
assert.Less(t, 0.999, actual[i].L2Info.FeeUSD/expected[i].L2Info.FeeUSD)
} else if expected[i].L2Info.FeeUSD < actual[i].L2Info.FeeUSD {
assert.Less(t, 0.999, expected[i].L2Info.FeeUSD/actual[i].L2Info.FeeUSD)
}
expected[i].L2Info.FeeUSD = actual[i].L2Info.FeeUSD
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]) assert.Equal(t, expected[i], actual[i])
} }

+ 47
- 39
api/dbtoapistructs.go

@ -5,6 +5,7 @@ import (
"strconv" "strconv"
"time" "time"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/babyjub"
@ -37,19 +38,19 @@ func (htx *historyTxsAPI) GetPagination() pagination { return htx.Pagination }
func (htx *historyTxsAPI) Len() int { return len(htx.Txs) } func (htx *historyTxsAPI) Len() int { return len(htx.Txs) }
type l1Info struct { type l1Info struct {
ToForgeL1TxsNum int64 `json:"toForgeL1TransactionsNum"`
UserOrigin bool `json:"userOrigin"`
FromEthAddr string `json:"fromEthereumAddress"`
FromBJJ string `json:"fromBJJ"`
LoadAmount string `json:"loadAmount"`
LoadAmountUSD float64 `json:"loadAmountUSD"`
EthBlockNum int64 `json:"ethereumBlockNum"`
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 { type l2Info struct {
Fee common.FeeSelector `json:"fee"`
FeeUSD float64 `json:"feeUSD"`
Nonce common.Nonce `json:"nonce"`
Fee common.FeeSelector `json:"fee"`
HistoricFeeUSD *float64 `json:"historicFeeUSD"`
Nonce common.Nonce `json:"nonce"`
} }
type historyTxAPI struct { type historyTxAPI struct {
@ -61,14 +62,11 @@ type historyTxAPI struct {
ToIdx string `json:"toAccountIndex"` ToIdx string `json:"toAccountIndex"`
Amount string `json:"amount"` Amount string `json:"amount"`
BatchNum *common.BatchNum `json:"batchNum"` BatchNum *common.BatchNum `json:"batchNum"`
TokenID common.TokenID `json:"tokenId"`
TokenSymbol string `json:"tokenSymbol"`
USD float64 `json:"historicUSD"`
HistoricUSD *float64 `json:"historicUSD"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
CurrentUSD float64 `json:"currentUSD"`
USDUpdate time.Time `json:"fiatUpdate"`
L1Info *l1Info `json:"L1Info"` L1Info *l1Info `json:"L1Info"`
L2Info *l2Info `json:"L2Info"` L2Info *l2Info `json:"L2Info"`
Token common.Token `json:"token"`
} }
func historyTxsToAPI(dbTxs []*historydb.HistoryTx) []historyTxAPI { func historyTxsToAPI(dbTxs []*historydb.HistoryTx) []historyTxAPI {
@ -78,40 +76,42 @@ func historyTxsToAPI(dbTxs []*historydb.HistoryTx) []historyTxAPI {
TxID: dbTxs[i].TxID, TxID: dbTxs[i].TxID,
Type: dbTxs[i].Type, Type: dbTxs[i].Type,
Position: dbTxs[i].Position, Position: dbTxs[i].Position,
FromIdx: "hez:" + dbTxs[i].TokenSymbol + ":" + strconv.Itoa(int(dbTxs[i].FromIdx)),
ToIdx: "hez:" + dbTxs[i].TokenSymbol + ":" + strconv.Itoa(int(dbTxs[i].ToIdx)),
FromIdx: idxToHez(dbTxs[i].FromIdx, dbTxs[i].TokenSymbol),
ToIdx: idxToHez(dbTxs[i].ToIdx, dbTxs[i].TokenSymbol),
Amount: dbTxs[i].Amount.String(), Amount: dbTxs[i].Amount.String(),
TokenID: dbTxs[i].TokenID,
USD: dbTxs[i].USD,
BatchNum: nil,
HistoricUSD: dbTxs[i].HistoricUSD,
BatchNum: dbTxs[i].BatchNum,
Timestamp: dbTxs[i].Timestamp, Timestamp: dbTxs[i].Timestamp,
TokenSymbol: dbTxs[i].TokenSymbol,
CurrentUSD: dbTxs[i].CurrentUSD,
USDUpdate: dbTxs[i].USDUpdate,
L1Info: nil,
L2Info: nil,
}
bn := dbTxs[i].BatchNum
if dbTxs[i].BatchNum != 0 {
apiTx.BatchNum = &bn
Token: common.Token{
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].IsL1 { if dbTxs[i].IsL1 {
apiTx.IsL1 = "L1" apiTx.IsL1 = "L1"
apiTx.L1Info = &l1Info{ apiTx.L1Info = &l1Info{
ToForgeL1TxsNum: dbTxs[i].ToForgeL1TxsNum,
UserOrigin: dbTxs[i].UserOrigin,
FromEthAddr: "hez:" + dbTxs[i].FromEthAddr.String(),
FromBJJ: bjjToString(dbTxs[i].FromBJJ),
LoadAmount: dbTxs[i].LoadAmount.String(),
LoadAmountUSD: dbTxs[i].LoadAmountUSD,
EthBlockNum: dbTxs[i].EthBlockNum,
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 { } else {
apiTx.IsL1 = "L2" apiTx.IsL1 = "L2"
apiTx.L2Info = &l2Info{ apiTx.L2Info = &l2Info{
Fee: dbTxs[i].Fee,
FeeUSD: dbTxs[i].FeeUSD,
Nonce: dbTxs[i].Nonce,
Fee: *dbTxs[i].Fee,
HistoricFeeUSD: dbTxs[i].HistoricFeeUSD,
Nonce: *dbTxs[i].Nonce,
} }
} }
apiTxs = append(apiTxs, apiTx) apiTxs = append(apiTxs, apiTx)
@ -128,3 +128,11 @@ func bjjToString(bjj *babyjub.PublicKey) string {
bjjSum := append(pkComp[:], sum) bjjSum := append(pkComp[:], sum)
return "hez:" + base64.RawURLEncoding.EncodeToString(bjjSum) return "hez:" + base64.RawURLEncoding.EncodeToString(bjjSum)
} }
func ethAddrToHez(addr ethCommon.Address) string {
return "hez:" + addr.String()
}
func idxToHez(idx common.Idx, tokenSymbol string) string {
return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx))
}

+ 181
- 115
api/swagger.yml

@ -96,7 +96,7 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Error500' $ref: '#/components/schemas/Error500'
'/account-creation-authorization/{hermezEthereumAddress}':
'/account-creation-authorization/{hezEthereumAddress}':
get: get:
tags: tags:
- Account - Account
@ -105,12 +105,12 @@ paths:
True if the coordinator has the required authorization to perform an account creation with the given Ethereum address on behalf of the Ethereum address holder. True if the coordinator has the required authorization to perform an account creation with the given Ethereum address on behalf of the Ethereum address holder.
operationId: getAccountCreationAuthorization operationId: getAccountCreationAuthorization
parameters: parameters:
- name: hermezEthereumAddress
- name: hezEthereumAddress
in: path in: path
description: Ethereum address. description: Ethereum address.
required: true required: true
schema: schema:
$ref: '#/components/schemas/HermezEthereumAddress'
$ref: '#/components/schemas/HezEthereumAddress'
responses: responses:
'200': '200':
description: Successful operation. description: Successful operation.
@ -144,15 +144,15 @@ paths:
description: Get accounts balances and other associated information. description: Get accounts balances and other associated information.
operationId: getAccounts operationId: getAccounts
parameters: parameters:
- name: hermezEthereumAddress
- name: hezEthereumAddress
in: query in: query
description: Only get accounts associated to an Ethereum address. Incompatible with the query `BJJ`. description: Only get accounts associated to an Ethereum address. Incompatible with the query `BJJ`.
required: false required: false
schema: schema:
$ref: '#/components/schemas/HermezEthereumAddress'
$ref: '#/components/schemas/HezEthereumAddress'
- name: BJJ - name: BJJ
in: query in: query
description: Only get accounts associated to a BabyJubJub public key. Incompatible with the query `hermezEthereumAddress`.
description: Only get accounts associated to a BabyJubJub public key. Incompatible with the query `hezEthereumAddress`.
required: false required: false
schema: schema:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
@ -258,21 +258,21 @@ paths:
description: Get exit information. This information is required to perform a withdraw. description: Get exit information. This information is required to perform a withdraw.
operationId: getExits operationId: getExits
parameters: parameters:
- name: hermezEthereumAddress
- name: hezEthereumAddress
in: query in: query
description: Get exits associated to a Ethereum address. Incompatible with query `BJJ` and `accountIndex`. description: Get exits associated to a Ethereum address. Incompatible with query `BJJ` and `accountIndex`.
required: false required: false
schema: schema:
$ref: '#/components/schemas/HermezEthereumAddress'
$ref: '#/components/schemas/HezEthereumAddress'
- name: BJJ - name: BJJ
in: query in: query
description: Get exits associated to a BabyJubJub public key. Incompatible with query `hermezEthereumAddress` and `accountIndex`.
description: Get exits associated to a BabyJubJub public key. Incompatible with query `hezEthereumAddress` and `accountIndex`.
required: false required: false
schema: schema:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
- name: accountIndex - name: accountIndex
in: query in: query
description: Get exits associated to a specific account. Incompatible with queries `hermezEthereumAddress` and `BJJ`.
description: Get exits associated to a specific account. Incompatible with queries `hezEthereumAddress` and `BJJ`.
required: false required: false
schema: schema:
$ref: '#/components/schemas/AccountIndex' $ref: '#/components/schemas/AccountIndex'
@ -388,7 +388,7 @@ paths:
content: content:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/PoolL2Transaction'
$ref: '#/components/schemas/PostPoolL2Transaction'
responses: responses:
'200': '200':
description: Successful operation. description: Successful operation.
@ -468,22 +468,22 @@ paths:
description: Only get transactions of specific token description: Only get transactions of specific token
schema: schema:
$ref: '#/components/schemas/TokenId' $ref: '#/components/schemas/TokenId'
- name: hermezEthereumAddress
- name: hezEthereumAddress
in: query in: query
required: false required: false
description: Only get transactions sent from or to an account associated to an Ethereum address Incompatible with the queries `BJJ` and `accountIndex`. description: Only get transactions sent from or to an account associated to an Ethereum address Incompatible with the queries `BJJ` and `accountIndex`.
schema: schema:
$ref: '#/components/schemas/HermezEthereumAddress'
$ref: '#/components/schemas/HezEthereumAddress'
- name: BJJ - name: BJJ
in: query in: query
description: Only get transactions associated to a BabyJubJub public key. Incompatible with the queries `hermezEthereumAddress` and `accountIndex`.
description: Only get transactions associated to a BabyJubJub public key. Incompatible with the queries `hezEthereumAddress` and `accountIndex`.
required: false required: false
schema: schema:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
- name: accountIndex - name: accountIndex
in: query in: query
required: false required: false
description: Only get transactions sent from or to a specific account. Incompatible with the queries `tokenId`, `hermezEthereumAddress` and `BJJ`.
description: Only get transactions sent from or to a specific account. Incompatible with the queries `tokenId`, `hezEthereumAddress` and `BJJ`.
schema: schema:
$ref: '#/components/schemas/AccountIndex' $ref: '#/components/schemas/AccountIndex'
- name: batchNum - name: batchNum
@ -1228,33 +1228,118 @@ paths:
$ref: '#/components/schemas/Error500' $ref: '#/components/schemas/Error500'
components: components:
schemas: schemas:
PoolL2Transaction:
PostPoolL2Transaction:
type: object type: object
properties: properties:
id: id:
$ref: '#/components/schemas/TransactionId' $ref: '#/components/schemas/TransactionId'
type: type:
$ref: '#/components/schemas/TransactionType' $ref: '#/components/schemas/TransactionType'
tokenId:
$ref: '#/components/schemas/TokenId'
fromAccountIndex: fromAccountIndex:
$ref: '#/components/schemas/AccountIndex' $ref: '#/components/schemas/AccountIndex'
toAccountIndex: toAccountIndex:
allOf: allOf:
- $ref: '#/components/schemas/AccountIndex' - $ref: '#/components/schemas/AccountIndex'
- example: "hez:DAI:672" - example: "hez:DAI:672"
toEthereumAddress:
$ref: '#/components/schemas/HermezEthereumAddress'
toHezEthereumAddress:
$ref: '#/components/schemas/HezEthereumAddress'
toBjj: toBjj:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
tokenId:
$ref: '#/components/schemas/TokenId'
USD:
type: number
description: Value of the token in USD.
example: 4.53
fiatUpdate:
amount:
allOf:
- $ref: '#/components/schemas/BigInt'
- description: Amount of tokens to be sent.
example: "63"
fee:
$ref: '#/components/schemas/FeeSelector'
nonce:
$ref: '#/components/schemas/Nonce'
signature:
allOf:
- $ref: '#/components/schemas/Signature'
- description: Signature of the transaction. More info [here](https://idocs.hermez.io/#/spec/zkrollup/README?id=l2a-idl2).
- example: "72024a43f546b0e1d9d5d7c4c30c259102a9726363adcc4ec7b6aea686bcb5116f485c5542d27c4092ae0ceaf38e3bb44417639bd2070a58ba1aa1aab9d92c03"
requestFromAccountIndex:
type: string type: string
format: date-time
description: Timestamp of the moment the `USD` value was updated.
description: References the `fromAccountIndex` of the requested transaction.
example: null
nullable: true
requestToAccountIndex:
type: string
description: References the `toAccountIndex` of the requested transaction.
example: null
nullable: true
requestToHezEthereumAddress:
type: string
description: References the `toHezEthereumAddress` of the requested transaction.
pattern: "^hez:0x[a-fA-F0-9]{40}$"
example: null
nullable: true
requestToBJJ:
type: string
description: References the `toBJJ` of the requested transaction.
pattern: "^hez:[A-Za-z0-9_-]{44}$"
example: null
nullable: true
requestTokenId:
type: integer
description: References the `tokenId` of the requested transaction.
example: null
nullable: true
requestAmount:
type: string
description: References the `amount` of the requested transaction.
example: null
nullable: true
requestFee:
type: integer
description: References the `fee` of the requested transaction.
example: null
nullable: true
requestNonce:
type: integer
description: References the `nonce` of the requested transaction.
example: null
nullable: true
required:
- id
- type
- tokenId
- fromAccountIndex
- toHezAccountIndex
- toHezEthereumAddress
- toBjj
- amount
- fee
- nonce
- signature
- requestFromAccountIndex
- requestToAccountIndex
- requestToHezEthereumAddress
- requestToBJJ
- requestTokenId
- requestAmount
- requestFee
- requestNonce
PoolL2Transaction:
type: object
properties:
id:
$ref: '#/components/schemas/TransactionId'
type:
$ref: '#/components/schemas/TransactionType'
fromAccountIndex:
$ref: '#/components/schemas/AccountIndex'
toAccountIndex:
allOf:
- $ref: '#/components/schemas/AccountIndex'
- example: "hez:DAI:672"
toHezEthereumAddress:
$ref: '#/components/schemas/HezEthereumAddress'
toBjj:
$ref: '#/components/schemas/BJJ'
amount: amount:
allOf: allOf:
- $ref: '#/components/schemas/BigInt' - $ref: '#/components/schemas/BigInt'
@ -1262,10 +1347,6 @@ components:
example: "63" example: "63"
fee: fee:
$ref: '#/components/schemas/FeeSelector' $ref: '#/components/schemas/FeeSelector'
feeUSD:
type: number
description: Fee in USD.
example: 0.75
nonce: nonce:
$ref: '#/components/schemas/Nonce' $ref: '#/components/schemas/Nonce'
state: state:
@ -1294,9 +1375,9 @@ components:
- $ref: '#/components/schemas/AccountIndex' - $ref: '#/components/schemas/AccountIndex'
- nullable: true - nullable: true
- example: "hez:DAI:33" - example: "hez:DAI:33"
requestToEthereumAddress:
requestToHezEthereumAddress:
allOf: allOf:
- $ref: '#/components/schemas/HermezEthereumAddress'
- $ref: '#/components/schemas/HezEthereumAddress'
- nullable: true - nullable: true
- example: "hez:0xbb942cfcd25ad4d90a62358b0dd84f33b3982699" - example: "hez:0xbb942cfcd25ad4d90a62358b0dd84f33b3982699"
requestToBJJ: requestToBJJ:
@ -1325,12 +1406,12 @@ components:
- $ref: '#/components/schemas/Nonce' - $ref: '#/components/schemas/Nonce'
- nullable: true - nullable: true
- example: 6 - example: 6
tokenSymbol:
$ref: '#/components/schemas/TokenSymbol'
token:
$ref: '#/components/schemas/Token'
required: required:
- fromAccountIndex - fromAccountIndex
- toAccountIndex
- toEthereumAddress
- toHezAccountIndex
- toHezEthereumAddress
- toBjj - toBjj
- tokenId - tokenId
- amount - amount
@ -1346,14 +1427,14 @@ components:
description: "Address of an Etherum account." description: "Address of an Etherum account."
pattern: "^0x[a-fA-F0-9]{40}$" pattern: "^0x[a-fA-F0-9]{40}$"
example: "0xaa942cfcd25ad4d90a62358b0dd84f33b398262a" example: "0xaa942cfcd25ad4d90a62358b0dd84f33b398262a"
HermezEthereumAddress:
HezEthereumAddress:
type: string type: string
description: "Address of an Etherum account linked to the Hermez network." description: "Address of an Etherum account linked to the Hermez network."
pattern: "^hez:0x[a-fA-F0-9]{40}$" pattern: "^hez:0x[a-fA-F0-9]{40}$"
example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a" example: "hez:0xaa942cfcd25ad4d90a62358b0dd84f33b398262a"
BJJ: BJJ:
type: string type: string
description: "BabyJubJub public key, encoded as base64, which result in 33 bytes (last byte used as checksum)."
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}$" pattern: "^hez:[A-Za-z0-9_-]{44}$"
example: "hez:rR7LXKal-av7I56Y0dEBCVmwc9zpoLY5ERhy5w7G-xwe" example: "hez:rR7LXKal-av7I56Y0dEBCVmwc9zpoLY5ERhy5w7G-xwe"
AccountIndex: AccountIndex:
@ -1429,8 +1510,8 @@ components:
timestamp: timestamp:
type: string type: string
format: date-time format: date-time
ethereumAddress:
$ref: '#/components/schemas/HermezEthereumAddress'
hezEthereumAddress:
$ref: '#/components/schemas/HezEthereumAddress'
bjj: bjj:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
signature: signature:
@ -1475,26 +1556,17 @@ components:
maximum: 4294967295 maximum: 4294967295
example: 5432 example: 5432
nullable: true nullable: true
tokenId:
$ref: '#/components/schemas/TokenId'
tokenSymbol:
$ref: '#/components/schemas/TokenSymbol'
historicUSD: historicUSD:
type: number type: number
description: Value in USD at the moment the transaction was forged. description: Value in USD at the moment the transaction was forged.
example: 49.7 example: 49.7
currentUSD:
type: number
description: Value in USD at the current token/USD conversion.
example: 50.01
fiatUpdate:
type: string
format: date-time
description: Timestamp of the moment the `currentUSD` value was updated.
nullable: true
timestamp: timestamp:
type: string type: string
format: date-time format: date-time
description: In the case of L1 indicates the moment where the transaction was added in the smart contract. For L2 indicates when the transaction was forged. description: In the case of L1 indicates the moment where the transaction was added in the smart contract. For L2 indicates when the transaction was forged.
token:
$ref: '#/components/schemas/Token'
L1Info: L1Info:
type: object type: object
description: Additional information that only applies to L1 transactions. description: Additional information that only applies to L1 transactions.
@ -1505,8 +1577,8 @@ 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.
fromEthereumAddress:
$ref: '#/components/schemas/HermezEthereumAddress'
fromHezEthereumAddress:
$ref: '#/components/schemas/HezEthereumAddress'
fromBJJ: fromBJJ:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
loadAmount: loadAmount:
@ -1514,10 +1586,11 @@ components:
- $ref: '#/components/schemas/BigInt' - $ref: '#/components/schemas/BigInt'
- description: Tokens transfered from L1 to L2. - description: Tokens transfered from L1 to L2.
- example: "49" - example: "49"
loadAmountUSD:
historicLoadAmountUSD:
type: number type: number
description: Load amount in USD, at the moment the transaction was made. description: Load amount in USD, at the moment the transaction was made.
example: 3.897 example: 3.897
nullable: true
ethereumBlockNum: ethereumBlockNum:
allOf: allOf:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'
@ -1526,10 +1599,10 @@ components:
required: required:
- toForgeL1TransactionsNum - toForgeL1TransactionsNum
- userOrigin - userOrigin
- fromEthereumAddress
- fromHezEthereumAddress
- fromBJJ - fromBJJ
- loadAmount - loadAmount
- loadAmountUSD
- historicLoadAmountUSD
- ethereumBlockNum - ethereumBlockNum
additionalProperties: false additionalProperties: false
L2Info: L2Info:
@ -1539,16 +1612,17 @@ components:
properties: properties:
fee: fee:
$ref: '#/components/schemas/FeeSelector' $ref: '#/components/schemas/FeeSelector'
feeUSD:
historicFeeUSD:
type: number type: number
description: Fee in USD, at the moment the transaction was forged. description: Fee in USD, at the moment the transaction was forged.
example: 263.89 example: 263.89
nullable: true
nonce: nonce:
$ref: '#/components/schemas/Nonce' $ref: '#/components/schemas/Nonce'
example: null example: null
required: required:
- fee - fee
- feeUSD
- historicFeeUSD
- nonce - nonce
additionalProperties: false additionalProperties: false
required: required:
@ -1560,12 +1634,9 @@ components:
- toAccountIndex - toAccountIndex
- amount - amount
- batchNum - batchNum
- tokenId
- tokenSymbol
- historicUSD - historicUSD
- currentUSD
- fiatUpdate
- timestamp - timestamp
- token
- L1Info - L1Info
- L2Info - L2Info
additionalProperties: false additionalProperties: false
@ -1618,10 +1689,8 @@ components:
items: items:
type: object type: object
properties: properties:
tokenId:
$ref: '#/components/schemas/TokenId'
tokenSymbol:
$ref: '#/components/schemas/TokenSymbol'
token:
$ref: '#/components/schemas/Token'
amount: amount:
allOf: allOf:
- $ref: '#/components/schemas/BigInt' - $ref: '#/components/schemas/BigInt'
@ -1635,13 +1704,21 @@ components:
$ref: '#/components/schemas/BatchNum' $ref: '#/components/schemas/BatchNum'
ethereumBlockNum: ethereumBlockNum:
$ref: '#/components/schemas/EthBlockNum' $ref: '#/components/schemas/EthBlockNum'
ethereumBlockHash:
type: string
description: hash of the Ethereum block in which the batch was forged
example: "0xfe88c94d860f01a17f961bf4bdfb6e0c6cd10d3fda5cc861e805ca1240c58553"
timestamp:
type: string
format: date-time
description: Time in which the batch was forged.
forgerAddr: forgerAddr:
$ref: '#/components/schemas/EthereumAddress' $ref: '#/components/schemas/EthereumAddress'
collectedFees: collectedFees:
$ref: '#/components/schemas/CollectedFees' $ref: '#/components/schemas/CollectedFees'
totalCollectedFeesUSD:
historicTotalCollectedFeesUSD:
type: number type: number
description: Sum of the all the fees collected, in USD.
description: Sum of the all the fees collected, in USD, at the moment the batch was forged.
example: 23.3 example: 23.3
stateRoot: stateRoot:
allOf: allOf:
@ -1668,35 +1745,8 @@ components:
type: object type: object
description: Group of transactions forged in a coordinator and sent and validated in Ethereum. description: Group of transactions forged in a coordinator and sent and validated in Ethereum.
properties: properties:
batchNum:
$ref: '#/components/schemas/BatchNum'
forgerAddr:
$ref: '#/components/schemas/EthereumAddress'
collectedFees:
$ref: '#/components/schemas/CollectedFees'
ethereumBlockNum:
$ref: '#/components/schemas/EthBlockNum'
stateRoot:
allOf:
- $ref: '#/components/schemas/Hash'
- description: Root of the accounts Merkle Tree.
- example: "2734657026572a8708d883"
numAccounts:
type: integer
description: Number of registered accounts in this batch.
exitRoot:
allOf:
- $ref: '#/components/schemas/Hash'
- description: Root of the exit Merkle Tree associated to this batch.
- example: "2734657026572a8708d883"
forgeL1TransactionsNum:
allOf:
- $ref: '#/components/schemas/ToForgeL1TransactionsNum'
- description: Identifier that corresponds to the group of L1 transactions forged in the current batch.
- nullable: true
- example: 9
slotNum:
$ref: '#/components/schemas/SlotNum'
batch:
$ref: '#/components/schemas/Batch'
transactions: transactions:
type: array type: array
description: List of forged transactions in the batch description: List of forged transactions in the batch
@ -1752,6 +1802,8 @@ components:
properties: properties:
forgerAddr: forgerAddr:
$ref: '#/components/schemas/EthereumAddress' $ref: '#/components/schemas/EthereumAddress'
slotNum:
$ref: '#/components/schemas/SlotNum'
withdrawAddr: withdrawAddr:
$ref: '#/components/schemas/EthereumAddress' $ref: '#/components/schemas/EthereumAddress'
URL: URL:
@ -1814,7 +1866,7 @@ components:
decimals: decimals:
type: integer type: integer
description: Number of decimals of the token. description: Number of decimals of the token.
example: 5
example: 18
ethereumBlockNum: ethereumBlockNum:
allOf: allOf:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'
@ -1823,11 +1875,23 @@ components:
USD: USD:
type: number type: number
description: Value of the token in USD. description: Value of the token in USD.
example: 4.53
example: 1.01
nullable: true
fiatUpdate: fiatUpdate:
type: string type: string
format: date-time format: date-time
description: Timestamp of the moment the `USD` value was updated. description: Timestamp of the moment the `USD` value was updated.
nullable: true
required:
- id
- ethereumAddress
- name
- symbol
- decimals
- ethereumBlockNum
- USD
- fiatUpdate
additionalProperties: false
Tokens: Tokens:
type: object type: object
properties: properties:
@ -1855,8 +1919,6 @@ components:
example: "0x347089321de8971320489793a823470918fffeab" example: "0x347089321de8971320489793a823470918fffeab"
balance: balance:
$ref: '#/components/schemas/BigInt' $ref: '#/components/schemas/BigInt'
nullifier:
$ref: '#/components/schemas/BigInt'
instantWithdrawn: instantWithdrawn:
allOf: allOf:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'
@ -1872,6 +1934,8 @@ components:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'
- description: Block in which the exit balance was delayed withdrawn after a delay withdraw request. Null indicates that a delay withdraw hasn't been performed. - description: Block in which the exit balance was delayed withdrawn after a delay withdraw request. Null indicates that a delay withdraw hasn't been performed.
- example: null - example: null
token:
$ref: '#/components/schemas/Token'
Exits: Exits:
type: object type: object
properties: properties:
@ -1888,24 +1952,16 @@ components:
properties: properties:
accountIndex: accountIndex:
$ref: '#/components/schemas/AccountIndex' $ref: '#/components/schemas/AccountIndex'
tokenId:
$ref: '#/components/schemas/TokenId'
tokenSymbol:
$ref: '#/components/schemas/TokenSymbol'
tokenName:
$ref: '#/components/schemas/TokenName'
nonce: nonce:
$ref: '#/components/schemas/Nonce' $ref: '#/components/schemas/Nonce'
balance: balance:
$ref: '#/components/schemas/BigInt' $ref: '#/components/schemas/BigInt'
balanceUSD:
type: integer
description: Balance of the account in USD
example: 1304
bjj: bjj:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
ethereumAddress:
$ref: '#/components/schemas/HermezEthereumAddress'
hezEthereumAddress:
$ref: '#/components/schemas/HezEthereumAddress'
token:
$ref: '#/components/schemas/Token'
Accounts: Accounts:
type: object type: object
properties: properties:
@ -2053,6 +2109,7 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum address of the boot coordinator. - description: Ethereum address of the boot coordinator.
- example: "0x997dc4262BCDbf85190C01c996b4C06a461d2430"
slotDeadline: slotDeadline:
type: integer type: integer
description: Number of blocks at the end of a slot in which any coordinator can forge if the winner has not forged one before. description: Number of blocks at the end of a slot in which any coordinator can forge if the winner has not forged one before.
@ -2078,6 +2135,7 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum address where the donations will go to. - description: Ethereum address where the donations will go to.
- example: "0x887dc4262BCDbf85190C01c996b4C06a461d2430"
allocationRatio: allocationRatio:
type: array type: array
description: Percentage in which fees will be splitted between donations, governance and burning. The sum of the tree values should be 100. description: Percentage in which fees will be splitted between donations, governance and burning. The sum of the tree values should be 100.
@ -2092,22 +2150,27 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum address of the rollup smart contract. - description: Ethereum address of the rollup smart contract.
- example: "0x777dc4262BCDbf85190C01c996b4C06a461d2430"
governanceAddress: governanceAddress:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum address of the governance mechanism. - description: Ethereum address of the governance mechanism.
- example: "0x667dc4262BCDbf85190C01c996b4C06a461d2430"
whitheHackerGroupAddress: whitheHackerGroupAddress:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum Address that can claim the funds in an emergency when the maximum emergency mode time is exceeded. - description: Ethereum Address that can claim the funds in an emergency when the maximum emergency mode time is exceeded.
- example: "0x557dc4262BCDbf85190C01c996b4C06a461d2430"
keeperAddress: keeperAddress:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum Address that can enable emergency mode and modify the delay to make a withdrawal. - description: Ethereum Address that can enable emergency mode and modify the delay to make a withdrawal.
- example: "0x557dc4262BCDbf85190C01c996b4C06a461d2430"
withdrawalDelay: withdrawalDelay:
allOf: allOf:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'
- description: The time that anyone needs to wait until a withdrawal of the funds is allowed, in Ethereum blocks. - description: The time that anyone needs to wait until a withdrawal of the funds is allowed, in Ethereum blocks.
- example: 539573849
emergencyModeStartingTime: emergencyModeStartingTime:
type: integer type: integer
description: Ethereum block in which the emergency mode will be activated. description: Ethereum block in which the emergency mode will be activated.
@ -2140,6 +2203,7 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum address of the HEZ token. - description: Ethereum address of the HEZ token.
- example: "0x444dc4262BCDbf85190C01c996b4C06a461d2430"
maxTxVerifiers: maxTxVerifiers:
type: integer type: integer
description: Maximum transactions of the verifiers. description: Maximum transactions of the verifiers.
@ -2214,10 +2278,12 @@ components:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum address of the HEZ token. - description: Ethereum address of the HEZ token.
- example: "0x333dc4262BCDbf85190C01c996b4C06a461d2430"
rollupAddress: rollupAddress:
allOf: allOf:
- $ref: '#/components/schemas/EthereumAddress' - $ref: '#/components/schemas/EthereumAddress'
- description: Ethereum address of the rollup smart contract. - description: Ethereum address of the rollup smart contract.
- example: "0x222dc4262BCDbf85190C01c996b4C06a461d2430"
genesisBlockNum: genesisBlockNum:
allOf: allOf:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'

+ 1
- 4
cli/node/cfg.example.toml

@ -6,16 +6,13 @@ Port = 5432
Host = "localhost" Host = "localhost"
User = "hermez" User = "hermez"
Password = "yourpasswordhere" Password = "yourpasswordhere"
Name = "hermez"
[L2DB] [L2DB]
Name = "l2"
SafetyPeriod = 10 SafetyPeriod = 10
MaxTxs = 512 MaxTxs = 512
TTL = "24h" TTL = "24h"
[HistoryDB]
Name = "history"
[Web3] [Web3]
URL = "XXX" URL = "XXX"

+ 6
- 2
common/l1tx.go

@ -29,7 +29,9 @@ type L1Tx struct {
LoadAmount *big.Int LoadAmount *big.Int
EthBlockNum int64 // Ethereum Block Number in which this L1Tx was added to the queue EthBlockNum int64 // Ethereum Block Number in which this L1Tx was added to the queue
Type TxType Type TxType
BatchNum BatchNum
BatchNum *BatchNum
USD *float64
LoadAmountUSD *float64
} }
// Tx returns a *Tx from the L1Tx // Tx returns a *Tx from the L1Tx
@ -52,11 +54,13 @@ func (tx *L1Tx) Tx() *Tx {
FromBJJ: tx.FromBJJ, FromBJJ: tx.FromBJJ,
LoadAmount: tx.LoadAmount, LoadAmount: tx.LoadAmount,
EthBlockNum: tx.EthBlockNum, EthBlockNum: tx.EthBlockNum,
USD: tx.USD,
LoadAmountUSD: tx.LoadAmountUSD,
} }
if tx.LoadAmount != nil { if tx.LoadAmount != nil {
lf := new(big.Float).SetInt(tx.LoadAmount) lf := new(big.Float).SetInt(tx.LoadAmount)
loadAmountFloat, _ := lf.Float64() loadAmountFloat, _ := lf.Float64()
genericTx.LoadAmountFloat = loadAmountFloat
genericTx.LoadAmountFloat = &loadAmountFloat
} }
return genericTx return genericTx
} }

+ 13
- 3
common/l2tx.go

@ -13,7 +13,9 @@ type L2Tx struct {
FromIdx Idx FromIdx Idx
ToIdx Idx ToIdx Idx
Amount *big.Int Amount *big.Int
USD *float64
Fee FeeSelector Fee FeeSelector
FeeUSD *float64
Nonce Nonce Nonce Nonce
Type TxType Type TxType
EthBlockNum int64 // Ethereum Block Number in which this L2Tx was added to the queue EthBlockNum int64 // Ethereum Block Number in which this L2Tx was added to the queue
@ -23,6 +25,12 @@ type L2Tx struct {
func (tx *L2Tx) Tx() *Tx { func (tx *L2Tx) Tx() *Tx {
f := new(big.Float).SetInt(tx.Amount) f := new(big.Float).SetInt(tx.Amount)
amountFloat, _ := f.Float64() amountFloat, _ := f.Float64()
batchNum := new(BatchNum)
*batchNum = tx.BatchNum
fee := new(FeeSelector)
*fee = tx.Fee
nonce := new(Nonce)
*nonce = tx.Nonce
return &Tx{ return &Tx{
IsL1: false, IsL1: false,
TxID: tx.TxID, TxID: tx.TxID,
@ -31,11 +39,13 @@ func (tx *L2Tx) Tx() *Tx {
FromIdx: tx.FromIdx, FromIdx: tx.FromIdx,
ToIdx: tx.ToIdx, ToIdx: tx.ToIdx,
Amount: tx.Amount, Amount: tx.Amount,
USD: tx.USD,
AmountFloat: amountFloat, AmountFloat: amountFloat,
BatchNum: tx.BatchNum,
BatchNum: batchNum,
EthBlockNum: tx.EthBlockNum, EthBlockNum: tx.EthBlockNum,
Fee: tx.Fee,
Nonce: tx.Nonce,
Fee: fee,
FeeUSD: tx.FeeUSD,
Nonce: nonce,
} }
} }

+ 6
- 5
common/pooll2tx.go

@ -21,7 +21,7 @@ type PoolL2Tx struct {
TokenID TokenID `meddler:"token_id"` TokenID TokenID `meddler:"token_id"`
Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float16 Amount *big.Int `meddler:"amount,bigint"` // TODO: change to float16
AmountFloat float64 `meddler:"amount_f"` // TODO: change to float16 AmountFloat float64 `meddler:"amount_f"` // TODO: change to float16
USD float64 `meddler:"value_usd"` // TODO: change to float16
USD *float64 `meddler:"value_usd"` // TODO: change to float16
Fee FeeSelector `meddler:"fee"` Fee FeeSelector `meddler:"fee"`
Nonce Nonce `meddler:"nonce"` // effective 40 bits used Nonce Nonce `meddler:"nonce"` // effective 40 bits used
State PoolL2TxState `meddler:"state"` State PoolL2TxState `meddler:"state"`
@ -37,11 +37,12 @@ type PoolL2Tx struct {
RqAmount *big.Int `meddler:"rq_amount,bigintnull"` // TODO: change to float16 RqAmount *big.Int `meddler:"rq_amount,bigintnull"` // TODO: change to float16
RqFee FeeSelector `meddler:"rq_fee,zeroisnull"` RqFee FeeSelector `meddler:"rq_fee,zeroisnull"`
RqNonce uint64 `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used RqNonce uint64 `meddler:"rq_nonce,zeroisnull"` // effective 48 bits used
AbsoluteFee float64 `meddler:"fee_usd,zeroisnull"`
AbsoluteFeeUpdate time.Time `meddler:"usd_update,utctimez"`
AbsoluteFee *float64 `meddler:"fee_usd"`
AbsoluteFeeUpdate *time.Time `meddler:"usd_update,utctime"`
Type TxType `meddler:"tx_type"` Type TxType `meddler:"tx_type"`
// Extra metadata, may be uninitialized // Extra metadata, may be uninitialized
RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs RqTxCompressedData []byte `meddler:"-"` // 253 bits, optional for atomic txs
TokenSymbol string `meddler:"token_symbol"`
} }
// TxCompressedData spec: // TxCompressedData spec:
@ -186,8 +187,8 @@ func (tx *PoolL2Tx) Tx() *Tx {
FromIdx: tx.FromIdx, FromIdx: tx.FromIdx,
ToIdx: tx.ToIdx, ToIdx: tx.ToIdx,
Amount: tx.Amount, Amount: tx.Amount,
Nonce: tx.Nonce,
Fee: tx.Fee,
Nonce: &tx.Nonce,
Fee: &tx.Fee,
Type: tx.Type, Type: tx.Type,
} }
} }

+ 8
- 8
common/token.go

@ -14,14 +14,14 @@ const tokenIDBytesLen = 4
// Token is a struct that represents an Ethereum token that is supported in Hermez network // Token is a struct that represents an Ethereum token that is supported in Hermez network
type Token struct { type Token struct {
TokenID TokenID `meddler:"token_id"`
EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum block number in which this token was registered
EthAddr ethCommon.Address `meddler:"eth_addr"`
Name string `meddler:"name"`
Symbol string `meddler:"symbol"`
Decimals uint64 `meddler:"decimals"`
USD float64 `meddler:"usd,zeroisnull"`
USDUpdate time.Time `meddler:"usd_update,utctimez"`
TokenID TokenID `json:"id" meddler:"token_id"`
EthBlockNum int64 `json:"ethereumBlockNum" meddler:"eth_block_num"` // Ethereum block number in which this token was registered
EthAddr ethCommon.Address `json:"ethereumAddress" meddler:"eth_addr"`
Name string `json:"name" meddler:"name"`
Symbol string `json:"symbol" meddler:"symbol"`
Decimals uint64 `json:"decimals" meddler:"decimals"`
USD *float64 `json:"USD" meddler:"usd"`
USDUpdate *time.Time `json:"fiatUpdate" meddler:"usd_update,utctime"`
} }
// TokenInfo provides the price of the token in USD // TokenInfo provides the price of the token in USD

+ 17
- 17
common/tx.go

@ -41,30 +41,30 @@ const (
// Tx is a struct used by the TxSelector & BatchBuilder as a generic type generated from L1Tx & PoolL2Tx // Tx is a struct used by the TxSelector & BatchBuilder as a generic type generated from L1Tx & PoolL2Tx
type Tx struct { type Tx struct {
// Generic // Generic
IsL1 bool `meddler:"is_l1"`
TxID TxID `meddler:"id"`
Type TxType `meddler:"type"`
Position int `meddler:"position"`
FromIdx Idx `meddler:"from_idx"`
ToIdx Idx `meddler:"to_idx"`
Amount *big.Int `meddler:"amount,bigint"`
AmountFloat float64 `meddler:"amount_f"`
TokenID TokenID `meddler:"token_id"`
USD float64 `meddler:"amount_usd,zeroisnull"`
BatchNum BatchNum `meddler:"batch_num,zeroisnull"` // batchNum in which this tx was forged. If the tx is L2, this must be != 0
EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum Block Number in which this L1Tx was added to the queue
IsL1 bool `meddler:"is_l1"`
TxID TxID `meddler:"id"`
Type TxType `meddler:"type"`
Position int `meddler:"position"`
FromIdx Idx `meddler:"from_idx"`
ToIdx Idx `meddler:"to_idx"`
Amount *big.Int `meddler:"amount,bigint"`
AmountFloat float64 `meddler:"amount_f"`
TokenID TokenID `meddler:"token_id"`
USD *float64 `meddler:"amount_usd"`
BatchNum *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
// 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"` FromEthAddr ethCommon.Address `meddler:"from_eth_addr"`
FromBJJ *babyjub.PublicKey `meddler:"from_bjj"` FromBJJ *babyjub.PublicKey `meddler:"from_bjj"`
LoadAmount *big.Int `meddler:"load_amount,bigintnull"` LoadAmount *big.Int `meddler:"load_amount,bigintnull"`
LoadAmountFloat float64 `meddler:"load_amount_f"`
LoadAmountUSD float64 `meddler:"load_amount_usd,zeroisnull"`
LoadAmountFloat *float64 `meddler:"load_amount_f"`
LoadAmountUSD *float64 `meddler:"load_amount_usd"`
// L2 // L2
Fee FeeSelector `meddler:"fee,zeroisnull"`
FeeUSD float64 `meddler:"fee_usd,zeroisnull"`
Nonce Nonce `meddler:"nonce,zeroisnull"`
Fee *FeeSelector `meddler:"fee"`
FeeUSD *float64 `meddler:"fee_usd"`
Nonce *Nonce `meddler:"nonce"`
} }
// L1Tx returns a *L1Tx from the Tx // L1Tx returns a *L1Tx from the Tx

+ 1
- 4
config/config.go

@ -36,7 +36,6 @@ type Coordinator struct {
ForgerAddress ethCommon.Address `validate:"required"` ForgerAddress ethCommon.Address `validate:"required"`
ForgeLoopInterval Duration `validate:"required"` ForgeLoopInterval Duration `validate:"required"`
L2DB struct { L2DB struct {
Name string `validate:"required"`
SafetyPeriod common.BatchNum `validate:"required"` SafetyPeriod common.BatchNum `validate:"required"`
MaxTxs uint32 `validate:"required"` MaxTxs uint32 `validate:"required"`
TTL Duration `validate:"required"` TTL Duration `validate:"required"`
@ -60,9 +59,7 @@ type Node struct {
Host string `validate:"required"` Host string `validate:"required"`
User string `validate:"required"` User string `validate:"required"`
Password string `validate:"required"` Password string `validate:"required"`
} `validate:"required"`
HistoryDB struct {
Name string `validate:"required"`
Name string `validate:"required"`
} `validate:"required"` } `validate:"required"`
Web3 struct { Web3 struct {
URL string `validate:"required"` URL string `validate:"required"`

+ 3
- 1
coordinator/coordinator_test.go

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/hermeznetwork/hermez-node/batchbuilder" "github.com/hermeznetwork/hermez-node/batchbuilder"
dbUtils "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/db/l2db"
"github.com/hermeznetwork/hermez-node/db/statedb" "github.com/hermeznetwork/hermez-node/db/statedb"
@ -26,8 +27,9 @@ func newTestModules(t *testing.T) (*txselector.TxSelector, *batchbuilder.BatchBu
assert.Nil(t, err) assert.Nil(t, err)
pass := os.Getenv("POSTGRES_PASS") pass := os.Getenv("POSTGRES_PASS")
l2DB, err := l2db.NewL2DB(5432, "localhost", "hermez", pass, "l2", 10, 512, 24*time.Hour)
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
require.Nil(t, err) require.Nil(t, err)
l2DB := l2db.NewL2DB(db, 10, 100, 24*time.Hour)
txselDir, err := ioutil.TempDir("", "tmpTxSelDB") txselDir, err := ioutil.TempDir("", "tmpTxSelDB")
require.Nil(t, err) require.Nil(t, err)

+ 35
- 67
db/historydb/historydb.go

@ -6,16 +6,13 @@ import (
"fmt" "fmt"
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/gobuffalo/packr/v2"
"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/log"
"github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/babyjub"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
//nolint:errcheck // driver for postgres DB //nolint:errcheck // driver for postgres DB
_ "github.com/lib/pq" _ "github.com/lib/pq"
migrate "github.com/rubenv/sql-migrate"
"github.com/russross/meddler" "github.com/russross/meddler"
) )
@ -57,28 +54,8 @@ type BatchData struct {
} }
// NewHistoryDB initialize the DB // NewHistoryDB initialize the DB
func NewHistoryDB(port int, host, user, password, dbname string) (*HistoryDB, error) {
// Connect to DB
psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
hdb, err := sqlx.Connect("postgres", psqlconn)
if err != nil {
return nil, err
}
// Init meddler
db.InitMeddler()
meddler.Default = meddler.PostgreSQL
// Run DB migrations
migrations := &migrate.PackrMigrationSource{
Box: packr.New("history-migrations", "./migrations"),
}
nMigrations, err := migrate.Exec(hdb.DB, "postgres", migrations, migrate.Up)
if err != nil {
return nil, err
}
log.Debug("HistoryDB applied ", nMigrations, " migrations for ", dbname, " database")
return &HistoryDB{hdb}, nil
func NewHistoryDB(db *sqlx.DB) *HistoryDB {
return &HistoryDB{db: db}
} }
// AddBlock insert a block into the DB // AddBlock insert a block into the DB
@ -328,8 +305,14 @@ func (hdb *HistoryDB) GetAccounts() ([]*common.Account, error) {
return accs, err return accs, err
} }
// AddL1Txs inserts L1 txs to the DB
// AddL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
func (hdb *HistoryDB) AddL1Txs(l1txs []common.L1Tx) error { return hdb.addL1Txs(hdb.db, l1txs) } func (hdb *HistoryDB) AddL1Txs(l1txs []common.L1Tx) error { return hdb.addL1Txs(hdb.db, l1txs) }
// addL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
func (hdb *HistoryDB) addL1Txs(d meddler.DB, l1txs []common.L1Tx) error { func (hdb *HistoryDB) addL1Txs(d meddler.DB, l1txs []common.L1Tx) error {
txs := []common.Tx{} txs := []common.Tx{}
for _, tx := range l1txs { for _, tx := range l1txs {
@ -338,8 +321,10 @@ func (hdb *HistoryDB) addL1Txs(d meddler.DB, l1txs []common.L1Tx) error {
return hdb.addTxs(d, txs) return hdb.addTxs(d, txs)
} }
// AddL2Txs inserts L2 txs to the DB
// AddL2Txs inserts L2 txs to the DB. USD and FeeUSD will be set automatically before storing the tx.
func (hdb *HistoryDB) AddL2Txs(l2txs []common.L2Tx) error { return hdb.addL2Txs(hdb.db, l2txs) } func (hdb *HistoryDB) AddL2Txs(l2txs []common.L2Tx) error { return hdb.addL2Txs(hdb.db, l2txs) }
// addL2Txs inserts L2 txs to the DB. USD and FeeUSD will be set automatically before storing the tx.
func (hdb *HistoryDB) addL2Txs(d meddler.DB, l2txs []common.L2Tx) error { func (hdb *HistoryDB) addL2Txs(d meddler.DB, l2txs []common.L2Tx) error {
txs := []common.Tx{} txs := []common.Tx{}
for _, tx := range l2txs { for _, tx := range l2txs {
@ -348,8 +333,6 @@ func (hdb *HistoryDB) addL2Txs(d meddler.DB, l2txs []common.L2Tx) error {
return hdb.addTxs(d, txs) return hdb.addTxs(d, txs)
} }
// AddTxs insert L1 txs into the DB
func (hdb *HistoryDB) AddTxs(txs []common.Tx) error { return hdb.addTxs(hdb.db, txs) }
func (hdb *HistoryDB) addTxs(d meddler.DB, txs []common.Tx) error { func (hdb *HistoryDB) addTxs(d meddler.DB, txs []common.Tx) error {
return db.BulkInsert( return db.BulkInsert(
d, d,
@ -381,16 +364,6 @@ func (hdb *HistoryDB) addTxs(d meddler.DB, txs []common.Tx) error {
) )
} }
// SetBatchNumL1UserTxs sets the batchNum in all the L1UserTxs with toForgeL1TxsNum.
func (hdb *HistoryDB) SetBatchNumL1UserTxs(toForgeL1TxsNum, batchNum int64) error {
return hdb.setBatchNumL1UserTxs(hdb.db, toForgeL1TxsNum, batchNum)
}
func (hdb *HistoryDB) setBatchNumL1UserTxs(d meddler.DB, toForgeL1TxsNum, batchNum int64) error {
_, err := d.Exec("UPDATE tx SET batch_num = $1 WHERE to_forge_l1_txs_num = $2 AND is_l1 = TRUE AND user_origin = TRUE;",
batchNum, toForgeL1TxsNum)
return err
}
// GetTxs returns a list of txs from the DB // GetTxs returns a list of txs from the DB
func (hdb *HistoryDB) GetTxs() ([]*common.Tx, error) { func (hdb *HistoryDB) GetTxs() ([]*common.Tx, error) {
var txs []*common.Tx var txs []*common.Tx
@ -413,8 +386,10 @@ func (hdb *HistoryDB) GetHistoryTxs(
} }
var query string var query string
var args []interface{} var args []interface{}
queryStr := `SELECT tx.*, tx.amount_f * token.usd AS current_usd,
token.symbol, token.usd_update, block.timestamp, count(*) OVER() AS total_items FROM tx
queryStr := `SELECT tx.*, token.token_id, token.eth_block_num AS token_block,
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
token.usd_update, block.timestamp, count(*) OVER() AS total_items
FROM tx
INNER JOIN token ON tx.token_id = token.token_id INNER JOIN token ON tx.token_id = token.token_id
INNER JOIN block ON tx.eth_block_num = block.eth_block_num ` INNER JOIN block ON tx.eth_block_num = block.eth_block_num `
// Apply filters // Apply filters
@ -513,17 +488,17 @@ func (hdb *HistoryDB) GetTx(txID common.TxID) (*common.Tx, error) {
) )
} }
// GetL1UserTxs gets L1 User Txs to be forged in a batch that will create an account
// TODO: This is currently not used. Figure out if it should be used somewhere or removed.
func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]*common.Tx, error) {
var txs []*common.Tx
err := meddler.QueryAll(
hdb.db, &txs,
"SELECT * FROM tx WHERE to_forge_l1_txs_num = $1 AND is_l1 = TRUE AND user_origin = TRUE;",
toForgeL1TxsNum,
)
return txs, err
}
// // GetL1UserTxs gets L1 User Txs to be forged in a batch that will create an account
// // TODO: This is currently not used. Figure out if it should be used somewhere or removed.
// func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]*common.Tx, error) {
// var txs []*common.Tx
// err := meddler.QueryAll(
// hdb.db, &txs,
// "SELECT * FROM tx WHERE to_forge_l1_txs_num = $1 AND is_l1 = TRUE AND user_origin = TRUE;",
// toForgeL1TxsNum,
// )
// return txs, err
// }
// TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value. // TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value.
@ -586,11 +561,15 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) {
// Add Batches // Add Batches
for _, batch := range blockData.Batches { for _, batch := range blockData.Batches {
// Add Batch: this will trigger an update on the DB
// that will set the batch num of forged L1 txs in this batch
err = hdb.addBatch(txn, batch.Batch)
if err != nil {
return err
}
// Add unforged l1 Txs
if batch.L1Batch { if batch.L1Batch {
err = hdb.setBatchNumL1UserTxs(txn, batch.Batch.ForgeL1TxsNum, int64(batch.Batch.BatchNum))
if err != nil {
return err
}
if len(batch.L1CoordinatorTxs) > 0 { if len(batch.L1CoordinatorTxs) > 0 {
err = hdb.addL1Txs(txn, batch.L1CoordinatorTxs) err = hdb.addL1Txs(txn, batch.L1CoordinatorTxs)
if err != nil { if err != nil {
@ -623,19 +602,8 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) {
} }
} }
// Add Batch
err = hdb.addBatch(txn, batch.Batch)
if err != nil {
return err
}
// TODO: INSERT CONTRACTS VARS // TODO: INSERT CONTRACTS VARS
} }
return txn.Commit() return txn.Commit()
} }
// Close frees the resources used by HistoryDB
func (hdb *HistoryDB) Close() error {
return hdb.db.Close()
}

+ 85
- 81
db/historydb/historydb_test.go

@ -1,7 +1,6 @@
package historydb package historydb
import ( import (
"fmt"
"math/big" "math/big"
"os" "os"
"testing" "testing"
@ -9,6 +8,8 @@ import (
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
dbUtils "github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/log"
"github.com/hermeznetwork/hermez-node/test" "github.com/hermeznetwork/hermez-node/test"
"github.com/iden3/go-iden3-crypto/babyjub" "github.com/iden3/go-iden3-crypto/babyjub"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -26,17 +27,20 @@ var historyDB *HistoryDB
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
// init DB // init DB
var err error
pass := os.Getenv("POSTGRES_PASS") pass := os.Getenv("POSTGRES_PASS")
historyDB, err = NewHistoryDB(5432, "localhost", "hermez", pass, "history")
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
if err != nil {
panic(err)
}
historyDB = NewHistoryDB(db)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Run tests // Run tests
result := m.Run() result := m.Run()
// Close DB // Close DB
if err := historyDB.Close(); err != nil {
fmt.Println("Error closing the history DB:", err)
if err := db.Close(); err != nil {
log.Error("Error closing the history DB:", err)
} }
os.Exit(result) os.Exit(result)
} }
@ -145,15 +149,6 @@ func TestTokens(t *testing.T) {
tokens := test.GenTokens(nTokens, blocks) tokens := test.GenTokens(nTokens, blocks)
err := historyDB.AddTokens(tokens) err := historyDB.AddTokens(tokens)
assert.NoError(t, err) assert.NoError(t, err)
// Update price of generated tokens without price
for i := 0; i < len(tokens); i++ {
if tokens[i].USD == 0 {
value := 3.33 + float64(i)
tokens[i].USD = value
err := historyDB.UpdateTokenValue(tokens[i].TokenID, value)
assert.NoError(t, err)
}
}
// Fetch tokens // Fetch tokens
fetchedTokens, err := historyDB.GetTokens() fetchedTokens, err := historyDB.GetTokens()
assert.NoError(t, err) assert.NoError(t, err)
@ -166,7 +161,11 @@ func TestTokens(t *testing.T) {
assert.Equal(t, tokens[i].Name, token.Name) assert.Equal(t, tokens[i].Name, token.Name)
assert.Equal(t, tokens[i].Symbol, token.Symbol) assert.Equal(t, tokens[i].Symbol, token.Symbol)
assert.Equal(t, tokens[i].USD, token.USD) assert.Equal(t, tokens[i].USD, token.USD)
assert.Greater(t, int64(1*time.Second), int64(time.Since(token.USDUpdate)))
if token.USDUpdate != nil {
assert.Greater(t, int64(1*time.Second), int64(time.Since(*token.USDUpdate)))
} else {
assert.Equal(t, tokens[i].USDUpdate, token.USDUpdate)
}
} }
} }
@ -205,12 +204,8 @@ func TestTxs(t *testing.T) {
// Prepare blocks in the DB // Prepare blocks in the DB
blocks := setTestBlocks(fromBlock, toBlock) blocks := setTestBlocks(fromBlock, toBlock)
// Generate fake tokens // Generate fake tokens
const nTokens = 5
const tokenValue = 1.23456
const nTokens = 500
tokens := test.GenTokens(nTokens, blocks) tokens := test.GenTokens(nTokens, blocks)
for i := 0; i < len(tokens); i++ {
tokens[i].USD = tokenValue
}
err := historyDB.AddTokens(tokens) err := historyDB.AddTokens(tokens)
assert.NoError(t, err) assert.NoError(t, err)
// Generate fake batches // Generate fake batches
@ -224,60 +219,17 @@ func TestTxs(t *testing.T) {
err = historyDB.AddAccounts(accs) err = historyDB.AddAccounts(accs)
assert.NoError(t, err) assert.NoError(t, err)
// Generate fake L1 txs // Generate fake L1 txs
const nL1s = 30
const nL1s = 64
_, l1txs := test.GenL1Txs(0, nL1s, 0, nil, accs, tokens, blocks, batches) _, l1txs := test.GenL1Txs(0, nL1s, 0, nil, accs, tokens, blocks, batches)
err = historyDB.AddL1Txs(l1txs) err = historyDB.AddL1Txs(l1txs)
assert.NoError(t, err) assert.NoError(t, err)
// Generate fake L2 txs // Generate fake L2 txs
const nL2s = 20
const nL2s = 2048 - nL1s
_, l2txs := test.GenL2Txs(0, nL2s, 0, nil, accs, tokens, blocks, batches) _, l2txs := test.GenL2Txs(0, nL2s, 0, nil, accs, tokens, blocks, batches)
err = historyDB.AddL2Txs(l2txs) err = historyDB.AddL2Txs(l2txs)
assert.NoError(t, err) assert.NoError(t, err)
// Compare fetched txs vs generated txs. // Compare fetched txs vs generated txs.
for i := 0; i < len(l1txs); i++ {
tx := l1txs[i].Tx()
fetchedTx, err := historyDB.GetTx(tx.TxID)
assert.NoError(t, err)
tx.USD = tokenValue * tx.AmountFloat
if fetchedTx.USD > tx.USD {
assert.Less(t, 0.999, tx.USD/fetchedTx.USD)
} else {
assert.Less(t, 0.999, fetchedTx.USD/tx.USD)
}
tx.LoadAmountUSD = tokenValue * tx.LoadAmountFloat
if fetchedTx.LoadAmountUSD > tx.LoadAmountUSD {
assert.Less(t, 0.999, tx.LoadAmountUSD/fetchedTx.LoadAmountUSD)
} else {
assert.Less(t, 0.999, fetchedTx.LoadAmountUSD/tx.LoadAmountUSD)
}
tx.LoadAmountUSD = 0
tx.USD = 0
fetchedTx.LoadAmountUSD = 0
fetchedTx.USD = 0
assert.Equal(t, tx, fetchedTx)
}
for i := 0; i < len(l2txs); i++ {
tx := l2txs[i].Tx()
fetchedTx, err := historyDB.GetTx(tx.TxID)
assert.NoError(t, err)
tx.USD = tokenValue * tx.AmountFloat
if fetchedTx.USD > tx.USD {
assert.Less(t, 0.999, tx.USD/fetchedTx.USD)
} else {
assert.Less(t, 0.999, fetchedTx.USD/tx.USD)
}
tx.FeeUSD = tx.USD * tx.Fee.Percentage()
if fetchedTx.FeeUSD > tx.FeeUSD {
assert.Less(t, 0.999, tx.FeeUSD/fetchedTx.FeeUSD)
} else if fetchedTx.FeeUSD < tx.FeeUSD {
assert.Less(t, 0.999, fetchedTx.FeeUSD/tx.FeeUSD)
}
tx.FeeUSD = 0
tx.USD = 0
fetchedTx.FeeUSD = 0
fetchedTx.USD = 0
assert.Equal(t, tx, fetchedTx)
}
fetchAndAssertTxs(t, l1txs, l2txs)
// Test trigger: L1 integrity // Test trigger: L1 integrity
// from_eth_addr can't be null // from_eth_addr can't be null
l1txs[0].FromEthAddr = ethCommon.Address{} l1txs[0].FromEthAddr = ethCommon.Address{}
@ -304,23 +256,75 @@ func TestTxs(t *testing.T) {
l2txs[0].Nonce = 0 l2txs[0].Nonce = 0
err = historyDB.AddL2Txs(l2txs) err = historyDB.AddL2Txs(l2txs)
assert.Error(t, err) assert.Error(t, err)
// Test trigger: forge L1 txs
// add next batch to DB
batchNum, toForgeL1TxsNum := test.GetNextToForgeNumAndBatch(batches)
batch := batches[0]
batch.BatchNum = batchNum
batch.ForgeL1TxsNum = toForgeL1TxsNum
assert.NoError(t, historyDB.AddBatch(&batch)) // This should update nL1s / 2 rows
// Set batch num in txs that should have been marked as forged in the DB
for i := 0; i < len(l1txs); i++ {
fetchedTx, err := historyDB.GetTx(l1txs[i].TxID)
assert.NoError(t, err)
if l1txs[i].ToForgeL1TxsNum == toForgeL1TxsNum {
assert.Equal(t, batchNum, *fetchedTx.BatchNum)
} else {
if fetchedTx.BatchNum != nil {
assert.NotEqual(t, batchNum, *fetchedTx.BatchNum)
}
}
}
// Test helper functions for Synchronizer // Test helper functions for Synchronizer
txs, err := historyDB.GetL1UserTxs(2)
assert.NoError(t, err)
assert.NotZero(t, len(txs))
position, err := historyDB.GetLastTxsPosition(2)
assert.NoError(t, err)
assert.Equal(t, 22, position)
// Test Update L1 TX Batch_num
assert.Equal(t, common.BatchNum(0), txs[0].BatchNum)
txs[0].BatchNum = common.BatchNum(1)
// err = historyDB.UpdateTxsBatchNum(txs)
err = historyDB.SetBatchNumL1UserTxs(2, 1)
assert.NoError(t, err)
txs, err = historyDB.GetL1UserTxs(2)
// GetLastTxsPosition
expectedPosition := -1
var choosenToForgeL1TxsNum int64 = -1
for _, tx := range l1txs {
if choosenToForgeL1TxsNum == -1 && tx.ToForgeL1TxsNum > 0 {
choosenToForgeL1TxsNum = tx.ToForgeL1TxsNum
expectedPosition = tx.Position
} else if choosenToForgeL1TxsNum == tx.ToForgeL1TxsNum && expectedPosition < tx.Position {
expectedPosition = tx.Position
}
}
position, err := historyDB.GetLastTxsPosition(choosenToForgeL1TxsNum)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotZero(t, len(txs))
assert.Equal(t, common.BatchNum(1), txs[0].BatchNum)
assert.Equal(t, expectedPosition, position)
// GetL1UserTxs: not needed? tests were broken
// txs, err := historyDB.GetL1UserTxs(2)
// assert.NoError(t, err)
// assert.NotZero(t, len(txs))
// assert.NoError(t, err)
// assert.Equal(t, 22, position)
// // Test Update L1 TX Batch_num
// assert.Equal(t, common.BatchNum(0), txs[0].BatchNum)
// txs[0].BatchNum = common.BatchNum(1)
// txs, err = historyDB.GetL1UserTxs(2)
// assert.NoError(t, err)
// assert.NotZero(t, len(txs))
// assert.Equal(t, common.BatchNum(1), txs[0].BatchNum)
}
func fetchAndAssertTxs(t *testing.T, l1txs []common.L1Tx, l2txs []common.L2Tx) {
for i := 0; i < len(l1txs); i++ {
tx := l1txs[i].Tx()
fetchedTx, err := historyDB.GetTx(tx.TxID)
assert.NoError(t, err)
test.AssertUSD(t, tx.USD, fetchedTx.USD)
test.AssertUSD(t, tx.LoadAmountUSD, fetchedTx.LoadAmountUSD)
assert.Equal(t, tx, fetchedTx)
}
for i := 0; i < len(l2txs); i++ {
tx := l2txs[i].Tx()
fetchedTx, err := historyDB.GetTx(tx.TxID)
tx.TokenID = fetchedTx.TokenID
assert.NoError(t, err)
test.AssertUSD(t, fetchedTx.USD, tx.USD)
test.AssertUSD(t, fetchedTx.FeeUSD, tx.FeeUSD)
assert.Equal(t, tx, fetchedTx)
}
} }
func TestExitTree(t *testing.T) { func TestExitTree(t *testing.T) {

+ 0
- 209
db/historydb/migrations/001_init.sql

@ -1,209 +0,0 @@
-- +migrate Up
CREATE TABLE block (
eth_block_num BIGINT PRIMARY KEY,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
hash BYTEA NOT NULL
);
CREATE TABLE coordinator (
forger_addr BYTEA NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
withdraw_addr BYTEA NOT NULL,
url VARCHAR(200) NOT NULL,
PRIMARY KEY (forger_addr, eth_block_num)
);
CREATE TABLE batch (
batch_num BIGINT PRIMARY KEY,
eth_block_num BIGINT REFERENCES block (eth_block_num) ON DELETE CASCADE,
forger_addr BYTEA NOT NULL, -- fake foreign key for coordinator
fees_collected BYTEA NOT NULL,
state_root BYTEA NOT NULL,
num_accounts BIGINT NOT NULL,
exit_root BYTEA NOT NULL,
forge_l1_txs_num BIGINT,
slot_num BIGINT NOT NULL
);
CREATE TABLE exit_tree (
batch_num BIGINT REFERENCES batch (batch_num) ON DELETE CASCADE,
account_idx BIGINT,
merkle_proof BYTEA NOT NULL,
balance BYTEA NOT NULL,
instant_withdrawn BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL,
delayed_withdraw_request BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL,
delayed_withdrawn BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL,
PRIMARY KEY (batch_num, account_idx)
);
CREATE TABLE bid (
slot_num BIGINT NOT NULL,
bid_value BYTEA NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
forger_addr BYTEA NOT NULL, -- fake foreign key for coordinator
PRIMARY KEY (slot_num, bid_value)
);
CREATE TABLE token (
token_id INT PRIMARY KEY,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
eth_addr BYTEA UNIQUE NOT NULL,
name VARCHAR(20) NOT NULL,
symbol VARCHAR(10) NOT NULL,
decimals INT NOT NULL,
usd NUMERIC,
usd_update TIMESTAMP
);
-- +migrate StatementBegin
CREATE FUNCTION set_token_usd_update()
RETURNS TRIGGER
AS
$BODY$
BEGIN
IF NEW."usd" IS NOT NULL AND NEW."usd_update" IS NULL THEN
NEW."usd_update" = timezone('utc', now());
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- +migrate StatementEnd
CREATE TRIGGER trigger_token_usd_update BEFORE UPDATE OR INSERT ON token
FOR EACH ROW EXECUTE PROCEDURE set_token_usd_update();
CREATE TABLE tx (
-- Generic TX
is_l1 BOOLEAN NOT NULL,
id BYTEA PRIMARY KEY,
type VARCHAR(40) NOT NULL,
position INT NOT NULL,
from_idx BIGINT NOT NULL,
to_idx BIGINT NOT NULL,
amount BYTEA NOT NULL,
amount_f NUMERIC NOT NULL,
token_id INT NOT NULL REFERENCES token (token_id),
amount_usd NUMERIC, -- Value of the amount in USD at the moment the tx was inserted in the DB
batch_num BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL, -- Can be NULL in the case of L1 txs that are on the queue but not forged yet.
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
-- L1
to_forge_l1_txs_num BIGINT,
user_origin BOOLEAN,
from_eth_addr BYTEA,
from_bjj BYTEA,
load_amount BYTEA,
load_amount_f NUMERIC,
load_amount_usd NUMERIC,
-- L2
fee INT,
fee_usd NUMERIC,
nonce BIGINT
);
CREATE INDEX tx_order ON tx (batch_num, position);
-- +migrate StatementBegin
CREATE FUNCTION set_tx()
RETURNS TRIGGER
AS
$BODY$
DECLARE token_value NUMERIC := (SELECT usd FROM token WHERE token_id = NEW.token_id);
BEGIN
-- Validate L1/L2 constrains
IF NEW.is_l1 AND (( -- L1 mandatory fields
NEW.user_origin IS NULL OR
NEW.from_eth_addr IS NULL OR
NEW.from_bjj IS NULL OR
NEW.load_amount IS NULL OR
NEW.load_amount_f IS NULL
) OR (NOT NEW.user_origin AND NEW.batch_num IS NULL)) THEN -- If is Coordinator L1, must include batch_num
RAISE EXCEPTION 'Invalid L1 tx.';
ELSIF NOT NEW.is_l1 THEN
IF NEW.fee IS NULL THEN
NEW.fee = (SELECT 0);
END IF;
IF NEW.batch_num IS NULL OR NEW.nonce IS NULL THEN
RAISE EXCEPTION 'Invalid L2 tx.';
END IF;
END IF;
-- If is L2, add token_id
IF NEW.token_id IS NULL THEN
NEW."token_id" = (SELECT token_id FROM account WHERE idx = NEW."from_idx");
END IF;
-- Set value_usd
NEW."amount_usd" = (SELECT token_value * NEW.amount_f);
NEW."load_amount_usd" = (SELECT token_value * NEW.load_amount_f);
IF NOT NEW.is_l1 THEN
NEW."fee_usd" = (SELECT token_value * NEW.amount_f * CASE
WHEN NEW.fee = 0 THEN 0
WHEN NEW.fee >= 1 AND NEW.fee <= 32 THEN POWER(10,-24+(NEW.fee::float/2))
WHEN NEW.fee >= 33 AND NEW.fee <= 223 THEN POWER(10,-8+(0.041666666666667*(NEW.fee::float-32)))
WHEN NEW.fee >= 224 AND NEW.fee <= 255 THEN POWER(10,NEW.fee-224) END);
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- +migrate StatementEnd
CREATE TRIGGER trigger_set_tx BEFORE INSERT ON tx
FOR EACH ROW EXECUTE PROCEDURE set_tx();
-- +migrate StatementBegin
CREATE FUNCTION forge_l1_user_txs()
RETURNS TRIGGER
AS
$BODY$
BEGIN
IF NEW.forge_l1_txs_num IS NOT NULL THEN
UPDATE tx
SET batch_num = NEW.batch_num
WHERE user_origin AND NEW.forge_l1_txs_num = to_forge_l1_txs_num;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- +migrate StatementEnd
CREATE TRIGGER trigger_forge_l1_txs AFTER INSERT ON batch
FOR EACH ROW EXECUTE PROCEDURE forge_l1_user_txs();
CREATE TABLE account (
idx BIGINT PRIMARY KEY,
token_id INT NOT NULL REFERENCES token (token_id) ON DELETE CASCADE,
batch_num BIGINT NOT NULL REFERENCES batch (batch_num) ON DELETE CASCADE,
bjj BYTEA NOT NULL,
eth_addr BYTEA NOT NULL
);
CREATE TABLE rollup_vars (
eth_block_num BIGINT PRIMARY KEY REFERENCES block (eth_block_num) ON DELETE CASCADE,
forge_l1_timeout BYTEA NOT NULL,
fee_l1_user_tx BYTEA NOT NULL,
fee_add_token BYTEA NOT NULL,
tokens_hez BYTEA NOT NULL,
governance BYTEA NOT NULL
);
CREATE TABLE consensus_vars (
eth_block_num BIGINT PRIMARY KEY REFERENCES block (eth_block_num) ON DELETE CASCADE,
slot_deadline INT NOT NULL,
close_auction_slots INT NOT NULL,
open_auction_slots INT NOT NULL,
min_bid_slots VARCHAR(200) NOT NULL,
outbidding INT NOT NULL,
donation_address BYTEA NOT NULL,
governance_address BYTEA NOT NULL,
allocation_ratio VARCHAR(200)
);
-- +migrate Down
DROP TABLE consensus_vars;
DROP TABLE rollup_vars;
DROP TABLE account;
DROP TABLE tx;
DROP TABLE token;
DROP TABLE bid;
DROP TABLE exit_tree;
DROP TABLE batch;
DROP TABLE coordinator;
DROP TABLE block;

+ 31
- 27
db/historydb/views.go

@ -13,34 +13,38 @@ import (
// 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 HistoryTx struct {
// Generic // Generic
IsL1 bool `meddler:"is_l1"`
TxID common.TxID `meddler:"id"`
Type common.TxType `meddler:"type"`
Position int `meddler:"position"`
FromIdx common.Idx `meddler:"from_idx"`
ToIdx common.Idx `meddler:"to_idx"`
Amount *big.Int `meddler:"amount,bigint"`
AmountFloat float64 `meddler:"amount_f"`
TokenID common.TokenID `meddler:"token_id"`
USD float64 `meddler:"amount_usd,zeroisnull"`
BatchNum common.BatchNum `meddler:"batch_num,zeroisnull"` // batchNum in which this tx was forged. If the tx is L2, this must be != 0
EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum Block Number in which this L1Tx was added to the queue
IsL1 bool `meddler:"is_l1"`
TxID common.TxID `meddler:"id"`
Type common.TxType `meddler:"type"`
Position int `meddler:"position"`
FromIdx common.Idx `meddler:"from_idx"`
ToIdx common.Idx `meddler:"to_idx"`
Amount *big.Int `meddler:"amount,bigint"`
AmountFloat float64 `meddler:"amount_f"`
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
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
UserOrigin bool `meddler:"user_origin"` // true if the tx was originated by a user, false if it was aoriginated by a coordinator. Note that this differ from the spec for implementation simplification purpposes
FromEthAddr ethCommon.Address `meddler:"from_eth_addr"`
FromBJJ *babyjub.PublicKey `meddler:"from_bjj"`
LoadAmount *big.Int `meddler:"load_amount,bigintnull"`
LoadAmountFloat float64 `meddler:"load_amount_f"`
LoadAmountUSD float64 `meddler:"load_amount_usd,zeroisnull"`
ToForgeL1TxsNum int64 `meddler:"to_forge_l1_txs_num"` // toForgeL1TxsNum in which the tx was forged / will be forged
UserOrigin bool `meddler:"user_origin"` // true if the tx was originated by a user, false if it was aoriginated by a coordinator. Note that this differ from the spec for implementation simplification purpposes
FromEthAddr ethCommon.Address `meddler:"from_eth_addr"`
FromBJJ *babyjub.PublicKey `meddler:"from_bjj"`
LoadAmount *big.Int `meddler:"load_amount,bigintnull"`
LoadAmountFloat *float64 `meddler:"load_amount_f"`
HistoricLoadAmountUSD *float64 `meddler:"load_amount_usd"`
// L2 // L2
Fee common.FeeSelector `meddler:"fee,zeroisnull"`
FeeUSD float64 `meddler:"fee_usd,zeroisnull"`
Nonce common.Nonce `meddler:"nonce,zeroisnull"`
Fee *common.FeeSelector `meddler:"fee"`
HistoricFeeUSD *float64 `meddler:"fee_usd"`
Nonce *common.Nonce `meddler:"nonce"`
// API extras // API extras
Timestamp time.Time `meddler:"timestamp,utctime"`
TokenSymbol string `meddler:"symbol"`
CurrentUSD float64 `meddler:"current_usd"`
USDUpdate time.Time `meddler:"usd_update,utctime"`
TotalItems int `meddler:"total_items"`
Timestamp time.Time `meddler:"timestamp,utctime"`
TotalItems int `meddler:"total_items"`
TokenID common.TokenID `meddler:"token_id"`
TokenEthBlockNum int64 `meddler:"token_block"`
TokenEthAddr ethCommon.Address `meddler:"eth_addr"`
TokenName string `meddler:"name"`
TokenSymbol string `meddler:"symbol"`
TokenDecimals uint64 `meddler:"decimals"`
TokenUSD *float64 `meddler:"usd"`
TokenUSDUpdate *time.Time `meddler:"usd_update"`
} }

+ 66
- 78
db/l2db/l2db.go

@ -1,19 +1,16 @@
package l2db package l2db
import ( import (
"fmt"
"math/big"
"time" "time"
ethCommon "github.com/ethereum/go-ethereum/common" ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/gobuffalo/packr/v2"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/log"
"github.com/iden3/go-iden3-crypto/babyjub"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
//nolint:errcheck // driver for postgres DB //nolint:errcheck // driver for postgres DB
_ "github.com/lib/pq" _ "github.com/lib/pq"
migrate "github.com/rubenv/sql-migrate"
"github.com/russross/meddler" "github.com/russross/meddler"
) )
@ -29,40 +26,15 @@ type L2DB struct {
} }
// NewL2DB creates a L2DB. // NewL2DB creates a L2DB.
// To create it, it's needed postgres configuration, safety period expressed in batches,
// To create it, it's needed db connection, safety period expressed in batches,
// maxTxs that the DB should have and TTL (time to live) for pending txs. // maxTxs that the DB should have and TTL (time to live) for pending txs.
func NewL2DB(
port int, host, user, password, dbname string,
safetyPeriod common.BatchNum,
maxTxs uint32,
TTL time.Duration,
) (*L2DB, error) {
// init meddler
db.InitMeddler()
meddler.Default = meddler.PostgreSQL
// Stablish DB connection
psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname)
db, err := sqlx.Connect("postgres", psqlconn)
if err != nil {
return nil, err
}
// Run DB migrations
migrations := &migrate.PackrMigrationSource{
Box: packr.New("l2db-migrations", "./migrations"),
}
nMigrations, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
if err != nil {
return nil, err
}
log.Debug("L2DB applied ", nMigrations, " migrations for ", dbname, " database")
func NewL2DB(db *sqlx.DB, safetyPeriod common.BatchNum, maxTxs uint32, TTL time.Duration) *L2DB {
return &L2DB{ return &L2DB{
db: db, db: db,
safetyPeriod: safetyPeriod, safetyPeriod: safetyPeriod,
ttl: TTL, ttl: TTL,
maxTxs: maxTxs, maxTxs: maxTxs,
}, nil
}
} }
// DB returns a pointer to the L2DB.db. This method should be used only for // DB returns a pointer to the L2DB.db. This method should be used only for
@ -86,27 +58,82 @@ func (l2db *L2DB) GetAccountCreationAuth(addr ethCommon.Address) (*common.Accoun
) )
} }
// AddTx inserts a tx into the L2DB
func (l2db *L2DB) AddTx(tx *common.PoolL2Tx) error {
return meddler.Insert(l2db.db, "tx_pool", tx)
// AddTxTest inserts a tx into the L2DB. This is useful for test purposes,
// but in production txs will only be inserted through the API (method TBD)
func (l2db *L2DB) AddTxTest(tx *common.PoolL2Tx) error {
type withouUSD struct {
TxID common.TxID `meddler:"tx_id"`
FromIdx common.Idx `meddler:"from_idx"`
ToIdx common.Idx `meddler:"to_idx"`
ToEthAddr ethCommon.Address `meddler:"to_eth_addr"`
ToBJJ *babyjub.PublicKey `meddler:"to_bjj"`
TokenID common.TokenID `meddler:"token_id"`
Amount *big.Int `meddler:"amount,bigint"`
AmountFloat float64 `meddler:"amount_f"`
Fee common.FeeSelector `meddler:"fee"`
Nonce common.Nonce `meddler:"nonce"`
State common.PoolL2TxState `meddler:"state"`
Signature *babyjub.Signature `meddler:"signature"`
Timestamp time.Time `meddler:"timestamp,utctime"`
BatchNum common.BatchNum `meddler:"batch_num,zeroisnull"`
RqFromIdx common.Idx `meddler:"rq_from_idx,zeroisnull"`
RqToIdx common.Idx `meddler:"rq_to_idx,zeroisnull"`
RqToEthAddr ethCommon.Address `meddler:"rq_to_eth_addr"`
RqToBJJ *babyjub.PublicKey `meddler:"rq_to_bjj"`
RqTokenID common.TokenID `meddler:"rq_token_id,zeroisnull"`
RqAmount *big.Int `meddler:"rq_amount,bigintnull"`
RqFee common.FeeSelector `meddler:"rq_fee,zeroisnull"`
RqNonce uint64 `meddler:"rq_nonce,zeroisnull"`
Type common.TxType `meddler:"tx_type"`
}
return meddler.Insert(l2db.db, "tx_pool", &withouUSD{
TxID: tx.TxID,
FromIdx: tx.FromIdx,
ToIdx: tx.ToIdx,
ToEthAddr: tx.ToEthAddr,
ToBJJ: tx.ToBJJ,
TokenID: tx.TokenID,
Amount: tx.Amount,
AmountFloat: tx.AmountFloat,
Fee: tx.Fee,
Nonce: tx.Nonce,
State: tx.State,
Signature: tx.Signature,
Timestamp: tx.Timestamp,
BatchNum: tx.BatchNum,
RqFromIdx: tx.RqFromIdx,
RqToIdx: tx.RqToIdx,
RqToEthAddr: tx.RqToEthAddr,
RqToBJJ: tx.RqToBJJ,
RqTokenID: tx.RqTokenID,
RqAmount: tx.RqAmount,
RqFee: tx.RqFee,
RqNonce: tx.RqNonce,
Type: tx.Type,
})
} }
// selectPoolTx select part of queries to get common.PoolL2Tx
const selectPoolTx = `SELECT tx_pool.*, token.usd * tx_pool.amount_f AS value_usd,
fee_percentage(tx_pool.fee::NUMERIC) * token.usd * tx_pool.amount_f AS fee_usd, token.usd_update,
token.symbol AS token_symbol FROM tx_pool INNER JOIN token ON tx_pool.token_id = token.token_id `
// GetTx return the specified Tx // GetTx return the specified Tx
func (l2db *L2DB) GetTx(txID common.TxID) (*common.PoolL2Tx, error) { func (l2db *L2DB) GetTx(txID common.TxID) (*common.PoolL2Tx, error) {
tx := new(common.PoolL2Tx) tx := new(common.PoolL2Tx)
return tx, meddler.QueryRow( return tx, meddler.QueryRow(
l2db.db, tx, l2db.db, tx,
"SELECT * FROM tx_pool WHERE tx_id = $1;",
selectPoolTx+"WHERE tx_id = $1;",
txID, txID,
) )
} }
// GetPendingTxs return all the pending txs of the L2DB
// GetPendingTxs return all the pending txs of the L2DB, that have a non NULL AbsoluteFee
func (l2db *L2DB) GetPendingTxs() ([]*common.PoolL2Tx, error) { func (l2db *L2DB) GetPendingTxs() ([]*common.PoolL2Tx, error) {
var txs []*common.PoolL2Tx var txs []*common.PoolL2Tx
err := meddler.QueryAll( err := meddler.QueryAll(
l2db.db, &txs, l2db.db, &txs,
"SELECT * FROM tx_pool WHERE state = $1",
selectPoolTx+"WHERE state = $1 AND token.usd IS NOT NULL",
common.PoolL2TxStatePending, common.PoolL2TxStatePending,
) )
return txs, err return txs, err
@ -202,40 +229,6 @@ func (l2db *L2DB) CheckNonces(updatedAccounts []common.Account, batchNum common.
return txn.Commit() return txn.Commit()
} }
// UpdateTxValue updates the absolute fee and value of txs given a token list that include their price in USD
func (l2db *L2DB) UpdateTxValue(tokens []common.Token) (err error) {
// WARNING: this is very slow and should be optimized
txn, err := l2db.db.Begin()
if err != nil {
return err
}
defer func() {
// Rollback the transaction if there was an error.
if err != nil {
err = txn.Rollback()
}
}()
now := time.Now()
for i := 0; i < len(tokens); i++ {
_, err = txn.Exec(
`UPDATE tx_pool
SET usd_update = $1, value_usd = amount_f * $2, fee_usd = $2 * amount_f * CASE
WHEN fee = 0 THEN 0
WHEN fee >= 1 AND fee <= 32 THEN POWER(10,-24+(fee::float/2))
WHEN fee >= 33 AND fee <= 223 THEN POWER(10,-8+(0.041666666666667*(fee::float-32)))
WHEN fee >= 224 AND fee <= 255 THEN POWER(10,fee-224) END
WHERE token_id = $3;`,
now,
tokens[i].USD,
tokens[i].TokenID,
)
if err != nil {
return err
}
}
return txn.Commit()
}
// Reorg updates the state of txs that were updated in a batch that has been discarted due to a blockchain reorg. // Reorg updates the state of txs that were updated in a batch that has been discarted due to a blockchain reorg.
// The state of the affected txs can change form Forged -> Pending or from Invalid -> Pending // The state of the affected txs can change form Forged -> Pending or from Invalid -> Pending
func (l2db *L2DB) Reorg(lastValidBatch common.BatchNum) error { func (l2db *L2DB) Reorg(lastValidBatch common.BatchNum) error {
@ -286,8 +279,3 @@ func (l2db *L2DB) Purge(currentBatchNum common.BatchNum) (err error) {
} }
return txn.Commit() return txn.Commit()
} }
// Close frees the resources used by the L2DB
func (l2db *L2DB) Close() error {
return l2db.db.Close()
}

+ 70
- 99
db/l2db/l2db_test.go

@ -1,79 +1,110 @@
package l2db package l2db
import ( import (
"fmt"
"math"
"os" "os"
"testing" "testing"
"time" "time"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
dbUtils "github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/hermez-node/log"
"github.com/hermeznetwork/hermez-node/test" "github.com/hermeznetwork/hermez-node/test"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var l2DB *L2DB var l2DB *L2DB
var tokens []common.Token
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
// init DB // init DB
var err error
pass := os.Getenv("POSTGRES_PASS") pass := os.Getenv("POSTGRES_PASS")
l2DB, err = NewL2DB(5432, "localhost", "hermez", pass, "l2", 10, 100, 24*time.Hour)
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
if err != nil {
panic(err)
}
l2DB = NewL2DB(db, 10, 100, 24*time.Hour)
tokens, err = prepareHistoryDB(db)
if err != nil { if err != nil {
log.Error("L2DB migration failed: " + err.Error())
panic(err) panic(err)
} else {
log.Debug("L2DB migration succed")
} }
// Run tests // Run tests
result := m.Run() result := m.Run()
// Close DB // Close DB
if err := l2DB.Close(); err != nil {
fmt.Println("Error closing the history DB:", err)
if err := db.Close(); err != nil {
log.Error("Error closing the history DB:", err)
} }
os.Exit(result) os.Exit(result)
} }
func TestAddTx(t *testing.T) {
func prepareHistoryDB(db *sqlx.DB) ([]common.Token, error) {
historyDB := historydb.NewHistoryDB(db)
const fromBlock int64 = 1
const toBlock int64 = 5
// Clean historyDB
if err := historyDB.Reorg(-1); err != nil {
panic(err)
}
// Store blocks to historyDB
blocks := test.GenBlocks(fromBlock, toBlock)
if err := historyDB.AddBlocks(blocks); err != nil {
panic(err)
}
// Store tokens to historyDB
const nTokens = 5
tokens := test.GenTokens(nTokens, blocks)
return tokens, historyDB.AddTokens(tokens)
}
func TestAddTxTest(t *testing.T) {
// Gen poolTxs
const nInserts = 20 const nInserts = 20
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
for _, tx := range txs { for _, tx := range txs {
err := l2DB.AddTx(tx)
err := l2DB.AddTxTest(tx)
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)
assert.Equal(t, tx.Timestamp.Unix(), fetchedTx.Timestamp.Unix())
tx.Timestamp = fetchedTx.Timestamp
assert.Equal(t, tx.AbsoluteFeeUpdate.Unix(), fetchedTx.AbsoluteFeeUpdate.Unix())
tx.Timestamp = fetchedTx.Timestamp
tx.AbsoluteFeeUpdate = fetchedTx.AbsoluteFeeUpdate
assert.Equal(t, tx, fetchedTx)
assertTx(t, tx, fetchedTx)
}
}
func assertTx(t *testing.T, expected, actual *common.PoolL2Tx) {
assert.Equal(t, expected.Timestamp.Unix(), actual.Timestamp.Unix())
expected.Timestamp = actual.Timestamp
if expected.AbsoluteFeeUpdate != nil {
assert.Equal(t, expected.AbsoluteFeeUpdate.Unix(), actual.AbsoluteFeeUpdate.Unix())
expected.AbsoluteFeeUpdate = actual.AbsoluteFeeUpdate
} else {
assert.Equal(t, expected.AbsoluteFeeUpdate, actual.AbsoluteFeeUpdate)
} }
test.AssertUSD(t, expected.AbsoluteFee, actual.AbsoluteFee)
assert.Equal(t, expected, actual)
} }
func BenchmarkAddTx(b *testing.B) {
func BenchmarkAddTxTest(b *testing.B) {
const nInserts = 20 const nInserts = 20
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
now := time.Now() now := time.Now()
for _, tx := range txs { for _, tx := range txs {
_ = l2DB.AddTx(tx)
_ = l2DB.AddTxTest(tx)
} }
elapsedTime := time.Since(now) elapsedTime := time.Since(now)
fmt.Println("Time to insert 2048 txs:", elapsedTime)
log.Info("Time to insert 2048 txs:", elapsedTime)
} }
func TestGetPending(t *testing.T) { func TestGetPending(t *testing.T) {
const nInserts = 20 const nInserts = 20
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
var pendingTxs []*common.PoolL2Tx var pendingTxs []*common.PoolL2Tx
for _, tx := range txs { for _, tx := range txs {
err := l2DB.AddTx(tx)
err := l2DB.AddTxTest(tx)
assert.NoError(t, err) assert.NoError(t, err)
if tx.State == common.PoolL2TxStatePending {
if tx.State == common.PoolL2TxStatePending && tx.AbsoluteFee != nil {
pendingTxs = append(pendingTxs, tx) pendingTxs = append(pendingTxs, tx)
} }
} }
@ -81,11 +112,7 @@ func TestGetPending(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, len(pendingTxs), len(fetchedTxs)) assert.Equal(t, len(pendingTxs), len(fetchedTxs))
for i, fetchedTx := range fetchedTxs { for i, fetchedTx := range fetchedTxs {
assert.Equal(t, pendingTxs[i].Timestamp.Unix(), fetchedTx.Timestamp.Unix())
pendingTxs[i].Timestamp = fetchedTx.Timestamp
assert.Equal(t, pendingTxs[i].AbsoluteFeeUpdate.Unix(), fetchedTx.AbsoluteFeeUpdate.Unix())
pendingTxs[i].AbsoluteFeeUpdate = fetchedTx.AbsoluteFeeUpdate
assert.Equal(t, pendingTxs[i], fetchedTx)
assertTx(t, pendingTxs[i], fetchedTx)
} }
} }
@ -94,12 +121,12 @@ func TestStartForging(t *testing.T) {
const nInserts = 60 const nInserts = 60
const fakeBatchNum common.BatchNum = 33 const fakeBatchNum common.BatchNum = 33
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
var startForgingTxIDs []common.TxID var startForgingTxIDs []common.TxID
randomizer := 0 randomizer := 0
// Add txs to DB // Add txs to DB
for _, tx := range txs { for _, tx := range txs {
err := l2DB.AddTx(tx)
err := l2DB.AddTxTest(tx)
assert.NoError(t, err) assert.NoError(t, err)
if tx.State == common.PoolL2TxStatePending && randomizer%2 == 0 { if tx.State == common.PoolL2TxStatePending && randomizer%2 == 0 {
randomizer++ randomizer++
@ -123,12 +150,12 @@ func TestDoneForging(t *testing.T) {
const nInserts = 60 const nInserts = 60
const fakeBatchNum common.BatchNum = 33 const fakeBatchNum common.BatchNum = 33
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
var doneForgingTxIDs []common.TxID var doneForgingTxIDs []common.TxID
randomizer := 0 randomizer := 0
// Add txs to DB // Add txs to DB
for _, tx := range txs { for _, tx := range txs {
err := l2DB.AddTx(tx)
err := l2DB.AddTxTest(tx)
assert.NoError(t, err) assert.NoError(t, err)
if tx.State == common.PoolL2TxStateForging && randomizer%2 == 0 { if tx.State == common.PoolL2TxStateForging && randomizer%2 == 0 {
randomizer++ randomizer++
@ -152,12 +179,12 @@ func TestInvalidate(t *testing.T) {
const nInserts = 60 const nInserts = 60
const fakeBatchNum common.BatchNum = 33 const fakeBatchNum common.BatchNum = 33
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
var invalidTxIDs []common.TxID var invalidTxIDs []common.TxID
randomizer := 0 randomizer := 0
// Add txs to DB // Add txs to DB
for _, tx := range txs { for _, tx := range txs {
err := l2DB.AddTx(tx)
err := l2DB.AddTxTest(tx)
assert.NoError(t, err) assert.NoError(t, err)
if tx.State != common.PoolL2TxStateInvalid && randomizer%2 == 0 { if tx.State != common.PoolL2TxStateInvalid && randomizer%2 == 0 {
randomizer++ randomizer++
@ -181,7 +208,7 @@ func TestCheckNonces(t *testing.T) {
const nInserts = 60 const nInserts = 60
const fakeBatchNum common.BatchNum = 33 const fakeBatchNum common.BatchNum = 33
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
var invalidTxIDs []common.TxID var invalidTxIDs []common.TxID
// Generate accounts // Generate accounts
const nAccoutns = 2 const nAccoutns = 2
@ -204,7 +231,7 @@ func TestCheckNonces(t *testing.T) {
txs[i].Nonce = currentNonce + 1 txs[i].Nonce = currentNonce + 1
} }
} }
err := l2DB.AddTx(txs[i])
err := l2DB.AddTxTest(txs[i])
assert.NoError(t, err) assert.NoError(t, err)
} }
// Start forging txs // Start forging txs
@ -219,69 +246,13 @@ func TestCheckNonces(t *testing.T) {
} }
} }
func TestUpdateTxValue(t *testing.T) {
// Generate txs
const nInserts = 255 // Force all possible fee selector values
test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
// Generate tokens
const nTokens = 2
tokens := []common.Token{}
for i := 0; i < nTokens; i++ {
tokens = append(tokens, common.Token{
TokenID: common.TokenID(i),
USD: float64(i) * 1.3,
})
}
// Add txs to DB
for i := 0; i < len(txs); i++ {
// Set Token
token := tokens[i%len(tokens)]
txs[i].TokenID = token.TokenID
// Insert to DB
err := l2DB.AddTx(txs[i])
assert.NoError(t, err)
// Set USD values (for comparing results when fetching from DB)
txs[i].USD = txs[i].AmountFloat * token.USD
if txs[i].Fee == 0 {
txs[i].AbsoluteFee = 0
} else if txs[i].Fee <= 32 {
txs[i].AbsoluteFee = txs[i].USD * math.Pow(10, -24+(float64(txs[i].Fee)/2))
} else if txs[i].Fee <= 223 {
txs[i].AbsoluteFee = txs[i].USD * math.Pow(10, -8+(0.041666666666667*(float64(txs[i].Fee)-32)))
} else {
txs[i].AbsoluteFee = txs[i].USD * math.Pow(10, float64(txs[i].Fee)-224)
}
}
// Update token value
err := l2DB.UpdateTxValue(tokens)
assert.NoError(t, err)
// Fetch txs and check that they've been updated correctly
for _, tx := range txs {
fetchedTx, err := l2DB.GetTx(tx.TxID)
assert.NoError(t, err)
if fetchedTx.USD > tx.USD {
assert.Less(t, 0.999, tx.USD/fetchedTx.USD)
} else if fetchedTx.USD < tx.USD {
assert.Less(t, 0.999, fetchedTx.USD/tx.USD)
}
if fetchedTx.AbsoluteFee > tx.AbsoluteFee {
assert.Less(t, 0.999, tx.AbsoluteFee/fetchedTx.AbsoluteFee)
} else if fetchedTx.AbsoluteFee < tx.AbsoluteFee {
assert.Less(t, 0.999, fetchedTx.AbsoluteFee/tx.AbsoluteFee)
}
// Time is set in the DB, so it cannot be compared exactly
assert.Greater(t, float64(15*time.Second), time.Since(fetchedTx.AbsoluteFeeUpdate).Seconds())
}
}
func TestReorg(t *testing.T) { func TestReorg(t *testing.T) {
// Generate txs // Generate txs
const nInserts = 20 const nInserts = 20
const lastValidBatch common.BatchNum = 20 const lastValidBatch common.BatchNum = 20
const reorgBatch common.BatchNum = lastValidBatch + 1 const reorgBatch common.BatchNum = lastValidBatch + 1
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(nInserts)
txs := test.GenPoolTxs(nInserts, tokens)
// Add txs to the DB // Add txs to the DB
reorgedTxIDs := []common.TxID{} reorgedTxIDs := []common.TxID{}
nonReorgedTxIDs := []common.TxID{} nonReorgedTxIDs := []common.TxID{}
@ -293,7 +264,7 @@ func TestReorg(t *testing.T) {
txs[i].BatchNum = lastValidBatch txs[i].BatchNum = lastValidBatch
nonReorgedTxIDs = append(nonReorgedTxIDs, txs[i].TxID) nonReorgedTxIDs = append(nonReorgedTxIDs, txs[i].TxID)
} }
err := l2DB.AddTx(txs[i])
err := l2DB.AddTxTest(txs[i])
assert.NoError(t, err) assert.NoError(t, err)
} }
err := l2DB.Reorg(lastValidBatch) err := l2DB.Reorg(lastValidBatch)
@ -316,7 +287,7 @@ func TestPurge(t *testing.T) {
// Generate txs // Generate txs
nInserts := l2DB.maxTxs + 20 nInserts := l2DB.maxTxs + 20
test.CleanL2DB(l2DB.DB()) test.CleanL2DB(l2DB.DB())
txs := test.GenPoolTxs(int(nInserts))
txs := test.GenPoolTxs(int(nInserts), tokens)
deletedIDs := []common.TxID{} deletedIDs := []common.TxID{}
keepedIDs := []common.TxID{} keepedIDs := []common.TxID{}
const toDeleteBatchNum common.BatchNum = 30 const toDeleteBatchNum common.BatchNum = 30
@ -335,14 +306,14 @@ func TestPurge(t *testing.T) {
} }
deletedIDs = append(deletedIDs, txs[i].TxID) deletedIDs = append(deletedIDs, txs[i].TxID)
} }
err := l2DB.AddTx(txs[i])
err := l2DB.AddTxTest(txs[i])
assert.NoError(t, err) assert.NoError(t, err)
} }
for i := int(l2DB.maxTxs); i < len(txs); i++ { for i := int(l2DB.maxTxs); i < len(txs); i++ {
// Delete after TTL // Delete after TTL
txs[i].Timestamp = time.Unix(time.Now().UTC().Unix()-int64(l2DB.ttl.Seconds()+float64(4*time.Second)), 0) txs[i].Timestamp = time.Unix(time.Now().UTC().Unix()-int64(l2DB.ttl.Seconds()+float64(4*time.Second)), 0)
deletedIDs = append(deletedIDs, txs[i].TxID) deletedIDs = append(deletedIDs, txs[i].TxID)
err := l2DB.AddTx(txs[i])
err := l2DB.AddTxTest(txs[i])
assert.NoError(t, err) assert.NoError(t, err)
} }
// Purge txs // Purge txs

+ 0
- 40
db/l2db/migrations/001_init.sql

@ -1,40 +0,0 @@
-- +migrate Up
CREATE TABLE tx_pool (
tx_id BYTEA PRIMARY KEY,
from_idx BIGINT NOT NULL,
to_idx BIGINT NOT NULL,
to_eth_addr BYTEA NOT NULL,
to_bjj BYTEA NOT NULL,
token_id INT NOT NULL,
amount BYTEA NOT NULL,
amount_f NUMERIC NOT NULL,
value_usd NUMERIC,
fee SMALLINT NOT NULL,
nonce BIGINT NOT NULL,
state CHAR(4) NOT NULL,
signature BYTEA NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
batch_num BIGINT,
rq_from_idx BIGINT,
rq_to_idx BIGINT,
rq_to_eth_addr BYTEA,
rq_to_bjj BYTEA,
rq_token_id INT,
rq_amount BYTEA,
rq_fee SMALLINT,
rq_nonce BIGINT,
fee_usd NUMERIC,
usd_update TIMESTAMP WITHOUT TIME ZONE,
tx_type VARCHAR(40) NOT NULL
);
CREATE TABLE account_creation_auth (
eth_addr BYTEA PRIMARY KEY,
bjj BYTEA NOT NULL,
signature BYTEA NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
-- +migrate Down
DROP TABLE account_creation_auth;
DROP TABLE tx_pool;

+ 515
- 0
db/migrations/0001.sql

@ -0,0 +1,515 @@
-- +migrate Up
-- History
CREATE TABLE block (
eth_block_num BIGINT PRIMARY KEY,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
hash BYTEA NOT NULL
);
CREATE TABLE coordinator (
forger_addr BYTEA NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
withdraw_addr BYTEA NOT NULL,
url VARCHAR(200) NOT NULL,
PRIMARY KEY (forger_addr, eth_block_num)
);
CREATE TABLE batch (
batch_num BIGINT PRIMARY KEY,
eth_block_num BIGINT REFERENCES block (eth_block_num) ON DELETE CASCADE,
forger_addr BYTEA NOT NULL, -- fake foreign key for coordinator
fees_collected BYTEA NOT NULL,
state_root BYTEA NOT NULL,
num_accounts BIGINT NOT NULL,
exit_root BYTEA NOT NULL,
forge_l1_txs_num BIGINT,
slot_num BIGINT NOT NULL
);
CREATE TABLE exit_tree (
batch_num BIGINT REFERENCES batch (batch_num) ON DELETE CASCADE,
account_idx BIGINT,
merkle_proof BYTEA NOT NULL,
balance BYTEA NOT NULL,
instant_withdrawn BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL,
delayed_withdraw_request BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL,
delayed_withdrawn BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL,
PRIMARY KEY (batch_num, account_idx)
);
CREATE TABLE bid (
slot_num BIGINT NOT NULL,
bid_value BYTEA NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
forger_addr BYTEA NOT NULL, -- fake foreign key for coordinator
PRIMARY KEY (slot_num, bid_value)
);
CREATE TABLE token (
token_id INT PRIMARY KEY,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
eth_addr BYTEA UNIQUE NOT NULL,
name VARCHAR(20) NOT NULL,
symbol VARCHAR(10) NOT NULL,
decimals INT NOT NULL,
usd NUMERIC,
usd_update TIMESTAMP
);
-- +migrate StatementBegin
CREATE FUNCTION set_token_usd_update()
RETURNS TRIGGER
AS
$BODY$
BEGIN
IF NEW."usd" IS NOT NULL AND NEW."usd_update" IS NULL THEN
NEW."usd_update" = timezone('utc', now());
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- +migrate StatementEnd
CREATE TRIGGER trigger_token_usd_update BEFORE UPDATE OR INSERT ON token
FOR EACH ROW EXECUTE PROCEDURE set_token_usd_update();
CREATE TABLE tx (
-- Generic TX
is_l1 BOOLEAN NOT NULL,
id BYTEA PRIMARY KEY,
type VARCHAR(40) NOT NULL,
position INT NOT NULL,
from_idx BIGINT NOT NULL,
to_idx BIGINT NOT NULL,
amount BYTEA NOT NULL,
amount_f NUMERIC NOT NULL,
token_id INT NOT NULL REFERENCES token (token_id),
amount_usd NUMERIC, -- Value of the amount in USD at the moment the tx was inserted in the DB
batch_num BIGINT REFERENCES batch (batch_num) ON DELETE SET NULL, -- Can be NULL in the case of L1 txs that are on the queue but not forged yet.
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
-- L1
to_forge_l1_txs_num BIGINT,
user_origin BOOLEAN,
from_eth_addr BYTEA,
from_bjj BYTEA,
load_amount BYTEA,
load_amount_f NUMERIC,
load_amount_usd NUMERIC,
-- L2
fee INT,
fee_usd NUMERIC,
nonce BIGINT
);
CREATE INDEX tx_order ON tx (batch_num, position);
-- +migrate StatementBegin
CREATE FUNCTION fee_percentage(NUMERIC)
RETURNS NUMERIC
AS
$BODY$
DECLARE perc NUMERIC;
BEGIN
SELECT CASE
WHEN $1 = 0 THEN 0
WHEN $1 = 1 THEN 3.162278e-24
WHEN $1 = 2 THEN 1.000000e-23
WHEN $1 = 3 THEN 3.162278e-23
WHEN $1 = 4 THEN 1.000000e-22
WHEN $1 = 5 THEN 3.162278e-22
WHEN $1 = 6 THEN 1.000000e-21
WHEN $1 = 7 THEN 3.162278e-21
WHEN $1 = 8 THEN 1.000000e-20
WHEN $1 = 9 THEN 3.162278e-20
WHEN $1 = 10 THEN 1.000000e-19
WHEN $1 = 11 THEN 3.162278e-19
WHEN $1 = 12 THEN 1.000000e-18
WHEN $1 = 13 THEN 3.162278e-18
WHEN $1 = 14 THEN 1.000000e-17
WHEN $1 = 15 THEN 3.162278e-17
WHEN $1 = 16 THEN 1.000000e-16
WHEN $1 = 17 THEN 3.162278e-16
WHEN $1 = 18 THEN 1.000000e-15
WHEN $1 = 19 THEN 3.162278e-15
WHEN $1 = 20 THEN 1.000000e-14
WHEN $1 = 21 THEN 3.162278e-14
WHEN $1 = 22 THEN 1.000000e-13
WHEN $1 = 23 THEN 3.162278e-13
WHEN $1 = 24 THEN 1.000000e-12
WHEN $1 = 25 THEN 3.162278e-12
WHEN $1 = 26 THEN 1.000000e-11
WHEN $1 = 27 THEN 3.162278e-11
WHEN $1 = 28 THEN 1.000000e-10
WHEN $1 = 29 THEN 3.162278e-10
WHEN $1 = 30 THEN 1.000000e-09
WHEN $1 = 31 THEN 3.162278e-09
WHEN $1 = 32 THEN 1.000000e-08
WHEN $1 = 33 THEN 1.100694e-08
WHEN $1 = 34 THEN 1.211528e-08
WHEN $1 = 35 THEN 1.333521e-08
WHEN $1 = 36 THEN 1.467799e-08
WHEN $1 = 37 THEN 1.615598e-08
WHEN $1 = 38 THEN 1.778279e-08
WHEN $1 = 39 THEN 1.957342e-08
WHEN $1 = 40 THEN 2.154435e-08
WHEN $1 = 41 THEN 2.371374e-08
WHEN $1 = 42 THEN 2.610157e-08
WHEN $1 = 43 THEN 2.872985e-08
WHEN $1 = 44 THEN 3.162278e-08
WHEN $1 = 45 THEN 3.480701e-08
WHEN $1 = 46 THEN 3.831187e-08
WHEN $1 = 47 THEN 4.216965e-08
WHEN $1 = 48 THEN 4.641589e-08
WHEN $1 = 49 THEN 5.108970e-08
WHEN $1 = 50 THEN 5.623413e-08
WHEN $1 = 51 THEN 6.189658e-08
WHEN $1 = 52 THEN 6.812921e-08
WHEN $1 = 53 THEN 7.498942e-08
WHEN $1 = 54 THEN 8.254042e-08
WHEN $1 = 55 THEN 9.085176e-08
WHEN $1 = 56 THEN 1.000000e-07
WHEN $1 = 57 THEN 1.100694e-07
WHEN $1 = 58 THEN 1.211528e-07
WHEN $1 = 59 THEN 1.333521e-07
WHEN $1 = 60 THEN 1.467799e-07
WHEN $1 = 61 THEN 1.615598e-07
WHEN $1 = 62 THEN 1.778279e-07
WHEN $1 = 63 THEN 1.957342e-07
WHEN $1 = 64 THEN 2.154435e-07
WHEN $1 = 65 THEN 2.371374e-07
WHEN $1 = 66 THEN 2.610157e-07
WHEN $1 = 67 THEN 2.872985e-07
WHEN $1 = 68 THEN 3.162278e-07
WHEN $1 = 69 THEN 3.480701e-07
WHEN $1 = 70 THEN 3.831187e-07
WHEN $1 = 71 THEN 4.216965e-07
WHEN $1 = 72 THEN 4.641589e-07
WHEN $1 = 73 THEN 5.108970e-07
WHEN $1 = 74 THEN 5.623413e-07
WHEN $1 = 75 THEN 6.189658e-07
WHEN $1 = 76 THEN 6.812921e-07
WHEN $1 = 77 THEN 7.498942e-07
WHEN $1 = 78 THEN 8.254042e-07
WHEN $1 = 79 THEN 9.085176e-07
WHEN $1 = 80 THEN 1.000000e-06
WHEN $1 = 81 THEN 1.100694e-06
WHEN $1 = 82 THEN 1.211528e-06
WHEN $1 = 83 THEN 1.333521e-06
WHEN $1 = 84 THEN 1.467799e-06
WHEN $1 = 85 THEN 1.615598e-06
WHEN $1 = 86 THEN 1.778279e-06
WHEN $1 = 87 THEN 1.957342e-06
WHEN $1 = 88 THEN 2.154435e-06
WHEN $1 = 89 THEN 2.371374e-06
WHEN $1 = 90 THEN 2.610157e-06
WHEN $1 = 91 THEN 2.872985e-06
WHEN $1 = 92 THEN 3.162278e-06
WHEN $1 = 93 THEN 3.480701e-06
WHEN $1 = 94 THEN 3.831187e-06
WHEN $1 = 95 THEN 4.216965e-06
WHEN $1 = 96 THEN 4.641589e-06
WHEN $1 = 97 THEN 5.108970e-06
WHEN $1 = 98 THEN 5.623413e-06
WHEN $1 = 99 THEN 6.189658e-06
WHEN $1 = 100 THEN 6.812921e-06
WHEN $1 = 101 THEN 7.498942e-06
WHEN $1 = 102 THEN 8.254042e-06
WHEN $1 = 103 THEN 9.085176e-06
WHEN $1 = 104 THEN 1.000000e-05
WHEN $1 = 105 THEN 1.100694e-05
WHEN $1 = 106 THEN 1.211528e-05
WHEN $1 = 107 THEN 1.333521e-05
WHEN $1 = 108 THEN 1.467799e-05
WHEN $1 = 109 THEN 1.615598e-05
WHEN $1 = 110 THEN 1.778279e-05
WHEN $1 = 111 THEN 1.957342e-05
WHEN $1 = 112 THEN 2.154435e-05
WHEN $1 = 113 THEN 2.371374e-05
WHEN $1 = 114 THEN 2.610157e-05
WHEN $1 = 115 THEN 2.872985e-05
WHEN $1 = 116 THEN 3.162278e-05
WHEN $1 = 117 THEN 3.480701e-05
WHEN $1 = 118 THEN 3.831187e-05
WHEN $1 = 119 THEN 4.216965e-05
WHEN $1 = 120 THEN 4.641589e-05
WHEN $1 = 121 THEN 5.108970e-05
WHEN $1 = 122 THEN 5.623413e-05
WHEN $1 = 123 THEN 6.189658e-05
WHEN $1 = 124 THEN 6.812921e-05
WHEN $1 = 125 THEN 7.498942e-05
WHEN $1 = 126 THEN 8.254042e-05
WHEN $1 = 127 THEN 9.085176e-05
WHEN $1 = 128 THEN 1.000000e-04
WHEN $1 = 129 THEN 1.100694e-04
WHEN $1 = 130 THEN 1.211528e-04
WHEN $1 = 131 THEN 1.333521e-04
WHEN $1 = 132 THEN 1.467799e-04
WHEN $1 = 133 THEN 1.615598e-04
WHEN $1 = 134 THEN 1.778279e-04
WHEN $1 = 135 THEN 1.957342e-04
WHEN $1 = 136 THEN 2.154435e-04
WHEN $1 = 137 THEN 2.371374e-04
WHEN $1 = 138 THEN 2.610157e-04
WHEN $1 = 139 THEN 2.872985e-04
WHEN $1 = 140 THEN 3.162278e-04
WHEN $1 = 141 THEN 3.480701e-04
WHEN $1 = 142 THEN 3.831187e-04
WHEN $1 = 143 THEN 4.216965e-04
WHEN $1 = 144 THEN 4.641589e-04
WHEN $1 = 145 THEN 5.108970e-04
WHEN $1 = 146 THEN 5.623413e-04
WHEN $1 = 147 THEN 6.189658e-04
WHEN $1 = 148 THEN 6.812921e-04
WHEN $1 = 149 THEN 7.498942e-04
WHEN $1 = 150 THEN 8.254042e-04
WHEN $1 = 151 THEN 9.085176e-04
WHEN $1 = 152 THEN 1.000000e-03
WHEN $1 = 153 THEN 1.100694e-03
WHEN $1 = 154 THEN 1.211528e-03
WHEN $1 = 155 THEN 1.333521e-03
WHEN $1 = 156 THEN 1.467799e-03
WHEN $1 = 157 THEN 1.615598e-03
WHEN $1 = 158 THEN 1.778279e-03
WHEN $1 = 159 THEN 1.957342e-03
WHEN $1 = 160 THEN 2.154435e-03
WHEN $1 = 161 THEN 2.371374e-03
WHEN $1 = 162 THEN 2.610157e-03
WHEN $1 = 163 THEN 2.872985e-03
WHEN $1 = 164 THEN 3.162278e-03
WHEN $1 = 165 THEN 3.480701e-03
WHEN $1 = 166 THEN 3.831187e-03
WHEN $1 = 167 THEN 4.216965e-03
WHEN $1 = 168 THEN 4.641589e-03
WHEN $1 = 169 THEN 5.108970e-03
WHEN $1 = 170 THEN 5.623413e-03
WHEN $1 = 171 THEN 6.189658e-03
WHEN $1 = 172 THEN 6.812921e-03
WHEN $1 = 173 THEN 7.498942e-03
WHEN $1 = 174 THEN 8.254042e-03
WHEN $1 = 175 THEN 9.085176e-03
WHEN $1 = 176 THEN 1.000000e-02
WHEN $1 = 177 THEN 1.100694e-02
WHEN $1 = 178 THEN 1.211528e-02
WHEN $1 = 179 THEN 1.333521e-02
WHEN $1 = 180 THEN 1.467799e-02
WHEN $1 = 181 THEN 1.615598e-02
WHEN $1 = 182 THEN 1.778279e-02
WHEN $1 = 183 THEN 1.957342e-02
WHEN $1 = 184 THEN 2.154435e-02
WHEN $1 = 185 THEN 2.371374e-02
WHEN $1 = 186 THEN 2.610157e-02
WHEN $1 = 187 THEN 2.872985e-02
WHEN $1 = 188 THEN 3.162278e-02
WHEN $1 = 189 THEN 3.480701e-02
WHEN $1 = 190 THEN 3.831187e-02
WHEN $1 = 191 THEN 4.216965e-02
WHEN $1 = 192 THEN 4.641589e-02
WHEN $1 = 193 THEN 5.108970e-02
WHEN $1 = 194 THEN 5.623413e-02
WHEN $1 = 195 THEN 6.189658e-02
WHEN $1 = 196 THEN 6.812921e-02
WHEN $1 = 197 THEN 7.498942e-02
WHEN $1 = 198 THEN 8.254042e-02
WHEN $1 = 199 THEN 9.085176e-02
WHEN $1 = 200 THEN 1.000000e-01
WHEN $1 = 201 THEN 1.100694e-01
WHEN $1 = 202 THEN 1.211528e-01
WHEN $1 = 203 THEN 1.333521e-01
WHEN $1 = 204 THEN 1.467799e-01
WHEN $1 = 205 THEN 1.615598e-01
WHEN $1 = 206 THEN 1.778279e-01
WHEN $1 = 207 THEN 1.957342e-01
WHEN $1 = 208 THEN 2.154435e-01
WHEN $1 = 209 THEN 2.371374e-01
WHEN $1 = 210 THEN 2.610157e-01
WHEN $1 = 211 THEN 2.872985e-01
WHEN $1 = 212 THEN 3.162278e-01
WHEN $1 = 213 THEN 3.480701e-01
WHEN $1 = 214 THEN 3.831187e-01
WHEN $1 = 215 THEN 4.216965e-01
WHEN $1 = 216 THEN 4.641589e-01
WHEN $1 = 217 THEN 5.108970e-01
WHEN $1 = 218 THEN 5.623413e-01
WHEN $1 = 219 THEN 6.189658e-01
WHEN $1 = 220 THEN 6.812921e-01
WHEN $1 = 221 THEN 7.498942e-01
WHEN $1 = 222 THEN 8.254042e-01
WHEN $1 = 223 THEN 9.085176e-01
WHEN $1 = 224 THEN 1.000000e+00
WHEN $1 = 225 THEN 1.000000e+01
WHEN $1 = 226 THEN 1.000000e+02
WHEN $1 = 227 THEN 1.000000e+03
WHEN $1 = 228 THEN 1.000000e+04
WHEN $1 = 229 THEN 1.000000e+05
WHEN $1 = 230 THEN 1.000000e+06
WHEN $1 = 231 THEN 1.000000e+07
WHEN $1 = 232 THEN 1.000000e+08
WHEN $1 = 233 THEN 1.000000e+09
WHEN $1 = 234 THEN 1.000000e+10
WHEN $1 = 235 THEN 1.000000e+11
WHEN $1 = 236 THEN 1.000000e+12
WHEN $1 = 237 THEN 1.000000e+13
WHEN $1 = 238 THEN 1.000000e+14
WHEN $1 = 239 THEN 1.000000e+15
WHEN $1 = 240 THEN 1.000000e+16
WHEN $1 = 241 THEN 1.000000e+17
WHEN $1 = 242 THEN 1.000000e+18
WHEN $1 = 243 THEN 1.000000e+19
WHEN $1 = 244 THEN 1.000000e+20
WHEN $1 = 245 THEN 1.000000e+21
WHEN $1 = 246 THEN 1.000000e+22
WHEN $1 = 247 THEN 1.000000e+23
WHEN $1 = 248 THEN 1.000000e+24
WHEN $1 = 249 THEN 1.000000e+25
WHEN $1 = 250 THEN 1.000000e+26
WHEN $1 = 251 THEN 1.000000e+27
WHEN $1 = 252 THEN 1.000000e+28
WHEN $1 = 253 THEN 1.000000e+29
WHEN $1 = 254 THEN 1.000000e+30
WHEN $1 = 255 THEN 1.000000e+31
END INTO perc;
RETURN perc;
END;
$BODY$
LANGUAGE plpgsql;
-- +migrate StatementEnd
-- +migrate StatementBegin
CREATE FUNCTION set_tx()
RETURNS TRIGGER
AS
$BODY$
DECLARE token_value NUMERIC;
BEGIN
-- Validate L1/L2 constrains
IF NEW.is_l1 AND (( -- L1 mandatory fields
NEW.user_origin IS NULL OR
NEW.from_eth_addr IS NULL OR
NEW.from_bjj IS NULL OR
NEW.load_amount IS NULL OR
NEW.load_amount_f IS NULL
) OR (NOT NEW.user_origin AND NEW.batch_num IS NULL)) THEN -- If is Coordinator L1, must include batch_num
RAISE EXCEPTION 'Invalid L1 tx.';
ELSIF NOT NEW.is_l1 THEN
IF NEW.fee IS NULL THEN
NEW.fee = (SELECT 0);
END IF;
IF NEW.batch_num IS NULL OR NEW.nonce IS NULL THEN
RAISE EXCEPTION 'Invalid L2 tx.';
END IF;
END IF;
-- If is L2, add token_id
IF NOT NEW.is_l1 THEN
NEW."token_id" = (SELECT token_id FROM account WHERE idx = NEW."from_idx");
END IF;
-- Set value_usd
token_value = (SELECT usd / POWER(10, decimals) FROM token WHERE token_id = NEW.token_id);
NEW."amount_usd" = (SELECT token_value * NEW.amount_f);
NEW."load_amount_usd" = (SELECT token_value * NEW.load_amount_f);
IF NOT NEW.is_l1 THEN
NEW."fee_usd" = (SELECT NEW."amount_usd" * fee_percentage(NEW.fee::NUMERIC));
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- +migrate StatementEnd
CREATE TRIGGER trigger_set_tx BEFORE INSERT ON tx
FOR EACH ROW EXECUTE PROCEDURE set_tx();
-- +migrate StatementBegin
CREATE FUNCTION forge_l1_user_txs()
RETURNS TRIGGER
AS
$BODY$
BEGIN
IF NEW.forge_l1_txs_num IS NOT NULL THEN
UPDATE tx
SET batch_num = NEW.batch_num
WHERE user_origin AND NEW.forge_l1_txs_num = to_forge_l1_txs_num;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- +migrate StatementEnd
CREATE TRIGGER trigger_forge_l1_txs AFTER INSERT ON batch
FOR EACH ROW EXECUTE PROCEDURE forge_l1_user_txs();
CREATE TABLE account (
idx BIGINT PRIMARY KEY,
token_id INT NOT NULL REFERENCES token (token_id) ON DELETE CASCADE,
batch_num BIGINT NOT NULL REFERENCES batch (batch_num) ON DELETE CASCADE,
bjj BYTEA NOT NULL,
eth_addr BYTEA NOT NULL
);
CREATE TABLE rollup_vars (
eth_block_num BIGINT PRIMARY KEY REFERENCES block (eth_block_num) ON DELETE CASCADE,
forge_l1_timeout BYTEA NOT NULL,
fee_l1_user_tx BYTEA NOT NULL,
fee_add_token BYTEA NOT NULL,
tokens_hez BYTEA NOT NULL,
governance BYTEA NOT NULL
);
CREATE TABLE consensus_vars (
eth_block_num BIGINT PRIMARY KEY REFERENCES block (eth_block_num) ON DELETE CASCADE,
slot_deadline INT NOT NULL,
close_auction_slots INT NOT NULL,
open_auction_slots INT NOT NULL,
min_bid_slots VARCHAR(200) NOT NULL,
outbidding INT NOT NULL,
donation_address BYTEA NOT NULL,
governance_address BYTEA NOT NULL,
allocation_ratio VARCHAR(200)
);
-- L2
CREATE TABLE tx_pool (
tx_id BYTEA PRIMARY KEY,
from_idx BIGINT NOT NULL,
to_idx BIGINT NOT NULL,
to_eth_addr BYTEA NOT NULL,
to_bjj BYTEA NOT NULL,
token_id INT NOT NULL REFERENCES token (token_id) ON DELETE CASCADE,
amount BYTEA NOT NULL,
amount_f NUMERIC NOT NULL,
fee SMALLINT NOT NULL,
nonce BIGINT NOT NULL,
state CHAR(4) NOT NULL,
signature BYTEA NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
batch_num BIGINT,
rq_from_idx BIGINT,
rq_to_idx BIGINT,
rq_to_eth_addr BYTEA,
rq_to_bjj BYTEA,
rq_token_id INT,
rq_amount BYTEA,
rq_fee SMALLINT,
rq_nonce BIGINT,
tx_type VARCHAR(40) NOT NULL
);
CREATE TABLE account_creation_auth (
eth_addr BYTEA PRIMARY KEY,
bjj BYTEA NOT NULL,
signature BYTEA NOT NULL,
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL
);
-- +migrate Down
DROP TABLE account_creation_auth;
DROP TABLE tx_pool;
DROP TABLE consensus_vars;
DROP TABLE rollup_vars;
DROP TABLE account;
DROP TABLE tx;
DROP TABLE token;
DROP TABLE bid;
DROP TABLE exit_tree;
DROP TABLE batch;
DROP TABLE coordinator;
DROP TABLE block;

+ 3
- 3
db/statedb/txprocessors_test.go

@ -27,7 +27,7 @@ func TestProcessTxs(t *testing.T) {
instructions, err := parser.Parse() instructions, err := parser.Parse()
assert.Nil(t, err) assert.Nil(t, err)
l1Txs, coordinatorL1Txs, poolL2Txs := test.GenerateTestTxs(t, instructions)
l1Txs, coordinatorL1Txs, poolL2Txs, _ := test.GenerateTestTxs(t, instructions)
assert.Equal(t, 29, len(l1Txs[0])) assert.Equal(t, 29, len(l1Txs[0]))
assert.Equal(t, 0, len(coordinatorL1Txs[0])) assert.Equal(t, 0, len(coordinatorL1Txs[0]))
assert.Equal(t, 21, len(poolL2Txs[0])) assert.Equal(t, 21, len(poolL2Txs[0]))
@ -57,7 +57,7 @@ func TestProcessTxsBatchByBatch(t *testing.T) {
instructions, err := parser.Parse() instructions, err := parser.Parse()
assert.Nil(t, err) assert.Nil(t, err)
l1Txs, coordinatorL1Txs, poolL2Txs := test.GenerateTestTxs(t, instructions)
l1Txs, coordinatorL1Txs, poolL2Txs, _ := test.GenerateTestTxs(t, instructions)
assert.Equal(t, 29, len(l1Txs[0])) assert.Equal(t, 29, len(l1Txs[0]))
assert.Equal(t, 0, len(coordinatorL1Txs[0])) assert.Equal(t, 0, len(coordinatorL1Txs[0]))
assert.Equal(t, 21, len(poolL2Txs[0])) assert.Equal(t, 21, len(poolL2Txs[0]))
@ -108,7 +108,7 @@ func TestZKInputsGeneration(t *testing.T) {
instructions, err := parser.Parse() instructions, err := parser.Parse()
assert.Nil(t, err) assert.Nil(t, err)
l1Txs, coordinatorL1Txs, poolL2Txs := test.GenerateTestTxs(t, instructions)
l1Txs, coordinatorL1Txs, poolL2Txs, _ := test.GenerateTestTxs(t, instructions)
assert.Equal(t, 29, len(l1Txs[0])) assert.Equal(t, 29, len(l1Txs[0]))
assert.Equal(t, 0, len(coordinatorL1Txs[0])) assert.Equal(t, 0, len(coordinatorL1Txs[0]))
assert.Equal(t, 21, len(poolL2Txs[0])) assert.Equal(t, 21, len(poolL2Txs[0]))

+ 36
- 2
db/utils.go

@ -7,11 +7,45 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/gobuffalo/packr/v2"
"github.com/hermeznetwork/hermez-node/log"
"github.com/jmoiron/sqlx"
migrate "github.com/rubenv/sql-migrate"
"github.com/russross/meddler" "github.com/russross/meddler"
) )
// InitMeddler registers tags to be used to read/write from SQL DBs using meddler
func InitMeddler() {
// InitSQLDB runs migrations and registers meddlers
func InitSQLDB(port int, host, user, password, name string) (*sqlx.DB, error) {
// Init meddler
initMeddler()
meddler.Default = meddler.PostgreSQL
// Stablish connection
psqlconn := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
host,
port,
user,
password,
name,
)
db, err := sqlx.Connect("postgres", psqlconn)
if err != nil {
return nil, err
}
// Run DB migrations
migrations := &migrate.PackrMigrationSource{
Box: packr.New("hermez-db-migrations", "./migrations"),
}
nMigrations, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
if err != nil {
return nil, err
}
log.Info("successfully runt ", nMigrations, " migrations")
return db, nil
}
// initMeddler registers tags to be used to read/write from SQL DBs using meddler
func initMeddler() {
meddler.Register("bigint", BigIntMeddler{}) meddler.Register("bigint", BigIntMeddler{})
meddler.Register("bigintnull", BigIntNullMeddler{}) meddler.Register("bigintnull", BigIntNullMeddler{})
} }

+ 13
- 13
node/node.go

@ -7,6 +7,7 @@ import (
"github.com/hermeznetwork/hermez-node/batchbuilder" "github.com/hermeznetwork/hermez-node/batchbuilder"
"github.com/hermeznetwork/hermez-node/config" "github.com/hermeznetwork/hermez-node/config"
"github.com/hermeznetwork/hermez-node/coordinator" "github.com/hermeznetwork/hermez-node/coordinator"
dbUtils "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/db/l2db"
"github.com/hermeznetwork/hermez-node/db/statedb" "github.com/hermeznetwork/hermez-node/db/statedb"
@ -14,6 +15,7 @@ import (
"github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/hermez-node/log"
"github.com/hermeznetwork/hermez-node/synchronizer" "github.com/hermeznetwork/hermez-node/synchronizer"
"github.com/hermeznetwork/hermez-node/txselector" "github.com/hermeznetwork/hermez-node/txselector"
"github.com/jmoiron/sqlx"
) )
// Mode sets the working mode of the node (synchronizer or coordinator) // Mode sets the working mode of the node (synchronizer or coordinator)
@ -49,23 +51,27 @@ type Node struct {
stoppedSync chan bool stoppedSync chan bool
// General // General
cfg *config.Node
mode Mode
cfg *config.Node
mode Mode
sqlConn *sqlx.DB
} }
// NewNode creates a Node // NewNode creates a Node
func NewNode(mode Mode, cfg *config.Node, coordCfg *config.Coordinator) (*Node, error) { func NewNode(mode Mode, cfg *config.Node, coordCfg *config.Coordinator) (*Node, error) {
historyDB, err := historydb.NewHistoryDB(
// Stablish DB connection
db, err := dbUtils.InitSQLDB(
cfg.PostgreSQL.Port, cfg.PostgreSQL.Port,
cfg.PostgreSQL.Host, cfg.PostgreSQL.Host,
cfg.PostgreSQL.User, cfg.PostgreSQL.User,
cfg.PostgreSQL.Password, cfg.PostgreSQL.Password,
cfg.HistoryDB.Name,
cfg.PostgreSQL.Name,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
historyDB := historydb.NewHistoryDB(db)
stateDB, err := statedb.NewStateDB(cfg.StateDB.Path, true, 32) stateDB, err := statedb.NewStateDB(cfg.StateDB.Path, true, 32)
if err != nil { if err != nil {
return nil, err return nil, err
@ -81,19 +87,12 @@ func NewNode(mode Mode, cfg *config.Node, coordCfg *config.Coordinator) (*Node,
var coord *coordinator.Coordinator var coord *coordinator.Coordinator
if mode == ModeCoordinator { if mode == ModeCoordinator {
l2DB, err := l2db.NewL2DB(
cfg.PostgreSQL.Port,
cfg.PostgreSQL.Host,
cfg.PostgreSQL.User,
cfg.PostgreSQL.Password,
coordCfg.L2DB.Name,
l2DB := l2db.NewL2DB(
db,
coordCfg.L2DB.SafetyPeriod, coordCfg.L2DB.SafetyPeriod,
coordCfg.L2DB.MaxTxs, coordCfg.L2DB.MaxTxs,
coordCfg.L2DB.TTL.Duration, coordCfg.L2DB.TTL.Duration,
) )
if err != nil {
return nil, err
}
// TODO: Get (maxL1UserTxs, maxL1OperatorTxs, maxTxs) from the smart contract // TODO: Get (maxL1UserTxs, maxL1OperatorTxs, maxTxs) from the smart contract
txSelector, err := txselector.NewTxSelector(coordCfg.TxSelector.Path, stateDB, l2DB, 10, 10, 10) txSelector, err := txselector.NewTxSelector(coordCfg.TxSelector.Path, stateDB, l2DB, 10, 10, 10)
if err != nil { if err != nil {
@ -129,6 +128,7 @@ func NewNode(mode Mode, cfg *config.Node, coordCfg *config.Coordinator) (*Node,
sync: sync, sync: sync,
cfg: cfg, cfg: cfg,
mode: mode, mode: mode,
sqlConn: db,
}, nil }, nil
} }

+ 3
- 2
synchronizer/synchronizer.go

@ -397,7 +397,9 @@ func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) {
l1CoordinatorTx.TxID = common.TxID(common.Hash([]byte("0x01" + strconv.FormatInt(int64(nextForgeL1TxsNum), 10) + strconv.FormatInt(int64(l1CoordinatorTx.Position), 10) + "00"))) l1CoordinatorTx.TxID = common.TxID(common.Hash([]byte("0x01" + strconv.FormatInt(int64(nextForgeL1TxsNum), 10) + strconv.FormatInt(int64(l1CoordinatorTx.Position), 10) + "00")))
l1CoordinatorTx.UserOrigin = false l1CoordinatorTx.UserOrigin = false
l1CoordinatorTx.EthBlockNum = blockNum l1CoordinatorTx.EthBlockNum = blockNum
l1CoordinatorTx.BatchNum = common.BatchNum(fbEvent.BatchNum)
bn := new(common.BatchNum)
*bn = common.BatchNum(fbEvent.BatchNum)
l1CoordinatorTx.BatchNum = bn
batchData.l1CoordinatorTxs = append(batchData.l1CoordinatorTxs, l1CoordinatorTx) batchData.l1CoordinatorTxs = append(batchData.l1CoordinatorTxs, l1CoordinatorTx)
@ -571,7 +573,6 @@ func getL1UserTx(l1UserTxEvents []eth.RollupEventL1UserTx, blockNum int64) []*co
eL1UserTx.L1Tx.Position = eL1UserTx.Position eL1UserTx.L1Tx.Position = eL1UserTx.Position
eL1UserTx.L1Tx.UserOrigin = true eL1UserTx.L1Tx.UserOrigin = true
eL1UserTx.L1Tx.EthBlockNum = blockNum eL1UserTx.L1Tx.EthBlockNum = blockNum
eL1UserTx.L1Tx.BatchNum = 0
l1Txs = append(l1Txs, &eL1UserTx.L1Tx) l1Txs = append(l1Txs, &eL1UserTx.L1Tx)
} }

+ 3
- 7
synchronizer/synchronizer_test.go

@ -1,12 +1,12 @@
package synchronizer package synchronizer
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
dbUtils "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/statedb" "github.com/hermeznetwork/hermez-node/db/statedb"
"github.com/hermeznetwork/hermez-node/eth" "github.com/hermeznetwork/hermez-node/eth"
@ -24,8 +24,9 @@ func Test(t *testing.T) {
// Init History DB // Init History DB
pass := os.Getenv("POSTGRES_PASS") pass := os.Getenv("POSTGRES_PASS")
historyDB, err := historydb.NewHistoryDB(5432, "localhost", "hermez", pass, "history")
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
require.Nil(t, err) require.Nil(t, err)
historyDB := historydb.NewHistoryDB(db)
err = historyDB.Reorg(0) err = historyDB.Reorg(0)
assert.Nil(t, err) assert.Nil(t, err)
@ -65,9 +66,4 @@ func Test(t *testing.T) {
err = s.Sync() err = s.Sync()
require.Nil(t, err) require.Nil(t, err)
*/ */
// Close History DB
if err := historyDB.Close(); err != nil {
fmt.Println("Error closing the history DB:", err)
}
} }

+ 23
- 0
test/dbUtils.go

@ -0,0 +1,23 @@
package test
import (
"testing"
"github.com/stretchr/testify/assert"
)
// AssertUSD asserts pointers to float64, and checks that they are equal
// with a tolerance of 0.01%. After that, the actual value is setted to the expected value
// in order to be able to perform further assertions using the standar assert functions.
func AssertUSD(t *testing.T, expected, actual *float64) {
if actual == nil {
assert.Equal(t, expected, actual)
return
}
if *expected < *actual {
assert.InEpsilon(t, *actual, *expected, 0.0001)
} else if *expected > *actual {
assert.InEpsilon(t, *expected, *actual, 0.0001)
}
*expected = *actual
}

+ 151
- 60
test/historydb.go

@ -3,6 +3,7 @@ package test
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
"strconv" "strconv"
"time" "time"
@ -43,8 +44,10 @@ func GenTokens(nTokens int, blocks []common.Block) []common.Token {
EthAddr: ethCommon.BigToAddress(big.NewInt(int64(i))), EthAddr: ethCommon.BigToAddress(big.NewInt(int64(i))),
} }
if i%2 == 0 { if i%2 == 0 {
token.USD = 3
token.USDUpdate = time.Now()
usd := 3.0
token.USD = &usd
now := time.Now()
token.USDUpdate = &now
} }
tokens = append(tokens, token) tokens = append(tokens, token)
} }
@ -122,70 +125,116 @@ func GenL1Txs(
} }
userTxs := []common.L1Tx{} userTxs := []common.L1Tx{}
othersTxs := []common.L1Tx{} othersTxs := []common.L1Tx{}
_, nextTxsNum := GetNextToForgeNumAndBatch(batches)
for i := 0; i < totalTxs; i++ { for i := 0; i < totalTxs; i++ {
var tx common.L1Tx
token := tokens[i%len(tokens)]
var usd *float64
var lUSD *float64
amount := big.NewInt(int64(i + 1))
if token.USD != nil {
//nolint:gomnd
noDecimalsUSD := *token.USD / math.Pow(10, float64(token.Decimals))
f := new(big.Float).SetInt(amount)
af, _ := f.Float64()
usd = new(float64)
*usd = noDecimalsUSD * af
lUSD = new(float64)
*lUSD = noDecimalsUSD * af
}
tx := common.L1Tx{
TxID: common.TxID(common.Hash([]byte("L1_" + strconv.Itoa(fromIdx+i)))),
Position: i,
UserOrigin: i%2 == 0,
TokenID: token.TokenID,
Amount: amount,
USD: usd,
LoadAmount: amount,
LoadAmountUSD: lUSD,
EthBlockNum: blocks[i%len(blocks)].EthBlockNum,
Type: randomTxType(i),
}
if batches[i%len(batches)].ForgeL1TxsNum != 0 { if batches[i%len(batches)].ForgeL1TxsNum != 0 {
tx = common.L1Tx{
TxID: common.TxID(common.Hash([]byte("L1_" + strconv.Itoa(fromIdx+i)))),
ToForgeL1TxsNum: batches[i%len(batches)].ForgeL1TxsNum,
Position: i,
UserOrigin: i%2 == 0,
TokenID: tokens[i%len(tokens)].TokenID,
Amount: big.NewInt(int64(i + 1)),
LoadAmount: big.NewInt(int64(i + 1)),
EthBlockNum: blocks[i%len(blocks)].EthBlockNum,
Type: randomTxType(i),
}
if i%4 == 0 {
tx.BatchNum = batches[i%len(batches)].BatchNum
}
// Add already forged txs
tx.BatchNum = &batches[i%len(batches)].BatchNum
setFromToAndAppend(tx, i, nUserTxs, userAddr, accounts, &userTxs, &othersTxs)
} else { } else {
continue
// Add unforged txs
tx.ToForgeL1TxsNum = nextTxsNum
tx.UserOrigin = true
setFromToAndAppend(tx, i, nUserTxs, userAddr, accounts, &userTxs, &othersTxs)
} }
if i < nUserTxs {
var from, to *common.Account
var err error
if i%2 == 0 {
from, err = randomAccount(i, true, userAddr, accounts)
if err != nil {
panic(err)
}
to, err = randomAccount(i, false, userAddr, accounts)
if err != nil {
panic(err)
}
} else {
from, err = randomAccount(i, false, userAddr, accounts)
if err != nil {
panic(err)
}
to, err = randomAccount(i, true, userAddr, accounts)
if err != nil {
panic(err)
}
}
return userTxs, othersTxs
}
// GetNextToForgeNumAndBatch returns the next BatchNum and ForgeL1TxsNum to be added
func GetNextToForgeNumAndBatch(batches []common.Batch) (common.BatchNum, int64) {
batchNum := batches[len(batches)-1].BatchNum + 1
var toForgeL1TxsNum int64
found := false
for i := len(batches) - 1; i >= 0; i-- {
if batches[i].ForgeL1TxsNum != 0 {
toForgeL1TxsNum = batches[i].ForgeL1TxsNum + 1
found = true
break
}
}
if !found {
panic("toForgeL1TxsNum not found")
}
return batchNum, toForgeL1TxsNum
}
func setFromToAndAppend(
tx common.L1Tx,
i, nUserTxs int,
userAddr *ethCommon.Address,
accounts []common.Account,
userTxs *[]common.L1Tx,
othersTxs *[]common.L1Tx,
) {
if i < nUserTxs {
var from, to *common.Account
var err error
if i%2 == 0 {
from, err = randomAccount(i, true, userAddr, accounts)
if err != nil {
panic(err)
}
to, err = randomAccount(i, false, userAddr, accounts)
if err != nil {
panic(err)
} }
tx.FromIdx = from.Idx
tx.FromEthAddr = from.EthAddr
tx.FromBJJ = from.PublicKey
tx.ToIdx = to.Idx
userTxs = append(userTxs, tx)
} else { } else {
from, err := randomAccount(i, false, userAddr, accounts)
from, err = randomAccount(i, false, userAddr, accounts)
if err != nil { if err != nil {
panic(err) panic(err)
} }
to, err := randomAccount(i, false, userAddr, accounts)
to, err = randomAccount(i, true, userAddr, accounts)
if err != nil { if err != nil {
panic(err) panic(err)
} }
tx.FromIdx = from.Idx
tx.FromEthAddr = from.EthAddr
tx.FromBJJ = from.PublicKey
tx.ToIdx = to.Idx
othersTxs = append(othersTxs, tx)
} }
tx.FromIdx = from.Idx
tx.FromEthAddr = from.EthAddr
tx.FromBJJ = from.PublicKey
tx.ToIdx = to.Idx
*userTxs = append(*userTxs, tx)
} else {
from, err := randomAccount(i, false, userAddr, accounts)
if err != nil {
panic(err)
}
to, err := randomAccount(i, false, userAddr, accounts)
if err != nil {
panic(err)
}
tx.FromIdx = from.Idx
tx.FromEthAddr = from.EthAddr
tx.FromBJJ = from.PublicKey
tx.ToIdx = to.Idx
*othersTxs = append(*othersTxs, tx)
} }
return userTxs, othersTxs
} }
// GenL2Txs generates L2 txs. WARNING: This is meant for DB/API testing, and may not be fully consistent with the protocol. // GenL2Txs generates L2 txs. WARNING: This is meant for DB/API testing, and may not be fully consistent with the protocol.
@ -204,14 +253,14 @@ func GenL2Txs(
userTxs := []common.L2Tx{} userTxs := []common.L2Tx{}
othersTxs := []common.L2Tx{} othersTxs := []common.L2Tx{}
for i := 0; i < totalTxs; i++ { for i := 0; i < totalTxs; i++ {
amount := big.NewInt(int64(i + 1))
fee := common.FeeSelector(i % 256) //nolint:gomnd
tx := common.L2Tx{ tx := common.L2Tx{
TxID: common.TxID(common.Hash([]byte("L2_" + strconv.Itoa(fromIdx+i)))),
BatchNum: batches[i%len(batches)].BatchNum,
Position: i,
//nolint:gomnd
Amount: big.NewInt(int64(i + 1)),
//nolint:gomnd
Fee: common.FeeSelector(i % 256),
TxID: common.TxID(common.Hash([]byte("L2_" + strconv.Itoa(fromIdx+i)))),
BatchNum: batches[i%len(batches)].BatchNum,
Position: i,
Amount: amount,
Fee: fee,
Nonce: common.Nonce(i + 1), Nonce: common.Nonce(i + 1),
EthBlockNum: blocks[i%len(blocks)].EthBlockNum, EthBlockNum: blocks[i%len(blocks)].EthBlockNum,
Type: randomTxType(i), Type: randomTxType(i),
@ -240,7 +289,6 @@ func GenL2Txs(
} }
tx.FromIdx = from.Idx tx.FromIdx = from.Idx
tx.ToIdx = to.Idx tx.ToIdx = to.Idx
userTxs = append(userTxs, tx)
} else { } else {
from, err := randomAccount(i, false, userAddr, accounts) from, err := randomAccount(i, false, userAddr, accounts)
if err != nil { if err != nil {
@ -252,12 +300,55 @@ func GenL2Txs(
} }
tx.FromIdx = from.Idx tx.FromIdx = from.Idx
tx.ToIdx = to.Idx tx.ToIdx = to.Idx
}
var usd *float64
var fUSD *float64
token := GetToken(tx.FromIdx, accounts, tokens)
if token.USD != nil {
//nolint:gomnd
noDecimalsUSD := *token.USD / math.Pow(10, float64(token.Decimals))
f := new(big.Float).SetInt(amount)
af, _ := f.Float64()
usd = new(float64)
fUSD = new(float64)
*usd = noDecimalsUSD * af
*fUSD = *usd * fee.Percentage()
}
tx.USD = usd
tx.FeeUSD = fUSD
if i < nUserTxs {
userTxs = append(userTxs, tx)
} else {
othersTxs = append(othersTxs, tx) othersTxs = append(othersTxs, tx)
} }
} }
return userTxs, othersTxs return userTxs, othersTxs
} }
// GetToken returns the Token associated to an Idx given a list of tokens and accounts.
// It panics when not found, intended for testing only.
func GetToken(idx common.Idx, accs []common.Account, tokens []common.Token) common.Token {
var id common.TokenID
found := false
for _, acc := range accs {
if acc.Idx == idx {
found = true
id = acc.TokenID
break
}
}
if !found {
panic("tokenID not found")
}
for i := 0; i < len(tokens); i++ {
if tokens[i].TokenID == id {
return tokens[i]
}
}
panic("token not found")
}
// GenCoordinators generates coordinators. WARNING: This is meant for DB/API testing, and may not be fully consistent with the protocol. // GenCoordinators generates coordinators. WARNING: This is meant for DB/API testing, and may not be fully consistent with the protocol.
func GenCoordinators(nCoords int, blocks []common.Block) []common.Coordinator { func GenCoordinators(nCoords int, blocks []common.Block) []common.Coordinator {
coords := []common.Coordinator{} coords := []common.Coordinator{}

+ 27
- 17
test/l2db.go

@ -24,7 +24,7 @@ func CleanL2DB(db *sqlx.DB) {
// GenPoolTxs generates L2 pool txs. // GenPoolTxs generates L2 pool txs.
// WARNING: This tx doesn't follow the protocol (signature, txID, ...) // WARNING: This tx doesn't follow the protocol (signature, txID, ...)
// it's just to test getting/setting from/to the DB. // it's just to test getting/setting from/to the DB.
func GenPoolTxs(n int) []*common.PoolL2Tx {
func GenPoolTxs(n int, tokens []common.Token) []*common.PoolL2Tx {
txs := make([]*common.PoolL2Tx, 0, n) txs := make([]*common.PoolL2Tx, 0, n)
privK := babyjub.NewRandPrivKey() privK := babyjub.NewRandPrivKey()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -44,21 +44,33 @@ func GenPoolTxs(n int) []*common.PoolL2Tx {
} }
f := new(big.Float).SetInt(big.NewInt(int64(i))) f := new(big.Float).SetInt(big.NewInt(int64(i)))
amountF, _ := f.Float64() amountF, _ := f.Float64()
var usd, absFee *float64
fee := common.FeeSelector(i % 255) //nolint:gomnd
token := tokens[i%len(tokens)]
if token.USD != nil {
usd = new(float64)
absFee = new(float64)
*usd = *token.USD * amountF
*absFee = fee.Percentage() * *usd
}
tx := &common.PoolL2Tx{ tx := &common.PoolL2Tx{
TxID: common.TxID(common.Hash([]byte(strconv.Itoa(i)))),
FromIdx: common.Idx(i),
ToIdx: common.Idx(i + 1),
ToEthAddr: ethCommon.BigToAddress(big.NewInt(int64(i))),
ToBJJ: privK.Public(),
TokenID: common.TokenID(i),
Amount: big.NewInt(int64(i)),
AmountFloat: amountF,
//nolint:gomnd
Fee: common.FeeSelector(i % 255),
Nonce: common.Nonce(i),
State: state,
Signature: privK.SignPoseidon(big.NewInt(int64(i))),
Timestamp: time.Now().UTC(),
TxID: common.TxID(common.Hash([]byte(strconv.Itoa(i)))),
FromIdx: common.Idx(i),
ToIdx: common.Idx(i + 1),
ToEthAddr: ethCommon.BigToAddress(big.NewInt(int64(i))),
ToBJJ: privK.Public(),
TokenID: token.TokenID,
Amount: big.NewInt(int64(i)),
AmountFloat: amountF,
USD: usd,
Fee: fee,
Nonce: common.Nonce(i),
State: state,
Signature: privK.SignPoseidon(big.NewInt(int64(i))),
Timestamp: time.Now().UTC(),
TokenSymbol: token.Symbol,
AbsoluteFee: absFee,
AbsoluteFeeUpdate: token.USDUpdate,
} }
if i%2 == 0 { // Optional parameters: rq if i%2 == 0 { // Optional parameters: rq
tx.RqFromIdx = common.Idx(i) tx.RqFromIdx = common.Idx(i)
@ -72,8 +84,6 @@ func GenPoolTxs(n int) []*common.PoolL2Tx {
} }
if i%3 == 0 { // Optional parameters: things that get updated "a posteriori" if i%3 == 0 { // Optional parameters: things that get updated "a posteriori"
tx.BatchNum = 489 tx.BatchNum = 489
tx.AbsoluteFee = 39.12345
tx.AbsoluteFeeUpdate = time.Now().UTC()
} }
txs = append(txs, tx) txs = append(txs, tx)
} }

+ 23
- 4
test/txs.go

@ -52,7 +52,7 @@ func GenerateKeys(t *testing.T, accNames []string) map[string]*Account {
// GenerateTestTxs generates L1Tx & PoolL2Tx in a deterministic way for the // GenerateTestTxs generates L1Tx & PoolL2Tx in a deterministic way for the
// given Instructions. // given Instructions.
func GenerateTestTxs(t *testing.T, instructions Instructions) ([][]*common.L1Tx, [][]*common.L1Tx, [][]*common.PoolL2Tx) {
func GenerateTestTxs(t *testing.T, instructions Instructions) ([][]*common.L1Tx, [][]*common.L1Tx, [][]*common.PoolL2Tx, []common.Token) {
accounts := GenerateKeys(t, instructions.Accounts) accounts := GenerateKeys(t, instructions.Accounts)
l1CreatedAccounts := make(map[string]*Account) l1CreatedAccounts := make(map[string]*Account)
@ -148,13 +148,32 @@ func GenerateTestTxs(t *testing.T, instructions Instructions) ([][]*common.L1Tx,
l1Txs = append(l1Txs, batchL1Txs) l1Txs = append(l1Txs, batchL1Txs)
coordinatorL1Txs = append(coordinatorL1Txs, batchCoordinatorL1Txs) coordinatorL1Txs = append(coordinatorL1Txs, batchCoordinatorL1Txs)
poolL2Txs = append(poolL2Txs, batchPoolL2Txs) poolL2Txs = append(poolL2Txs, batchPoolL2Txs)
return l1Txs, coordinatorL1Txs, poolL2Txs
tokens := []common.Token{}
for i := 0; i < len(poolL2Txs); i++ {
for j := 0; j < len(poolL2Txs[i]); j++ {
id := poolL2Txs[i][j].TokenID
found := false
for k := 0; k < len(tokens); k++ {
if tokens[k].TokenID == id {
found = true
break
}
}
if !found {
tokens = append(tokens, common.Token{
TokenID: id,
EthBlockNum: 1,
EthAddr: ethCommon.BigToAddress(big.NewInt(int64(i*10000 + j))),
})
}
}
}
return l1Txs, coordinatorL1Txs, poolL2Txs, tokens
} }
// GenerateTestTxsFromSet reurns the L1 & L2 transactions for a given Set of // GenerateTestTxsFromSet reurns the L1 & L2 transactions for a given Set of
// Instructions code // Instructions code
func GenerateTestTxsFromSet(t *testing.T, set string) ([][]*common.L1Tx, [][]*common.L1Tx, [][]*common.PoolL2Tx) {
func GenerateTestTxsFromSet(t *testing.T, set string) ([][]*common.L1Tx, [][]*common.L1Tx, [][]*common.PoolL2Tx, []common.Token) {
parser := NewParser(strings.NewReader(set)) parser := NewParser(strings.NewReader(set))
instructions, err := parser.Parse() instructions, err := parser.Parse()
require.Nil(t, err) require.Nil(t, err)

+ 1
- 1
test/txs_test.go

@ -29,7 +29,7 @@ func TestGenerateTestL2Txs(t *testing.T) {
instructions, err := parser.Parse() instructions, err := parser.Parse()
assert.Nil(t, err) assert.Nil(t, err)
l1txs, coordinatorL1txs, l2txs := GenerateTestTxs(t, instructions)
l1txs, coordinatorL1txs, l2txs, _ := GenerateTestTxs(t, instructions)
assert.Equal(t, 2, len(l1txs)) assert.Equal(t, 2, len(l1txs))
assert.Equal(t, 3, len(l1txs[0])) assert.Equal(t, 3, len(l1txs[0]))
assert.Equal(t, 1, len(coordinatorL1txs[0])) assert.Equal(t, 1, len(coordinatorL1txs[0]))

+ 1
- 1
txselector/txselector.go

@ -25,7 +25,7 @@ func (t txs) Swap(i, j int) {
t[i], t[j] = t[j], t[i] t[i], t[j] = t[j], t[i]
} }
func (t txs) Less(i, j int) bool { func (t txs) Less(i, j int) bool {
return t[i].AbsoluteFee > t[j].AbsoluteFee
return *t[i].AbsoluteFee > *t[j].AbsoluteFee
} }
// TxSelector implements all the functionalities to select the txs for the next batch // TxSelector implements all the functionalities to select the txs for the next batch

+ 18
- 3
txselector/txselector_test.go

@ -7,17 +7,21 @@ import (
"time" "time"
"github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/common"
dbUtils "github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/hermeznetwork/hermez-node/db/l2db" "github.com/hermeznetwork/hermez-node/db/l2db"
"github.com/hermeznetwork/hermez-node/db/statedb" "github.com/hermeznetwork/hermez-node/db/statedb"
"github.com/hermeznetwork/hermez-node/test" "github.com/hermeznetwork/hermez-node/test"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func initTest(t *testing.T, testSet string) *TxSelector { func initTest(t *testing.T, testSet string) *TxSelector {
pass := os.Getenv("POSTGRES_PASS") pass := os.Getenv("POSTGRES_PASS")
l2DB, err := l2db.NewL2DB(5432, "localhost", "hermez", pass, "l2", 10, 512, 24*time.Hour)
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
require.Nil(t, err) require.Nil(t, err)
l2DB := l2db.NewL2DB(db, 10, 100, 24*time.Hour)
dir, err := ioutil.TempDir("", "tmpdb") dir, err := ioutil.TempDir("", "tmpdb")
require.Nil(t, err) require.Nil(t, err)
@ -33,18 +37,29 @@ func initTest(t *testing.T, testSet string) *TxSelector {
} }
func addL2Txs(t *testing.T, txsel *TxSelector, poolL2Txs []*common.PoolL2Tx) { func addL2Txs(t *testing.T, txsel *TxSelector, poolL2Txs []*common.PoolL2Tx) {
for i := 0; i < len(poolL2Txs); i++ { for i := 0; i < len(poolL2Txs); i++ {
err := txsel.l2db.AddTx(poolL2Txs[i])
err := txsel.l2db.AddTxTest(poolL2Txs[i])
require.Nil(t, err) require.Nil(t, err)
} }
} }
func addTokens(t *testing.T, tokens []common.Token, db *sqlx.DB) {
hdb := historydb.NewHistoryDB(db)
assert.NoError(t, hdb.Reorg(-1))
assert.NoError(t, hdb.AddBlock(&common.Block{
EthBlockNum: 1,
}))
assert.NoError(t, hdb.AddTokens(tokens))
}
func TestGetL2TxSelection(t *testing.T) { func TestGetL2TxSelection(t *testing.T) {
txsel := initTest(t, test.SetTest0) txsel := initTest(t, test.SetTest0)
test.CleanL2DB(txsel.l2db.DB()) test.CleanL2DB(txsel.l2db.DB())
// generate test transactions // generate test transactions
l1Txs, _, poolL2Txs := test.GenerateTestTxsFromSet(t, test.SetTest0)
l1Txs, _, poolL2Txs, tokens := test.GenerateTestTxsFromSet(t, test.SetTest0)
// add tokens to HistoryDB to avoid breaking FK constrains
addTokens(t, tokens, txsel.l2db.DB())
// add the first batch of transactions to the TxSelector // add the first batch of transactions to the TxSelector
addL2Txs(t, txsel, poolL2Txs[0]) addL2Txs(t, txsel, poolL2Txs[0])

Loading…
Cancel
Save