- Introduced a new Diagnostics struct to capture transaction visibility state and activity state. - Updated BuildSnapshot function to return diagnostics alongside snapshot, completeness, and sampling. - Enhanced test cases to validate the new diagnostics data. - Updated frontend components to utilize the new diagnostics information for improved user feedback on freshness context. This change improves the observability of transaction activity and enhances the user experience by providing clearer insights into the freshness of data.
139 lines
4.3 KiB
Go
139 lines
4.3 KiB
Go
package rest
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/explorer/backend/api/freshness"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type fakeStatsRow struct {
|
|
scan func(dest ...any) error
|
|
}
|
|
|
|
func (r fakeStatsRow) Scan(dest ...any) error {
|
|
return r.scan(dest...)
|
|
}
|
|
|
|
func TestLoadExplorerStatsReturnsValues(t *testing.T) {
|
|
rpc := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
Method string `json:"method"`
|
|
}
|
|
require.NoError(t, json.NewDecoder(r.Body).Decode(&req))
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch req.Method {
|
|
case "eth_blockNumber":
|
|
_, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":"0x2c"}`))
|
|
case "eth_getBlockByNumber":
|
|
ts := time.Now().Add(-2 * time.Second).Unix()
|
|
_, _ = w.Write([]byte(`{"jsonrpc":"2.0","id":1,"result":{"timestamp":"0x` + strconv.FormatInt(ts, 16) + `"}}`))
|
|
default:
|
|
http.Error(w, `{"jsonrpc":"2.0","id":1,"error":{"message":"unsupported"}}`, http.StatusBadRequest)
|
|
}
|
|
}))
|
|
defer rpc.Close()
|
|
t.Setenv("RPC_URL", rpc.URL)
|
|
var call int
|
|
queryRow := func(_ context.Context, _ string, _ ...any) pgx.Row {
|
|
call++
|
|
return fakeStatsRow{
|
|
scan: func(dest ...any) error {
|
|
switch call {
|
|
case 1:
|
|
*dest[0].(*int64) = 11
|
|
case 2:
|
|
*dest[0].(*int64) = 22
|
|
case 3:
|
|
*dest[0].(*int64) = 33
|
|
case 4:
|
|
*dest[0].(*int64) = 44
|
|
case 5:
|
|
*dest[0].(*sql.NullFloat64) = sql.NullFloat64{Float64: 2000, Valid: true}
|
|
case 6:
|
|
*dest[0].(*sql.NullFloat64) = sql.NullFloat64{Float64: 1.25, Valid: true}
|
|
case 7:
|
|
*dest[0].(*sql.NullFloat64) = sql.NullFloat64{Float64: 37.5, Valid: true}
|
|
case 8:
|
|
*dest[0].(*sql.NullInt64) = sql.NullInt64{Int64: 18, Valid: true}
|
|
case 9:
|
|
*dest[0].(*int64) = 44
|
|
*dest[1].(*time.Time) = time.Now().Add(-2 * time.Second)
|
|
case 10:
|
|
*dest[0].(*string) = "0xabc"
|
|
*dest[1].(*int64) = 40
|
|
*dest[2].(*time.Time) = time.Now().Add(-5 * time.Second)
|
|
case 11:
|
|
*dest[0].(*int64) = 40
|
|
*dest[1].(*time.Time) = time.Now().Add(-5 * time.Second)
|
|
case 12:
|
|
*dest[0].(*int64) = 42
|
|
*dest[1].(*time.Time) = time.Now().Add(-3 * time.Second)
|
|
case 13:
|
|
*dest[0].(*int64) = 128
|
|
*dest[1].(*int64) = 10
|
|
*dest[2].(*int64) = 22
|
|
default:
|
|
t.Fatalf("unexpected query call %d", call)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
stats, err := loadExplorerStats(context.Background(), 138, queryRow)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(11), stats.TotalBlocks)
|
|
require.Equal(t, int64(22), stats.TotalTransactions)
|
|
require.Equal(t, int64(33), stats.TotalAddresses)
|
|
require.Equal(t, int64(44), stats.LatestBlock)
|
|
require.NotNil(t, stats.AverageBlockTime)
|
|
require.Equal(t, 2000.0, *stats.AverageBlockTime)
|
|
require.NotNil(t, stats.GasPrices)
|
|
require.NotNil(t, stats.GasPrices.Average)
|
|
require.Equal(t, 1.25, *stats.GasPrices.Average)
|
|
require.NotNil(t, stats.NetworkUtilizationPercentage)
|
|
require.Equal(t, 37.5, *stats.NetworkUtilizationPercentage)
|
|
require.NotNil(t, stats.TransactionsToday)
|
|
require.Equal(t, int64(18), *stats.TransactionsToday)
|
|
require.NotNil(t, stats.Freshness.ChainHead.BlockNumber)
|
|
require.Equal(t, int64(40), *stats.Freshness.LatestIndexedTransaction.BlockNumber)
|
|
require.Equal(t, int64(4), *stats.Freshness.LatestNonEmptyBlock.DistanceFromHead)
|
|
require.Equal(t, "active", stats.Diagnostics.ActivityState)
|
|
require.Equal(t, int64(4), *stats.Diagnostics.TxLagBlocks)
|
|
require.Equal(t, "reported", string(stats.Freshness.ChainHead.Source))
|
|
require.Equal(t, freshness.CompletenessComplete, stats.Completeness.GasMetrics)
|
|
require.Equal(t, freshness.CompletenessComplete, stats.Completeness.UtilizationMetric)
|
|
}
|
|
|
|
func TestLoadExplorerStatsReturnsErrorWhenQueryFails(t *testing.T) {
|
|
t.Setenv("RPC_URL", "")
|
|
queryRow := func(_ context.Context, query string, _ ...any) pgx.Row {
|
|
return fakeStatsRow{
|
|
scan: func(dest ...any) error {
|
|
if strings.Contains(query, "COUNT(*) FROM transactions") {
|
|
return errors.New("boom")
|
|
}
|
|
target, ok := dest[0].(*int64)
|
|
require.True(t, ok)
|
|
*target = 1
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
_, err := loadExplorerStats(context.Background(), 138, queryRow)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "query total transactions")
|
|
}
|