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
31 changes: 29 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@ func (app *App) FinalizeBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock)
cms := app.WriteState()
app.LightInvarianceChecks(ctx.Context(), cms, app.lightInvarianceConfig)
appHash := app.GetWorkingHash()
resp := app.getFinalizeBlockResponse(appHash, events, txRes, endBlockResp, consensusParamUpdates)
resp := app.getFinalizeBlockResponse(ctx.Context(), appHash, events, txRes, endBlockResp, consensusParamUpdates)
if hasHeadNotifier {
headNotifier.Stash(req, &resp)
}
Expand Down Expand Up @@ -1357,7 +1357,7 @@ func (app *App) FinalizeBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock)
cms := app.WriteState()
app.LightInvarianceChecks(ctx.Context(), cms, app.lightInvarianceConfig)
appHash := app.GetWorkingHash()
resp := app.getFinalizeBlockResponse(appHash, events, txResults, endBlockResp, consensusParamUpdates)
resp := app.getFinalizeBlockResponse(ctx.Context(), appHash, events, txResults, endBlockResp, consensusParamUpdates)
if hasHeadNotifier {
headNotifier.Stash(req, &resp)
}
Expand Down Expand Up @@ -2318,6 +2318,7 @@ func (app *App) DecodeTransactionsConcurrently(ctx sdk.Context, txs [][]byte) []
}

