diff --git a/api/state_test.go b/api/state_test.go index 267ffdf..033f559 100644 --- a/api/state_test.go +++ b/api/state_test.go @@ -97,7 +97,6 @@ func TestUpdateNetworkInfo(t *testing.T) { } func TestUpdateMetrics(t *testing.T) { - // TODO: Improve checks when til is integrated // Update Metrics needs api.status.Network.LastBatch.BatchNum to be updated lastBlock := tc.blocks[3] lastBatchNum := common.BatchNum(3) @@ -109,7 +108,7 @@ func TestUpdateMetrics(t *testing.T) { assert.NoError(t, err) assert.Greater(t, api.status.Metrics.TransactionsPerBatch, float64(0)) assert.Greater(t, api.status.Metrics.BatchFrequency, float64(0)) - assert.Greater(t, api.status.Metrics.TransactionsPerBatch, float64(0)) + assert.Greater(t, api.status.Metrics.TransactionsPerSecond, float64(0)) assert.Greater(t, api.status.Metrics.TotalAccounts, int64(0)) assert.Greater(t, api.status.Metrics.TotalBJJs, int64(0)) assert.Greater(t, api.status.Metrics.AvgTransactionFee, float64(0)) @@ -153,7 +152,7 @@ func TestGetState(t *testing.T) { assert.Equal(t, int(api.status.Auction.ClosedAuctionSlots)+1, len(status.Network.NextForgers)) assert.Greater(t, status.Metrics.TransactionsPerBatch, float64(0)) assert.Greater(t, status.Metrics.BatchFrequency, float64(0)) - assert.Greater(t, status.Metrics.TransactionsPerBatch, float64(0)) + assert.Greater(t, status.Metrics.TransactionsPerSecond, float64(0)) assert.Greater(t, status.Metrics.TotalAccounts, int64(0)) assert.Greater(t, status.Metrics.TotalBJJs, int64(0)) assert.Greater(t, status.Metrics.AvgTransactionFee, float64(0)) diff --git a/db/historydb/historydb.go b/db/historydb/historydb.go index 799d04a..1ea20b7 100644 --- a/db/historydb/historydb.go +++ b/db/historydb/historydb.go @@ -1800,17 +1800,25 @@ func (hdb *HistoryDB) GetMetrics(lastBatchNum common.BatchNum) (*Metrics, error) metrics := &Metrics{} err := meddler.QueryRow( hdb.db, metricsTotals, `SELECT COUNT(tx.*) as total_txs, - COALESCE (MIN(tx.batch_num), 0) as batch_num + COALESCE (MIN(tx.batch_num), 0) as batch_num, COALESCE (MIN(block.timestamp), + NOW()) AS min_timestamp, COALESCE (MAX(block.timestamp), NOW()) AS max_timestamp FROM tx INNER JOIN block ON tx.eth_block_num = block.eth_block_num WHERE block.timestamp >= NOW() - INTERVAL '24 HOURS';`) if err != nil { return nil, tracerr.Wrap(err) } - metrics.TransactionsPerSecond = float64(metricsTotals.TotalTransactions / (24 * 60 * 60)) + seconds := metricsTotals.MaxTimestamp.Sub(metricsTotals.MinTimestamp).Seconds() + // Avoid dividing by 0 + if seconds == 0 { + seconds++ + } + + metrics.TransactionsPerSecond = float64(metricsTotals.TotalTransactions) / seconds + if (lastBatchNum - metricsTotals.FirstBatchNum) > 0 { - metrics.TransactionsPerBatch = float64(int64(metricsTotals.TotalTransactions) / - int64(lastBatchNum-metricsTotals.FirstBatchNum)) + metrics.TransactionsPerBatch = float64(metricsTotals.TotalTransactions) / + float64(lastBatchNum-metricsTotals.FirstBatchNum+1) } else { metrics.TransactionsPerBatch = float64(0) } @@ -1823,7 +1831,7 @@ func (hdb *HistoryDB) GetMetrics(lastBatchNum common.BatchNum) (*Metrics, error) return nil, tracerr.Wrap(err) } if metricsTotals.TotalBatches > 0 { - metrics.BatchFrequency = float64((24 * 60 * 60) / metricsTotals.TotalBatches) + metrics.BatchFrequency = seconds / float64(metricsTotals.TotalBatches) } else { metrics.BatchFrequency = 0 } diff --git a/db/historydb/historydb_test.go b/db/historydb/historydb_test.go index f5dbc4d..38daa7f 100644 --- a/db/historydb/historydb_test.go +++ b/db/historydb/historydb_test.go @@ -2,6 +2,7 @@ package historydb import ( "database/sql" + "fmt" "math" "math/big" "os" @@ -1027,6 +1028,164 @@ func TestAddEscapeHatchWithdrawals(t *testing.T) { assert.Equal(t, escapeHatchWithdrawals, dbEscapeHatchWithdrawals) } +func TestGetMetrics(t *testing.T) { + test.WipeDB(historyDB.DB()) + + set := ` + Type: Blockchain + + AddToken(1) + + CreateAccountDeposit(1) A: 1000 // numTx=1 + CreateAccountDeposit(1) B: 2000 // numTx=2 + CreateAccountDeposit(1) C: 3000 //numTx=3 + + // block 0 is stored as default in the DB + // block 1 does not exist + > batchL1 // numBatches=1 + > batchL1 // numBatches=2 + > block // blockNum=2 + + Transfer(1) C-A : 10 (1) // numTx=4 + > batch // numBatches=3 + > block // blockNum=3 + Transfer(1) B-C : 10 (1) // numTx=5 + > batch // numBatches=5 + > block // blockNum=4 + Transfer(1) A-B : 10 (1) // numTx=6 + > batch // numBatches=5 + > block // blockNum=5 + Transfer(1) A-B : 10 (1) // numTx=7 + > batch // numBatches=6 + > block // blockNum=6 + ` + + const numBatches int = 6 + const numTx int = 7 + const blockNum = 6 - 1 + + tc := til.NewContext(uint16(0), common.RollupConstMaxL1UserTx) + tilCfgExtra := til.ConfigExtra{ + BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"), + CoordUser: "A", + } + blocks, err := tc.GenerateBlocks(set) + require.NoError(t, err) + err = tc.FillBlocksExtra(blocks, &tilCfgExtra) + require.NoError(t, err) + + // Sanity check + require.Equal(t, blockNum, len(blocks)) + + // Adding one batch per block + // batch frequency can be chosen + const frequency int = 15 + + for i := range blocks { + blocks[i].Block.Timestamp = time.Now().Add(-time.Second * time.Duration(frequency*(len(blocks)-i))) + err = historyDB.AddBlockSCData(&blocks[i]) + assert.NoError(t, err) + } + + res, err := historyDB.GetMetrics(common.BatchNum(numBatches)) + assert.NoError(t, err) + + assert.Equal(t, float64(numTx)/float64(numBatches-1), res.TransactionsPerBatch) + + // Frequency is not exactly the desired one, some decimals may appear + assert.GreaterOrEqual(t, res.BatchFrequency, float64(frequency)) + assert.Less(t, res.BatchFrequency, float64(frequency+1)) + // Truncate frecuency into an int to do an exact check + assert.Equal(t, frequency, int(res.BatchFrequency)) + // This may also be different in some decimals + // Truncate it to the third decimal to compare + assert.Equal(t, math.Trunc((float64(numTx)/float64(frequency*blockNum-frequency))/0.001)*0.001, math.Trunc(res.TransactionsPerSecond/0.001)*0.001) + assert.Equal(t, int64(3), res.TotalAccounts) + assert.Equal(t, int64(3), res.TotalBJJs) + // Til does not set fees + assert.Equal(t, float64(0), res.AvgTransactionFee) +} + +func TestGetMetricsMoreThan24Hours(t *testing.T) { + test.WipeDB(historyDB.DB()) + + testUsersLen := 3 + var set []til.Instruction + for user := 0; user < testUsersLen; user++ { + set = append(set, til.Instruction{ + Typ: common.TxTypeCreateAccountDeposit, + TokenID: common.TokenID(0), + DepositAmount: big.NewInt(1000000), + Amount: big.NewInt(0), + From: fmt.Sprintf("User%02d", user), + }) + set = append(set, til.Instruction{Typ: til.TypeNewBlock}) + } + set = append(set, til.Instruction{Typ: til.TypeNewBatchL1}) + set = append(set, til.Instruction{Typ: til.TypeNewBatchL1}) + set = append(set, til.Instruction{Typ: til.TypeNewBlock}) + + // Transfers + for x := 0; x < 6000; x++ { + set = append(set, til.Instruction{ + Typ: common.TxTypeTransfer, + TokenID: common.TokenID(0), + DepositAmount: big.NewInt(1), + Amount: big.NewInt(0), + From: "User00", + To: "User01", + }) + set = append(set, til.Instruction{Typ: til.TypeNewBatch}) + set = append(set, til.Instruction{Typ: til.TypeNewBlock}) + } + + var chainID uint16 = 0 + tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx) + blocks, err := tc.GenerateBlocksFromInstructions(set) + assert.NoError(t, err) + + tilCfgExtra := til.ConfigExtra{ + CoordUser: "A", + } + err = tc.FillBlocksExtra(blocks, &tilCfgExtra) + require.NoError(t, err) + + const numBatches int = 6002 + const numTx int = 6003 + const blockNum = 6005 - 1 + + // Sanity check + require.Equal(t, blockNum, len(blocks)) + + // Adding one batch per block + // batch frequency can be chosen + const frequency int = 15 + + for i := range blocks { + blocks[i].Block.Timestamp = time.Now().Add(-time.Second * time.Duration(frequency*(len(blocks)-i))) + err = historyDB.AddBlockSCData(&blocks[i]) + assert.NoError(t, err) + } + + res, err := historyDB.GetMetrics(common.BatchNum(numBatches)) + assert.NoError(t, err) + + assert.Equal(t, math.Trunc((float64(numTx)/float64(numBatches-1))/0.001)*0.001, math.Trunc(res.TransactionsPerBatch/0.001)*0.001) + + // Frequency is not exactly the desired one, some decimals may appear + assert.GreaterOrEqual(t, res.BatchFrequency, float64(frequency)) + assert.Less(t, res.BatchFrequency, float64(frequency+1)) + // Truncate frecuency into an int to do an exact check + assert.Equal(t, frequency, int(res.BatchFrequency)) + // This may also be different in some decimals + // Truncate it to the third decimal to compare + assert.Equal(t, math.Trunc((float64(numTx)/float64(frequency*blockNum-frequency))/0.001)*0.001, math.Trunc(res.TransactionsPerSecond/0.001)*0.001) + assert.Equal(t, int64(3), res.TotalAccounts) + assert.Equal(t, int64(3), res.TotalBJJs) + // Til does not set fees + assert.Equal(t, float64(0), res.AvgTransactionFee) +} + func TestGetMetricsEmpty(t *testing.T) { test.WipeDB(historyDB.DB()) _, err := historyDB.GetMetrics(0) diff --git a/db/historydb/views.go b/db/historydb/views.go index a70b46f..89e541a 100644 --- a/db/historydb/views.go +++ b/db/historydb/views.go @@ -314,6 +314,8 @@ type MetricsTotals struct { FirstBatchNum common.BatchNum `meddler:"batch_num"` TotalBatches int64 `meddler:"total_batches"` TotalFeesUSD float64 `meddler:"total_fees"` + MinTimestamp time.Time `meddler:"min_timestamp,utctime"` + MaxTimestamp time.Time `meddler:"max_timestamp,utctime"` } // BidAPI is a representation of a bid with additional information