Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion evmrpc/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (a *StateAPI) GetProof(ctx context.Context, address common.Address, storage
for _, key := range storageKeys {
paddedKey := common.BytesToHash([]byte(key))
formattedKey := append(types.StateKey(address), paddedKey[:]...)
qres := queryStore.Query(abci.RequestQuery{
qres := queryStore.Query(ctx, abci.RequestQuery{
Path: "/key",
Data: formattedKey,
Height: block.Block.Height,
Expand Down
73 changes: 60 additions & 13 deletions sei-cosmos/baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/sei-protocol/sei-chain/sei-cosmos/types/legacytm"
abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types"
tmproto "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types"
"go.opentelemetry.io/otel/attribute"
otelmetric "go.opentelemetry.io/otel/metric"
"google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"
)
Expand Down Expand Up @@ -102,7 +104,12 @@ func (app *BaseApp) Info(ctx context.Context, req *abci.RequestInfo) (*abci.Resp
}

func (app *BaseApp) MidBlock(ctx sdk.Context, height int64) (events []abci.Event) {
defer telemetry.MeasureSince(time.Now(), "abci", "mid_block")
start := time.Now()
defer func() {
baseappMetrics.midBlockDuration.Record(ctx.Context(), time.Since(start).Seconds())
// TODO(PLT-353): remove once baseapp_mid_block_duration verified
telemetry.MeasureSince(start, "abci", "mid_block")
}()

if app.midBlocker != nil {
midBlockEvents := app.midBlocker(ctx, height)
Expand All @@ -114,7 +121,12 @@ func (app *BaseApp) MidBlock(ctx sdk.Context, height int64) (events []abci.Event

// EndBlock implements the ABCI interface.
func (app *BaseApp) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
defer telemetry.MeasureSince(time.Now(), "abci", "end_block")
start := time.Now()
defer func() {
baseappMetrics.endBlockDuration.Record(ctx.Context(), time.Since(start).Seconds())
// TODO(PLT-353): remove once baseapp_end_block_duration verified
telemetry.MeasureSince(start, "abci", "end_block")
}()

if app.endBlocker != nil {
res = app.endBlocker(ctx, req)
Expand Down Expand Up @@ -166,15 +178,25 @@ func (app *BaseApp) DeliverTxBatch(ctx sdk.Context, req sdk.DeliverTxBatchReques
// Regardless of tx execution outcome, the ResponseDeliverTx will contain relevant
// gas execution context.
func (app *BaseApp) DeliverTx(ctx sdk.Context, req abci.RequestDeliverTxV2, tx sdk.Tx, checksum [32]byte) (res abci.ResponseDeliverTx) {
defer telemetry.MeasureSince(time.Now(), "abci", "deliver_tx")

deliverTxStart := time.Now()
gInfo := sdk.GasInfo{}
resultStr := "successful"

defer func() {
baseappMetrics.deliverTxDuration.Record(ctx.Context(), time.Since(deliverTxStart).Seconds())
// TODO(PLT-353): remove once baseapp_deliver_tx_duration verified
telemetry.MeasureSince(deliverTxStart, "abci", "deliver_tx")
baseappMetrics.txCount.Add(ctx.Context(), 1)
// TODO(PLT-353): remove once baseapp_tx_count verified
telemetry.IncrCounter(1, "tx", "count")
baseappMetrics.txResult.Add(ctx.Context(), 1, otelmetric.WithAttributes(attribute.String("result", resultStr)))
// TODO(PLT-353): remove once baseapp_tx_result verified
telemetry.IncrCounter(1, "tx", resultStr)
baseappMetrics.txGasUsed.Record(ctx.Context(), int64(gInfo.GasUsed)) //nolint:gosec
// TODO(PLT-353): remove once baseapp_tx_gas_used verified
telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used")
baseappMetrics.txGasWanted.Record(ctx.Context(), int64(gInfo.GasWanted)) //nolint:gosec
// TODO(PLT-353): remove once baseapp_tx_gas_wanted verified
telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted")
}()

Expand Down Expand Up @@ -245,8 +267,12 @@ func (app *BaseApp) SetDeliverStateToCommit() {
// against that height and gracefully halt if it matches the latest committed
// height.
func (app *BaseApp) Commit(ctx context.Context) (res *abci.ResponseCommit, err error) {
defer telemetry.MeasureSince(time.Now(), "abci", "commit")
commitStart := time.Now()
defer func() {
baseappMetrics.commitDuration.Record(ctx, time.Since(commitStart).Seconds())
// TODO(PLT-353): remove once baseapp_commit_duration verified
telemetry.MeasureSince(commitStart, "abci", "commit")
}()
app.commitLock.Lock()
defer app.commitLock.Unlock()

Expand Down Expand Up @@ -388,7 +414,13 @@ func (app *BaseApp) Snapshot(height int64) {
// Query implements the ABCI interface. It delegates to CommitMultiStore if it
// implements Queryable.
func (app *BaseApp) Query(ctx context.Context, req *abci.RequestQuery) (res *abci.ResponseQuery, err error) {
defer telemetry.MeasureSinceWithLabels([]string{"abci", "query"}, time.Now(), []metrics.Label{{Name: "path", Value: req.Path}})
queryStart := time.Now()
defer func() {
route := app.abciQueryMetricRoute(req.Path)
baseappMetrics.abciQueryDuration.Record(ctx, time.Since(queryStart).Seconds(), otelmetric.WithAttributes(attribute.String(abciQueryMetricRouteLabel, route)))
// TODO(PLT-353): remove once baseapp_abci_query_duration verified
telemetry.MeasureSinceWithLabels([]string{"abci", "query"}, queryStart, []metrics.Label{{Name: "path", Value: req.Path}})
}()

// Add panic recovery for all queries.
// ref: https://github.com/cosmos/cosmos-sdk/pull/8039
Expand Down Expand Up @@ -425,7 +457,7 @@ func (app *BaseApp) Query(ctx context.Context, req *abci.RequestQuery) (res *abc
resp = handleQueryApp(app, path, *req)

case "store":
resp = handleQueryStore(app, path, *req)
resp = handleQueryStore(ctx, app, path, *req)

case "custom":
resp = handleQueryCustom(app, path, *req)
Expand Down Expand Up @@ -846,7 +878,7 @@ func handleQueryApp(app *BaseApp, path []string, req abci.RequestQuery) abci.Res
), app.trace)
}

func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery {
func handleQueryStore(ctx context.Context, app *BaseApp, path []string, req abci.RequestQuery) abci.ResponseQuery {
var (
queryable sdk.Queryable
ok bool
Expand Down Expand Up @@ -875,7 +907,7 @@ func handleQueryStore(app *BaseApp, path []string, req abci.RequestQuery) abci.R
), app.trace)
}

resp := queryable.Query(req)
resp := queryable.Query(ctx, req)
resp.Height = req.Height

return resp
Expand Down Expand Up @@ -934,7 +966,12 @@ func splitPath(requestPath string) (path []string) {

// ABCI++
func (app *BaseApp) ProcessProposal(ctx context.Context, req *abci.RequestProcessProposal) (resp *abci.ResponseProcessProposal, err error) {
defer telemetry.MeasureSince(time.Now(), "abci", "process_proposal")
processProposalStart := time.Now()
defer func() {
baseappMetrics.processProposalDuration.Record(ctx, time.Since(processProposalStart).Seconds())
// TODO(PLT-353): remove once baseapp_process_proposal_duration verified
telemetry.MeasureSince(processProposalStart, "abci", "process_proposal")
}()
ppStart := time.Now()
defer func() { app.execProcessProposalMs = time.Since(ppStart).Milliseconds() }()
if app.ChainID != req.Header.ChainID {
Expand Down Expand Up @@ -997,7 +1034,12 @@ func (app *BaseApp) ProcessProposal(ctx context.Context, req *abci.RequestProces
}

func (app *BaseApp) FinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
defer telemetry.MeasureSince(time.Now(), "abci", "finalize_block")
finalizeBlockStart := time.Now()
defer func() {
baseappMetrics.finalizeBlockDuration.Record(ctx, time.Since(finalizeBlockStart).Seconds())
// TODO(PLT-353): remove once baseapp_finalize_block_duration verified
telemetry.MeasureSince(finalizeBlockStart, "abci", "finalize_block")
}()
fbStart := time.Now()
app.execBlockTxCount = len(req.Txs)
defer func() { app.execFinalizeBlockMs = time.Since(fbStart).Milliseconds() }()
Expand Down Expand Up @@ -1040,7 +1082,7 @@ func (app *BaseApp) FinalizeBlock(ctx context.Context, req *abci.RequestFinalize
}
}

func (app *BaseApp) GetTxPriorityHint(_ context.Context, req *abci.RequestGetTxPriorityHintV2) (_resp *abci.ResponseGetTxPriorityHint, _err error) {
func (app *BaseApp) GetTxPriorityHint(ctx context.Context, req *abci.RequestGetTxPriorityHintV2) (_resp *abci.ResponseGetTxPriorityHint, _err error) {
defer func() {
if r := recover(); r != nil {
// Fall back to no-op priority if we panic for any reason. This is to avoid DoS
Expand All @@ -1056,7 +1098,12 @@ func (app *BaseApp) GetTxPriorityHint(_ context.Context, req *abci.RequestGetTxP
}
}()

defer telemetry.MeasureSince(time.Now(), "abci", "get_tx_priority_hint")
priorityHintStart := time.Now()
defer func() {
baseappMetrics.getTxPriorityHintDuration.Record(ctx, time.Since(priorityHintStart).Seconds())
// TODO(PLT-353): remove once baseapp_get_tx_priority_hint_duration verified
telemetry.MeasureSince(priorityHintStart, "abci", "get_tx_priority_hint")
}()

tx, err := app.txDecoder(req.Tx)
if err != nil {
Expand Down
85 changes: 85 additions & 0 deletions sei-cosmos/baseapp/abci_query_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package baseapp

import storetypes "github.com/sei-protocol/sei-chain/sei-cosmos/store/types"

// storeByNameLookup is implemented by CommitMultiStore backends (rootmulti, storev2).
type storeByNameLookup interface {
GetStoreByName(name string) storetypes.Store
}

// abciStoreQuerySubpaths are the only subpath segments used by mounted store
// Query implementations (/key, /subspace). See store/rootmulti and storev2/commitment.
var abciStoreQuerySubpaths = map[string]struct{}{
"key": {},
"subspace": {},
}

// abciQueryMetricRoute returns a bounded label for ABCI query metrics.
// Raw client paths are not used directly to avoid unbounded metric cardinality.
//
// Rules:
// - Registered gRPC query paths are returned as-is (finite set at startup).
// - Legacy paths use a fixed prefix + registered segment shape.
// - Everything else is "other".
func (app *BaseApp) abciQueryMetricRoute(reqPath string) string {
if app.grpcQueryRouter != nil && app.grpcQueryRouter.Route(reqPath) != nil {
return reqPath
}

parts := splitPath(reqPath)
if len(parts) == 0 {
return "other"
}

switch parts[0] {
case "app":
if len(parts) >= 2 {
switch parts[1] {
case "simulate", "version", "snapshots":
return "app/" + parts[1]
}
}
return "app/unknown"

case "store":
return app.abciStoreQueryMetricRoute(parts)

case "custom":
if len(parts) >= 2 && parts[1] != "" && app.queryRouter != nil && app.queryRouter.Route(parts[1]) != nil {
return "custom/" + parts[1]
}
return "custom/unknown"

default:
return "other"
}
}

func (app *BaseApp) abciStoreQueryMetricRoute(parts []string) string {
if len(parts) < 3 {
return "store/unknown"
}
storeName, subpath := parts[1], parts[2]
if _, ok := abciStoreQuerySubpaths[subpath]; !ok {
return "store/unknown"
}
if !app.storeRegisteredForQuery(storeName) {
return "store/unknown"
}
return "store/" + storeName + "/" + subpath
}

func (app *BaseApp) storeRegisteredForQuery(name string) bool {
if lookup, ok := app.cms.(storeByNameLookup); ok && lookup.GetStoreByName(name) != nil {
return true
}
if app.qms != nil {
if lookup, ok := app.qms.(storeByNameLookup); ok && lookup.GetStoreByName(name) != nil {
return true
}
}
return false
}

// abciQueryMetricRouteLabel is kept as "path" for compatibility with existing dashboards.
const abciQueryMetricRouteLabel = "path"
74 changes: 74 additions & 0 deletions sei-cosmos/baseapp/abci_query_metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package baseapp

import (
"testing"

abci "github.com/sei-protocol/sei-chain/sei-tendermint/abci/types"
"github.com/sei-protocol/sei-chain/sei-cosmos/testutil"
"github.com/sei-protocol/sei-chain/sei-cosmos/testutil/testdata"
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tm-db"
)

func TestAbciQueryMetricRoute(t *testing.T) {
db := dbm.NewMemDB()
app := NewBaseApp(t.Name(), db, nil, nil, &testutil.TestAppOpts{})

grpcQR := app.GRPCQueryRouter()
grpcQR.SetInterfaceRegistry(testdata.NewTestInterfaceRegistry())
testdata.RegisterQueryServer(grpcQR, testdata.QueryImpl{})

app.QueryRouter().AddRoute("bank", func(_ sdk.Context, _ []string, _ abci.RequestQuery) ([]byte, error) {
return nil, nil
})

grpcEcho := "/testdata.Query/Echo"

tests := map[string]struct {
reqPath string
expected string
}{
// Production gRPC paths (3-month sample); registered via testdata.Query.
"grpc registered": {
reqPath: grpcEcho,
expected: grpcEcho,
},
"grpc unregistered": {
reqPath: "/cosmos.bank.v1beta1.Query/TotalSupply",
expected: "other",
},

// Legacy app paths.
"app snapshots": {reqPath: "app/snapshots", expected: "app/snapshots"},
"app version": {reqPath: "app/version", expected: "app/version"},
"app unknown": {reqPath: "app/unknown-action", expected: "app/unknown"},

// Legacy store paths (unknown store name or subpath → single bucket).
"store ibc key unregistered": {reqPath: "store/ibc/key", expected: "store/unknown"},
"store random attack": {reqPath: "store/random1/random2", expected: "store/unknown"},
"store bad subpath": {reqPath: "store/key1/evil", expected: "store/unknown"},
"store short": {reqPath: "store/bank", expected: "store/unknown"},

// Legacy custom paths; subpath segments are not part of the metric label.
"custom bank": {reqPath: "custom/bank/all_balances", expected: "custom/bank"},
"custom unregistered": {reqPath: "custom/unknown/foo", expected: "custom/unknown"},

// Garbage / attack paths.
"empty": {reqPath: "", expected: "other"},
"random": {reqPath: "/totally/made/up", expected: "other"},
"leading slash app": {reqPath: "/app/snapshots", expected: "app/snapshots"},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
require.Equal(t, tc.expected, app.abciQueryMetricRoute(tc.reqPath))
})
}
}

func TestAbciQueryMetricRoute_RegisteredStore(t *testing.T) {
app := setupBaseApp(t)
require.Equal(t, "store/key1/key", app.abciQueryMetricRoute("store/key1/key"))
require.Equal(t, "store/unknown", app.abciQueryMetricRoute("store/random1/key"))
}
2 changes: 1 addition & 1 deletion sei-cosmos/baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func TestHandleQueryStore_NonQueryableMultistore(t *testing.T) {
Height: 1,
}

resp := handleQueryStore(app, path, req)
resp := handleQueryStore(context.Background(), app, path, req)
require.True(t, resp.IsErr())
require.Contains(t, resp.Log, "multistore doesn't support queries")
}
Expand Down
Loading