func (app *App) getFinalizeBlockResponse(
ctx context.Context,
appHash []byte,
events []abci.Event,
txResults []*abci.ExecTxResult,
Expand All @@ -2327,6 +2328,14 @@ func (app *App) getFinalizeBlockResponse(
if app.EvmKeeper.EthReplayConfig.Enabled || app.EvmKeeper.EthBlockTestConfig.Enabled {
return abci.ResponseFinalizeBlock{}
}

// Both FinalizeBlocker paths that build a response converge here, so this records once
// per finalized block; ProcessBlock runs twice for a height whose optimistic result is
// discarded.
if gasUsed, ok := sumBlockGasUsed(txResults); ok {
appMetrics.blockGasUsed.Record(ctx, gasUsed)
}

return abci.ResponseFinalizeBlock{
Events: events,
TxResults: txResults,
Expand All @@ -2341,6 +2350,24 @@ func (app *App) getFinalizeBlockResponse(
}
}

// sumBlockGasUsed totals the gas consumed by every non-nil transaction result in a block.
// It reports false when a result carries negative gas or when the total overflows int64;
// the total is then meaningless and callers must discard it.
func sumBlockGasUsed(txResults []*abci.ExecTxResult) (int64, bool) {
var total int64
for _, txResult := range txResults {
if txResult == nil {
continue
}
gasUsed := txResult.GasUsed
if gasUsed < 0 || total > math.MaxInt64-gasUsed {
return 0, false
}
total += gasUsed
}
return total, true
}

func cloneConsensusParams(params *tmproto.ConsensusParams) *tmproto.ConsensusParams {
if params == nil {
return nil
Expand Down
61 changes: 61 additions & 0 deletions app/block_gas_used_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package app

import (
"math"
"testing"

"github.com/sei-protocol/sei-chain/sei-tendermint/abci/types"
"github.com/stretchr/testify/require"
)

// gasUsedResults builds one ExecTxResult per gas value, matching the shape
// FinalizeBlock hands to sumBlockGasUsed.
func gasUsedResults(gasUsed ...int64) []*types.ExecTxResult {
results := make([]*types.ExecTxResult, 0, len(gasUsed))
for _, g := range gasUsed {
results = append(results, &types.ExecTxResult{GasUsed: g})
}
return results
}

// TestSumBlockGasUsedEmptyBlock records a zero total for a block with no transactions, so
// the histogram carries one sample per finalized block.
func TestSumBlockGasUsedEmptyBlock(t *testing.T) {
total, ok := sumBlockGasUsed(nil)
require.True(t, ok)
require.Zero(t, total)
}

// TestSumBlockGasUsedSumsResults covers the ordinary path.
func TestSumBlockGasUsedSumsResults(t *testing.T) {
total, ok := sumBlockGasUsed(gasUsedResults(21_000, 100_000, 0, 5_000_000))
require.True(t, ok)
require.Equal(t, int64(5_121_000), total)
}

// TestSumBlockGasUsedSkipsNilResults exercises the nil-entry branch of the accounting loop.
func TestSumBlockGasUsedSkipsNilResults(t *testing.T) {
results := []*types.ExecTxResult{nil, {GasUsed: 21_000}, nil, {GasUsed: 42_000}}
total, ok := sumBlockGasUsed(results)
require.True(t, ok)
require.Equal(t, int64(63_000), total)
}

// TestSumBlockGasUsedRejectsNegativeGas exercises the negative-gas branch.
func TestSumBlockGasUsedRejectsNegativeGas(t *testing.T) {
_, ok := sumBlockGasUsed(gasUsedResults(21_000, -1))
require.False(t, ok)
}

// TestSumBlockGasUsedRejectsOverflow exercises the int64 overflow branch.
func TestSumBlockGasUsedRejectsOverflow(t *testing.T) {
_, ok := sumBlockGasUsed(gasUsedResults(math.MaxInt64, 1))
require.False(t, ok)
}

// TestSumBlockGasUsedAcceptsExactInt64Max confirms the overflow guard is not off by one.
func TestSumBlockGasUsedAcceptsExactInt64Max(t *testing.T) {
total, ok := sumBlockGasUsed(gasUsedResults(math.MaxInt64-1, 1))
require.True(t, ok)
require.Equal(t, int64(math.MaxInt64), total)
}
1 change: 1 addition & 0 deletions app/consensus_params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func TestGetFinalizeBlockResponsePropagatesFullConsensusParams(t *testing.T) {

app := &App{}
resp := app.getFinalizeBlockResponse(
t.Context(),
[]byte("hash"),
nil,
nil,
Expand Down
15 changes: 14 additions & 1 deletion app/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ var (
0.000025, 0.000050, 0.0001, 0.0005, 0.001, 0.0025, 0.005, 0.010, 0.020, 0.050, 0.075, 0.1, 0.25, 0.5, 1, 10,
)

// blockGasWantedBuckets covers the 0–50 M gas range (current MaxGasWanted cap)
// blockGasWantedBuckets covers the 0–50 M gas range (current MaxGasWanted cap).
// The per-block gas wanted and gas used histograms share it: a transaction's gas
// used is bounded by its gas wanted, so the same cap bounds both block totals.
blockGasWantedBuckets = metric.WithExplicitBucketBoundaries(
10e3, 25e3, 50e3, 100e3, 250e3, 500e3,
1e6, 2.5e6, 5e6, 10e6, 12.5e6, 25e6, 50e6,
Expand Down Expand Up @@ -66,6 +68,7 @@ var (
// Per-block gas utilisation
blockGasWanted metric.Int64Histogram
blockGasWantedRatio metric.Float64Histogram
blockGasUsed metric.Int64Histogram

// Light invariance check
invarianceDuration metric.Float64Histogram
Expand Down Expand Up @@ -180,6 +183,16 @@ var (
blockGasWantedRatioBuckets,
)),

blockGasUsed: must(meter.Int64Histogram(
"app_block_gas_used",
metric.WithDescription("Per-block total gas used across all transactions. Recorded on "+
"FinalizeBlock, so unlike app_block_gas_wanted (recorded on ProcessProposal) it has "+
"samples during block/state sync with no matching gas_wanted sample -- do not divide "+
"the two without accounting for that"),
metric.WithUnit("{gas}"),
blockGasWantedBuckets,
)),

invarianceDuration: must(meter.Float64Histogram(
"app_lightinvariance_supply_duration",
metric.WithDescription("Duration of light invariance total supply check"),
Expand Down
Loading