package l2db
|
|
|
|
import (
|
|
"math/big"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
ethCommon "github.com/ethereum/go-ethereum/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/test"
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
var l2DB *L2DB
|
|
var tokens []common.Token
|
|
var tokensUSD []historydb.TokenWithUSD
|
|
|
|
func TestMain(m *testing.M) {
|
|
// init DB
|
|
pass := os.Getenv("POSTGRES_PASS")
|
|
db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
l2DB = NewL2DB(db, 10, 100, 24*time.Hour)
|
|
tokens, tokensUSD = prepareHistoryDB(db)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// Run tests
|
|
result := m.Run()
|
|
// Close DB
|
|
if err := db.Close(); err != nil {
|
|
log.Error("Error closing the history DB:", err)
|
|
}
|
|
os.Exit(result)
|
|
}
|
|
|
|
func prepareHistoryDB(db *sqlx.DB) ([]common.Token, []historydb.TokenWithUSD) {
|
|
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)
|
|
if err := historyDB.AddTokens(tokens); err != nil {
|
|
panic(err)
|
|
}
|
|
readTokens := []historydb.TokenWithUSD{}
|
|
for i, token := range tokens {
|
|
readToken := historydb.TokenWithUSD{
|
|
TokenID: token.TokenID,
|
|
EthBlockNum: token.EthBlockNum,
|
|
EthAddr: token.EthAddr,
|
|
Name: token.Name,
|
|
Symbol: token.Symbol,
|
|
Decimals: token.Decimals,
|
|
}
|
|
if i%2 != 0 {
|
|
value := float64(i) * 5.4321
|
|
if err := historyDB.UpdateTokenValue(token.Symbol, value); err != nil {
|
|
panic(err)
|
|
}
|
|
now := time.Now().UTC()
|
|
readToken.USDUpdate = &now
|
|
readToken.USD = &value
|
|
}
|
|
readTokens = append(readTokens, readToken)
|
|
}
|
|
return tokens, readTokens
|
|
}
|
|
|
|
func TestAddTxTest(t *testing.T) {
|
|
// Gen poolTxs
|
|
const nInserts = 20
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
for _, tx := range txs {
|
|
err := l2DB.AddTxTest(tx)
|
|
assert.NoError(t, err)
|
|
fetchedTx, err := l2DB.GetTx(tx.TxID)
|
|
assert.NoError(t, err)
|
|
assertReadTx(t, commonToRead(tx, tokens), fetchedTx)
|
|
}
|
|
}
|
|
|
|
func commonToRead(commonTx *common.PoolL2Tx, tokens []common.Token) *PoolL2TxRead {
|
|
readTx := &PoolL2TxRead{
|
|
TxID: commonTx.TxID,
|
|
FromIdx: commonTx.FromIdx,
|
|
ToBJJ: commonTx.ToBJJ,
|
|
Amount: commonTx.Amount,
|
|
Fee: commonTx.Fee,
|
|
Nonce: commonTx.Nonce,
|
|
State: commonTx.State,
|
|
Signature: commonTx.Signature,
|
|
RqToBJJ: commonTx.RqToBJJ,
|
|
RqAmount: commonTx.RqAmount,
|
|
Type: commonTx.Type,
|
|
Timestamp: commonTx.Timestamp,
|
|
TokenID: commonTx.TokenID,
|
|
}
|
|
// token related fields
|
|
// find token
|
|
token := historydb.TokenWithUSD{}
|
|
for _, tkn := range tokensUSD {
|
|
if tkn.TokenID == readTx.TokenID {
|
|
token = tkn
|
|
break
|
|
}
|
|
}
|
|
// set token related fields
|
|
readTx.TokenEthBlockNum = token.EthBlockNum
|
|
readTx.TokenEthAddr = token.EthAddr
|
|
readTx.TokenName = token.Name
|
|
readTx.TokenSymbol = token.Symbol
|
|
readTx.TokenDecimals = token.Decimals
|
|
readTx.TokenUSD = token.USD
|
|
readTx.TokenUSDUpdate = token.USDUpdate
|
|
// nullable fields
|
|
if commonTx.ToIdx != 0 {
|
|
readTx.ToIdx = &commonTx.ToIdx
|
|
}
|
|
nilAddr := ethCommon.BigToAddress(big.NewInt(0))
|
|
if commonTx.ToEthAddr != nilAddr {
|
|
readTx.ToEthAddr = &commonTx.ToEthAddr
|
|
}
|
|
if commonTx.RqFromIdx != 0 {
|
|
readTx.RqFromIdx = &commonTx.RqFromIdx
|
|
}
|
|
if commonTx.RqToIdx != 0 { // if true, all Rq... fields must be different to nil
|
|
readTx.RqToIdx = &commonTx.RqToIdx
|
|
readTx.RqTokenID = &commonTx.RqTokenID
|
|
readTx.RqFee = &commonTx.RqFee
|
|
readTx.RqNonce = &commonTx.RqNonce
|
|
}
|
|
if commonTx.RqToEthAddr != nilAddr {
|
|
readTx.RqToEthAddr = &commonTx.RqToEthAddr
|
|
}
|
|
return readTx
|
|
}
|
|
|
|
func assertReadTx(t *testing.T, expected, actual *PoolL2TxRead) {
|
|
// Check that timestamp has been set within the last 3 seconds
|
|
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
|
assert.GreaterOrEqual(t, time.Now().UTC().Unix(), actual.Timestamp.Unix())
|
|
expected.Timestamp = actual.Timestamp
|
|
// Check token related stuff
|
|
if expected.TokenUSDUpdate != nil {
|
|
// Check that TokenUSDUpdate has been set within the last 3 seconds
|
|
assert.Less(t, time.Now().UTC().Unix()-3, actual.TokenUSDUpdate.Unix())
|
|
assert.GreaterOrEqual(t, time.Now().UTC().Unix(), actual.TokenUSDUpdate.Unix())
|
|
expected.TokenUSDUpdate = actual.TokenUSDUpdate
|
|
}
|
|
assert.Equal(t, expected, actual)
|
|
}
|
|
|
|
func assertTx(t *testing.T, expected, actual *common.PoolL2Tx) {
|
|
// Check that timestamp has been set within the last 3 seconds
|
|
assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
|
|
assert.GreaterOrEqual(t, time.Now().UTC().Unix(), actual.Timestamp.Unix())
|
|
expected.Timestamp = actual.Timestamp
|
|
// Check absolute fee
|
|
// find token
|
|
token := historydb.TokenWithUSD{}
|
|
for _, tkn := range tokensUSD {
|
|
if expected.TokenID == tkn.TokenID {
|
|
token = tkn
|
|
break
|
|
}
|
|
}
|
|
// If the token has value in USD setted
|
|
if token.USDUpdate != nil {
|
|
assert.Equal(t, token.USDUpdate.Unix(), actual.AbsoluteFeeUpdate.Unix())
|
|
expected.AbsoluteFeeUpdate = actual.AbsoluteFeeUpdate
|
|
// Set expected fee
|
|
f := new(big.Float).SetInt(expected.Amount)
|
|
amountF, _ := f.Float64()
|
|
expected.AbsoluteFee = *token.USD * amountF * expected.Fee.Percentage()
|
|
test.AssertUSD(t, &expected.AbsoluteFee, &actual.AbsoluteFee)
|
|
}
|
|
assert.Equal(t, expected, actual)
|
|
}
|
|
|
|
func BenchmarkAddTxTest(b *testing.B) {
|
|
const nInserts = 20
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
now := time.Now()
|
|
for _, tx := range txs {
|
|
_ = l2DB.AddTxTest(tx)
|
|
}
|
|
elapsedTime := time.Since(now)
|
|
log.Info("Time to insert 2048 txs:", elapsedTime)
|
|
}
|
|
|
|
func TestGetPending(t *testing.T) {
|
|
const nInserts = 20
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
var pendingTxs []*common.PoolL2Tx
|
|
for _, tx := range txs {
|
|
err := l2DB.AddTxTest(tx)
|
|
assert.NoError(t, err)
|
|
if tx.State == common.PoolL2TxStatePending {
|
|
pendingTxs = append(pendingTxs, tx)
|
|
}
|
|
}
|
|
fetchedTxs, err := l2DB.GetPendingTxs()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, len(pendingTxs), len(fetchedTxs))
|
|
for i := range fetchedTxs {
|
|
assertTx(t, pendingTxs[i], &fetchedTxs[i])
|
|
}
|
|
}
|
|
|
|
/*
|
|
WARNING: this should be fixed once transaktio is ready
|
|
func TestStartForging(t *testing.T) {
|
|
// Generate txs
|
|
const nInserts = 60
|
|
const fakeBatchNum common.BatchNum = 33
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
var startForgingTxIDs []common.TxID
|
|
randomizer := 0
|
|
// Add txs to DB
|
|
for _, tx := range txs {
|
|
err := l2DB.AddTxTest(tx)
|
|
assert.NoError(t, err)
|
|
if tx.State == common.PoolL2TxStatePending && randomizer%2 == 0 {
|
|
randomizer++
|
|
startForgingTxIDs = append(startForgingTxIDs, tx.TxID)
|
|
}
|
|
}
|
|
// Start forging txs
|
|
err := l2DB.StartForging(startForgingTxIDs, fakeBatchNum)
|
|
assert.NoError(t, err)
|
|
// Fetch txs and check that they've been updated correctly
|
|
for _, id := range startForgingTxIDs {
|
|
fetchedTx, err := l2DB.GetTx(id)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, common.PoolL2TxStateForging, fetchedTx.State)
|
|
assert.Equal(t, fakeBatchNum, *fetchedTx.BatchNum)
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
WARNING: this should be fixed once transaktio is ready
|
|
func TestDoneForging(t *testing.T) {
|
|
// Generate txs
|
|
const nInserts = 60
|
|
const fakeBatchNum common.BatchNum = 33
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
var doneForgingTxIDs []common.TxID
|
|
randomizer := 0
|
|
// Add txs to DB
|
|
for _, tx := range txs {
|
|
err := l2DB.AddTxTest(tx)
|
|
assert.NoError(t, err)
|
|
if tx.State == common.PoolL2TxStateForging && randomizer%2 == 0 {
|
|
randomizer++
|
|
doneForgingTxIDs = append(doneForgingTxIDs, tx.TxID)
|
|
}
|
|
}
|
|
// Start forging txs
|
|
err := l2DB.DoneForging(doneForgingTxIDs, fakeBatchNum)
|
|
assert.NoError(t, err)
|
|
// Fetch txs and check that they've been updated correctly
|
|
for _, id := range doneForgingTxIDs {
|
|
fetchedTx, err := l2DB.GetTx(id)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, common.PoolL2TxStateForged, fetchedTx.State)
|
|
assert.Equal(t, fakeBatchNum, *fetchedTx.BatchNum)
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
WARNING: this should be fixed once transaktio is ready
|
|
func TestInvalidate(t *testing.T) {
|
|
// Generate txs
|
|
const nInserts = 60
|
|
const fakeBatchNum common.BatchNum = 33
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
var invalidTxIDs []common.TxID
|
|
randomizer := 0
|
|
// Add txs to DB
|
|
for _, tx := range txs {
|
|
err := l2DB.AddTxTest(tx)
|
|
assert.NoError(t, err)
|
|
if tx.State != common.PoolL2TxStateInvalid && randomizer%2 == 0 {
|
|
randomizer++
|
|
invalidTxIDs = append(invalidTxIDs, tx.TxID)
|
|
}
|
|
}
|
|
// Start forging txs
|
|
err := l2DB.InvalidateTxs(invalidTxIDs, fakeBatchNum)
|
|
assert.NoError(t, err)
|
|
// Fetch txs and check that they've been updated correctly
|
|
for _, id := range invalidTxIDs {
|
|
fetchedTx, err := l2DB.GetTx(id)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, common.PoolL2TxStateInvalid, fetchedTx.State)
|
|
assert.Equal(t, fakeBatchNum, *fetchedTx.BatchNum)
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
WARNING: this should be fixed once transaktio is ready
|
|
func TestCheckNonces(t *testing.T) {
|
|
// Generate txs
|
|
const nInserts = 60
|
|
const fakeBatchNum common.BatchNum = 33
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
var invalidTxIDs []common.TxID
|
|
// Generate accounts
|
|
const nAccoutns = 2
|
|
const currentNonce = 2
|
|
accs := []common.Account{}
|
|
for i := 0; i < nAccoutns; i++ {
|
|
accs = append(accs, common.Account{
|
|
Idx: common.Idx(i),
|
|
Nonce: currentNonce,
|
|
})
|
|
}
|
|
// Add txs to DB
|
|
for i := 0; i < len(txs); i++ {
|
|
if txs[i].State != common.PoolL2TxStateInvalid {
|
|
if i%2 == 0 { // Ensure transaction will be marked as invalid due to old nonce
|
|
txs[i].Nonce = accs[i%len(accs)].Nonce
|
|
txs[i].FromIdx = accs[i%len(accs)].Idx
|
|
invalidTxIDs = append(invalidTxIDs, txs[i].TxID)
|
|
} else { // Ensure transaction will NOT be marked as invalid due to old nonce
|
|
txs[i].Nonce = currentNonce + 1
|
|
}
|
|
}
|
|
err := l2DB.AddTxTest(txs[i])
|
|
assert.NoError(t, err)
|
|
}
|
|
// Start forging txs
|
|
err := l2DB.InvalidateTxs(invalidTxIDs, fakeBatchNum)
|
|
assert.NoError(t, err)
|
|
// Fetch txs and check that they've been updated correctly
|
|
for _, id := range invalidTxIDs {
|
|
fetchedTx, err := l2DB.GetTx(id)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, common.PoolL2TxStateInvalid, fetchedTx.State)
|
|
assert.Equal(t, fakeBatchNum, *fetchedTx.BatchNum)
|
|
}
|
|
}
|
|
*/
|
|
|
|
func TestReorg(t *testing.T) {
|
|
// Generate txs
|
|
const nInserts = 20
|
|
const lastValidBatch common.BatchNum = 20
|
|
const reorgBatch common.BatchNum = lastValidBatch + 1
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(nInserts, tokens)
|
|
// Add txs to the DB
|
|
reorgedTxIDs := []common.TxID{}
|
|
nonReorgedTxIDs := []common.TxID{}
|
|
for i := 0; i < len(txs); i++ {
|
|
err := l2DB.AddTxTest(txs[i])
|
|
assert.NoError(t, err)
|
|
var batchNum common.BatchNum
|
|
if txs[i].State == common.PoolL2TxStateForged || txs[i].State == common.PoolL2TxStateInvalid {
|
|
reorgedTxIDs = append(reorgedTxIDs, txs[i].TxID)
|
|
batchNum = reorgBatch
|
|
} else {
|
|
nonReorgedTxIDs = append(nonReorgedTxIDs, txs[i].TxID)
|
|
batchNum = lastValidBatch
|
|
}
|
|
_, err = l2DB.db.Exec(
|
|
"UPDATE tx_pool SET batch_num = $1 WHERE tx_id = $2;",
|
|
batchNum, txs[i].TxID,
|
|
)
|
|
assert.NoError(t, err)
|
|
}
|
|
err := l2DB.Reorg(lastValidBatch)
|
|
assert.NoError(t, err)
|
|
for _, id := range reorgedTxIDs {
|
|
tx, err := l2DB.GetTx(id)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, tx.BatchNum)
|
|
assert.Equal(t, common.PoolL2TxStatePending, tx.State)
|
|
}
|
|
for _, id := range nonReorgedTxIDs {
|
|
fetchedTx, err := l2DB.GetTx(id)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, lastValidBatch, *fetchedTx.BatchNum)
|
|
}
|
|
}
|
|
|
|
func TestPurge(t *testing.T) {
|
|
/*
|
|
WARNING: this should be fixed once transaktio is ready
|
|
// Generate txs
|
|
nInserts := l2DB.maxTxs + 20
|
|
test.CleanL2DB(l2DB.DB())
|
|
txs := test.GenPoolTxs(int(nInserts), tokens)
|
|
deletedIDs := []common.TxID{}
|
|
keepedIDs := []common.TxID{}
|
|
const toDeleteBatchNum common.BatchNum = 30
|
|
safeBatchNum := toDeleteBatchNum + l2DB.safetyPeriod + 1
|
|
// Add txs to the DB
|
|
for i := 0; i < int(l2DB.maxTxs); i++ {
|
|
var batchNum common.BatchNum
|
|
if i%2 == 0 { // keep tx
|
|
batchNum = safeBatchNum
|
|
keepedIDs = append(keepedIDs, txs[i].TxID)
|
|
} else { // delete after safety period
|
|
batchNum = toDeleteBatchNum
|
|
if i%3 == 0 {
|
|
txs[i].State = common.PoolL2TxStateForged
|
|
} else {
|
|
txs[i].State = common.PoolL2TxStateInvalid
|
|
}
|
|
deletedIDs = append(deletedIDs, txs[i].TxID)
|
|
}
|
|
err := l2DB.AddTxTest(txs[i])
|
|
assert.NoError(t, err)
|
|
// Set batchNum
|
|
_, err = l2DB.db.Exec(
|
|
"UPDATE tx_pool SET batch_num = $1 WHERE tx_id = $2;",
|
|
batchNum, txs[i].TxID,
|
|
)
|
|
assert.NoError(t, err)
|
|
}
|
|
for i := int(l2DB.maxTxs); i < len(txs); i++ {
|
|
// Delete after TTL
|
|
deletedIDs = append(deletedIDs, txs[i].TxID)
|
|
err := l2DB.AddTxTest(txs[i])
|
|
assert.NoError(t, err)
|
|
// Set timestamp
|
|
deleteTimestamp := time.Unix(time.Now().UTC().Unix()-int64(l2DB.ttl.Seconds()+float64(4*time.Second)), 0)
|
|
_, err = l2DB.db.Exec(
|
|
"UPDATE tx_pool SET timestamp = $1 WHERE tx_id = $2;",
|
|
deleteTimestamp, txs[i].TxID,
|
|
)
|
|
assert.NoError(t, err)
|
|
}
|
|
// Purge txs
|
|
err := l2DB.Purge(safeBatchNum)
|
|
assert.NoError(t, err)
|
|
// Check results
|
|
for _, id := range deletedIDs {
|
|
tx, err := l2DB.GetTx(id)
|
|
if err == nil {
|
|
log.Debug(tx)
|
|
}
|
|
assert.Error(t, err)
|
|
}
|
|
for _, id := range keepedIDs {
|
|
_, err := l2DB.GetTx(id)
|
|
assert.NoError(t, err)
|
|
}
|
|
*/
|
|
}
|
|
|
|
func TestAuth(t *testing.T) {
|
|
test.CleanL2DB(l2DB.DB())
|
|
const nAuths = 5
|
|
// Generate authorizations
|
|
auths := test.GenAuths(nAuths)
|
|
for i := 0; i < len(auths); i++ {
|
|
// Add to the DB
|
|
err := l2DB.AddAccountCreationAuth(auths[i])
|
|
assert.NoError(t, err)
|
|
// Fetch from DB
|
|
auth, err := l2DB.GetAccountCreationAuth(auths[i].EthAddr)
|
|
assert.NoError(t, err)
|
|
// Check fetched vs generated
|
|
assert.Equal(t, auths[i].EthAddr, auth.EthAddr)
|
|
assert.Equal(t, auths[i].BJJ, auth.BJJ)
|
|
assert.Equal(t, auths[i].Signature, auth.Signature)
|
|
assert.Equal(t, auths[i].Timestamp.Unix(), auths[i].Timestamp.Unix())
|
|
}
|
|
}
|