Skip to content

Commit c4d7ca1

Browse files
authored
feat: monitor empty consensus block (#89)
Add monitoring for empty consensus blocks. Important for chains where empty CL blocks aren't broadcasted to execution layer (e.g. Berachain), affecting validator's EL uptime. - Added empty_blocks prometheus counter - Added empty block indicator (🟡) in logs - Added tests
1 parent f09d0c2 commit c4d7ca1

File tree

4 files changed

+46
-6
lines changed

4 files changed

+46
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Metrics (without prefix) | Description
116116
`node_synced` | Set to 1 is the node is synced (ie. not catching-up)
117117
`proposal_end_time` | Timestamp of the voting end time of a proposal
118118
`proposed_blocks` | Number of proposed blocks per validator (for a bonded validator)
119+
`empty_blocks` | Number of empty blocks (blocks with zero transactions) proposed by validator
119120
`rank` | Rank of the validator
120121
`seat_price` | Min seat price to be in the active set (ie. bonded tokens of the latest validator)
121122
`signed_blocks_window` | Number of blocks per signing window

pkg/metrics/metrics.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Metrics struct {
3131
SoloMissedBlocks *prometheus.CounterVec
3232
ConsecutiveMissedBlocks *prometheus.GaugeVec
3333
MissedBlocksWindow *prometheus.GaugeVec
34+
EmptyBlocks *prometheus.CounterVec
3435
Tokens *prometheus.GaugeVec
3536
IsBonded *prometheus.GaugeVec
3637
IsJailed *prometheus.GaugeVec
@@ -125,6 +126,14 @@ func New(namespace string) *Metrics {
125126
},
126127
[]string{"chain_id", "address", "name"},
127128
),
129+
EmptyBlocks: prometheus.NewCounterVec(
130+
prometheus.CounterOpts{
131+
Namespace: namespace,
132+
Name: "empty_blocks",
133+
Help: "Number of empty blocks proposed by validator",
134+
},
135+
[]string{"chain_id", "address", "name"},
136+
),
128137
TrackedBlocks: prometheus.NewCounterVec(
129138
prometheus.CounterOpts{
130139
Namespace: namespace,
@@ -280,6 +289,7 @@ func (m *Metrics) Register() {
280289
m.Registry.MustRegister(m.SoloMissedBlocks)
281290
m.Registry.MustRegister(m.ConsecutiveMissedBlocks)
282291
m.Registry.MustRegister(m.MissedBlocksWindow)
292+
m.Registry.MustRegister(m.EmptyBlocks)
283293
m.Registry.MustRegister(m.TrackedBlocks)
284294
m.Registry.MustRegister(m.Transactions)
285295
m.Registry.MustRegister(m.SkippedBlocks)

pkg/watcher/block.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type BlockWatcher struct {
3131
validatorSet atomic.Value // []*types.Validator
3232
latestBlockHeight int64
3333
latestBlockProposer string
34+
latestBlockTransactions int
3435
webhook *webhook.Webhook
3536
customWebhooks []BlockWebhook
3637
}
@@ -177,12 +178,13 @@ func (w *BlockWatcher) handleBlockInfo(ctx context.Context, block *BlockInfo) {
177178
return
178179
}
179180

180-
// Ensure to inititalize counters for each validator
181+
// Ensure to initialize counters for each validator
181182
for _, val := range w.trackedValidators {
182183
w.metrics.ValidatedBlocks.WithLabelValues(chainId, val.Address, val.Name)
183184
w.metrics.MissedBlocks.WithLabelValues(chainId, val.Address, val.Name)
184185
w.metrics.SoloMissedBlocks.WithLabelValues(chainId, val.Address, val.Name)
185186
w.metrics.ConsecutiveMissedBlocks.WithLabelValues(chainId, val.Address, val.Name)
187+
w.metrics.EmptyBlocks.WithLabelValues(chainId, val.Address, val.Name)
186188
}
187189
w.metrics.SkippedBlocks.WithLabelValues(chainId)
188190

@@ -202,7 +204,13 @@ func (w *BlockWatcher) handleBlockInfo(ctx context.Context, block *BlockInfo) {
202204
for _, res := range block.ValidatorStatus {
203205
icon := "⚪️"
204206
if w.latestBlockProposer == res.Address {
205-
icon = "👑"
207+
// Check if this is an empty block
208+
if w.latestBlockTransactions == 0 {
209+
icon = "🟡"
210+
w.metrics.EmptyBlocks.WithLabelValues(block.ChainID, res.Address, res.Label).Inc()
211+
} else {
212+
icon = "👑"
213+
}
206214
w.metrics.ProposedBlocks.WithLabelValues(block.ChainID, res.Address, res.Label).Inc()
207215
w.metrics.ValidatedBlocks.WithLabelValues(block.ChainID, res.Address, res.Label).Inc()
208216
w.metrics.ConsecutiveMissedBlocks.WithLabelValues(block.ChainID, res.Address, res.Label).Set(0)
@@ -235,6 +243,7 @@ func (w *BlockWatcher) handleBlockInfo(ctx context.Context, block *BlockInfo) {
235243

236244
w.latestBlockHeight = block.Height
237245
w.latestBlockProposer = block.ProposerAddress
246+
w.latestBlockTransactions = block.Transactions
238247
}
239248

240249
func (w *BlockWatcher) computeValidatorStatus(block *types.Block) []ValidatorStatus {

pkg/watcher/block_test.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,23 @@ func TestBlockWatcher(t *testing.T) {
103103
{
104104
ChainID: chainID,
105105
Height: 44,
106+
Transactions: 0,
107+
TotalValidators: 2,
108+
SignedValidators: 2,
109+
ProposerAddress: kilnAddress,
110+
ValidatorStatus: []ValidatorStatus{
111+
{
112+
Address: kilnAddress,
113+
Label: kilnName,
114+
Bonded: true,
115+
Signed: true,
116+
Rank: 2,
117+
},
118+
},
119+
},
120+
{
121+
ChainID: chainID,
122+
Height: 45,
106123
Transactions: 7,
107124
TotalValidators: 2,
108125
SignedValidators: 2,
@@ -129,24 +146,27 @@ func TestBlockWatcher(t *testing.T) {
129146
`#41 1/2 validators ✅ Kiln`,
130147
`#42 2/2 validators ✅ Kiln`,
131148
`#43 2/2 validators 👑 Kiln`,
149+
`#44 2/2 validators 🟡 Kiln`,
132150
}, "\n")+"\n",
133151
blockWatcher.writer.(*bytes.Buffer).String(),
134152
)
135153

136-
assert.Equal(t, float64(44), testutil.ToFloat64(blockWatcher.metrics.BlockHeight.WithLabelValues(chainID)))
154+
assert.Equal(t, float64(45), testutil.ToFloat64(blockWatcher.metrics.BlockHeight.WithLabelValues(chainID)))
137155
assert.Equal(t, float64(29), testutil.ToFloat64(blockWatcher.metrics.Transactions.WithLabelValues(chainID)))
138156
assert.Equal(t, float64(2), testutil.ToFloat64(blockWatcher.metrics.ActiveSet.WithLabelValues(chainID)))
139-
assert.Equal(t, float64(5), testutil.ToFloat64(blockWatcher.metrics.TrackedBlocks.WithLabelValues(chainID)))
157+
assert.Equal(t, float64(6), testutil.ToFloat64(blockWatcher.metrics.TrackedBlocks.WithLabelValues(chainID)))
140158
assert.Equal(t, float64(5), testutil.ToFloat64(blockWatcher.metrics.SkippedBlocks.WithLabelValues(chainID)))
141159

142160
assert.Equal(t, 1, testutil.CollectAndCount(blockWatcher.metrics.ValidatedBlocks))
143161
assert.Equal(t, 1, testutil.CollectAndCount(blockWatcher.metrics.MissedBlocks))
144162
assert.Equal(t, 1, testutil.CollectAndCount(blockWatcher.metrics.SoloMissedBlocks))
145163
assert.Equal(t, 1, testutil.CollectAndCount(blockWatcher.metrics.ConsecutiveMissedBlocks))
146-
assert.Equal(t, float64(1), testutil.ToFloat64(blockWatcher.metrics.ProposedBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
147-
assert.Equal(t, float64(3), testutil.ToFloat64(blockWatcher.metrics.ValidatedBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
164+
assert.Equal(t, 1, testutil.CollectAndCount(blockWatcher.metrics.EmptyBlocks))
165+
assert.Equal(t, float64(2), testutil.ToFloat64(blockWatcher.metrics.ProposedBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
166+
assert.Equal(t, float64(4), testutil.ToFloat64(blockWatcher.metrics.ValidatedBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
148167
assert.Equal(t, float64(1), testutil.ToFloat64(blockWatcher.metrics.MissedBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
149168
assert.Equal(t, float64(0), testutil.ToFloat64(blockWatcher.metrics.SoloMissedBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
150169
assert.Equal(t, float64(0), testutil.ToFloat64(blockWatcher.metrics.ConsecutiveMissedBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
170+
assert.Equal(t, float64(1), testutil.ToFloat64(blockWatcher.metrics.EmptyBlocks.WithLabelValues(chainID, kilnAddress, kilnName)))
151171
})
152172
}

0 commit comments

Comments
 (0)