Skip to content

Commit f2e27bf

Browse files
committed
feat(metrics): add L1 latest height metric and improve L1 monitoring
- Add LatestHeight method to Subscriber interface and EthSubscriber - Add l1_latest_height metric for current L1 blockchain tip - Rename l1_height to l1_latest_verified_l2_block_number for clarity - Add l1_height metric for L1 finalized height This provides comprehensive L1 monitoring with three distinct metrics: - L2 block numbers finalized on L1 - L1 finalized height (confirmed blocks) - L1 latest height (chain tip)
1 parent a729717 commit f2e27bf

File tree

8 files changed

+121
-28
lines changed

8 files changed

+121
-28
lines changed

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
go.uber.org/mock v0.5.0
3838
go.uber.org/zap v1.27.0
3939
golang.org/x/crypto v0.36.0
40+
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
4041
golang.org/x/sync v0.12.0
4142
google.golang.org/grpc v1.71.0
4243
google.golang.org/protobuf v1.36.5
@@ -64,7 +65,6 @@ require (
6465
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
6566
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
6667
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
67-
github.com/dmarkham/enumer v1.5.11 // indirect
6868
github.com/docker/go-units v0.5.0 // indirect
6969
github.com/elastic/gosigar v0.14.3 // indirect
7070
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
@@ -140,7 +140,6 @@ require (
140140
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
141141
github.com/opencontainers/runtime-spec v1.2.0 // indirect
142142
github.com/opentracing/opentracing-go v1.2.0 // indirect
143-
github.com/pascaldekloe/name v1.0.0 // indirect
144143
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
145144
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
146145
github.com/pion/datachannel v1.5.10 // indirect
@@ -193,7 +192,6 @@ require (
193192
go.uber.org/dig v1.18.0 // indirect
194193
go.uber.org/fx v1.23.0 // indirect
195194
go.uber.org/multierr v1.11.0 // indirect
196-
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
197195
golang.org/x/mod v0.23.0 // indirect
198196
golang.org/x/net v0.36.0 // indirect
199197
golang.org/x/sys v0.31.0 // indirect

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U
105105
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
106106
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
107107
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
108-
github.com/dmarkham/enumer v1.5.11 h1:quorLCaEfzjJ23Pf7PB9lyyaHseh91YfTM/sAD/4Mbo=
109-
github.com/dmarkham/enumer v1.5.11/go.mod h1:yixql+kDDQRYqcuBM2n9Vlt7NoT9ixgXhaXry8vmRg8=
110108
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
111109
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
112110
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
@@ -393,8 +391,6 @@ github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/
393391
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
394392
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
395393
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
396-
github.com/pascaldekloe/name v1.0.0 h1:n7LKFgHixETzxpRv2R77YgPUFo85QHGZKrdaYm7eY5U=
397-
github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
398394
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
399395
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
400396
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=

l1/eth_subscriber.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ func (s *EthSubscriber) FinalisedHeight(ctx context.Context) (uint64, error) {
7979
return head.Number.Uint64(), nil
8080
}
8181

82+
func (s *EthSubscriber) LatestHeight(ctx context.Context) (uint64, error) {
83+
reqTimer := time.Now()
84+
height, err := s.ethClient.BlockNumber(ctx)
85+
if err != nil {
86+
return 0, fmt.Errorf("get latest Ethereum block number: %w", err)
87+
}
88+
s.listener.OnL1Call("eth_blockNumber", time.Since(reqTimer))
89+
90+
return height, nil
91+
}
92+
8293
func (s *EthSubscriber) Close() {
8394
s.ethClient.Close()
8495
}

l1/l1.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
//go:generate mockgen -destination=../mocks/mock_subscriber.go -package=mocks github.com/NethermindEth/juno/l1 Subscriber
2121
type Subscriber interface {
2222
FinalisedHeight(ctx context.Context) (uint64, error)
23+
LatestHeight(ctx context.Context) (uint64, error)
2324
WatchLogStateUpdate(ctx context.Context, sink chan<- *contract.StarknetLogStateUpdate) (event.Subscription, error)
2425
ChainID(ctx context.Context) (*big.Int, error)
2526
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)

l1/l1_test.go

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ func newTestL1Client(service service) *rpc.Server {
170170

171171
type service interface {
172172
GetBlockByNumber(ctx context.Context, number string, fullTx bool) (any, error)
173+
BlockNumber(ctx context.Context) (string, error)
173174
}
174175

175176
type testService struct{}
@@ -192,27 +193,57 @@ func (testService) GetBlockByNumber(ctx context.Context, number string, fullTx b
192193
}, nil
193194
}
194195

196+
func (testService) BlockNumber(ctx context.Context) (string, error) {
197+
return "0xc8", nil // 200 in hex
198+
}
199+
195200
type testEmptyService struct{}
196201

197202
func (testEmptyService) GetBlockByNumber(ctx context.Context, number string, fullTx bool) (any, error) {
198203
return nil, nil
199204
}
200205

206+
func (testEmptyService) BlockNumber(ctx context.Context) (string, error) {
207+
return "", errors.New("empty service")
208+
}
209+
201210
type testFaultyService struct{}
202211

203212
func (testFaultyService) GetBlockByNumber(ctx context.Context, number string, fullTx bool) (any, error) {
204213
return uint(0), nil
205214
}
206215

216+
func (testFaultyService) BlockNumber(ctx context.Context) (string, error) {
217+
return "invalid", nil
218+
}
219+
207220
func TestEthSubscriber_FinalisedHeight(t *testing.T) {
208-
tests := map[string]struct {
221+
tests := createEthSubscriberTests(100) // FinalisedHeight returns 100 for testService
222+
testEthSubscriberHeight(t, tests, func(subscriber *l1.EthSubscriber, ctx context.Context) (uint64, error) {
223+
return subscriber.FinalisedHeight(ctx)
224+
})
225+
}
226+
227+
func TestEthSubscriber_LatestHeight(t *testing.T) {
228+
tests := createEthSubscriberTests(200) // LatestHeight returns 200 for testService
229+
testEthSubscriberHeight(t, tests, func(subscriber *l1.EthSubscriber, ctx context.Context) (uint64, error) {
230+
return subscriber.LatestHeight(ctx)
231+
})
232+
}
233+
234+
func createEthSubscriberTests(testServiceExpectedHeight uint64) map[string]struct {
235+
service service
236+
expectedHeight uint64
237+
expectedError bool
238+
} {
239+
return map[string]struct {
209240
service service
210241
expectedHeight uint64
211242
expectedError bool
212243
}{
213244
"testService": {
214245
service: testService{},
215-
expectedHeight: 100,
246+
expectedHeight: testServiceExpectedHeight,
216247
expectedError: false,
217248
},
218249
"testEmptyService": {
@@ -226,34 +257,41 @@ func TestEthSubscriber_FinalisedHeight(t *testing.T) {
226257
expectedError: true,
227258
},
228259
}
260+
}
229261

262+
func testEthSubscriberHeight(t *testing.T, tests map[string]struct {
263+
service service
264+
expectedHeight uint64
265+
expectedError bool
266+
}, heightFunc func(*l1.EthSubscriber, context.Context) (uint64, error),
267+
) {
230268
for name, test := range tests {
231269
t.Run(name, func(t *testing.T) {
232-
startServer := func(addr string, service service) (*rpc.Server, net.Listener) {
233-
srv := newTestL1Client(service)
234-
l, err := net.Listen("tcp", addr)
235-
if err != nil {
236-
t.Fatal("can't listen:", err)
237-
}
238-
go func() {
239-
_ = http.Serve(l, srv.WebsocketHandler([]string{"*"}))
240-
}()
241-
return srv, l
242-
}
243-
244270
ctx, cancel := context.WithTimeout(t.Context(), 12*time.Second)
245271
defer cancel()
246272

247-
server, listener := startServer("127.0.0.1:0", test.service)
273+
server, listener := startTestServer("127.0.0.1:0", test.service)
248274
defer server.Stop()
249275

250276
subscriber, err := l1.NewEthSubscriber("ws://"+listener.Addr().String(), common.Address{})
251277
require.NoError(t, err)
252278
defer subscriber.Close()
253279

254-
height, err := subscriber.FinalisedHeight(ctx)
280+
height, err := heightFunc(subscriber, ctx)
255281
require.Equal(t, test.expectedHeight, height)
256282
require.Equal(t, test.expectedError, err != nil)
257283
})
258284
}
259285
}
286+
287+
func startTestServer(addr string, service service) (*rpc.Server, net.Listener) {
288+
srv := newTestL1Client(service)
289+
l, err := net.Listen("tcp", addr)
290+
if err != nil {
291+
panic(err)
292+
}
293+
go func() {
294+
_ = http.Serve(l, srv.WebsocketHandler([]string{"*"}))
295+
}()
296+
return srv, l
297+
}

mocks/mock_subscriber.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/metrics.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package node
22

33
import (
4+
"context"
45
"math"
56
"strconv"
67
"time"
@@ -17,6 +18,8 @@ import (
1718
"github.com/prometheus/client_golang/prometheus"
1819
)
1920

21+
const l1MetricsTimeout = 5 * time.Second
22+
2023
func makeDBMetrics() db.EventListener {
2124
latencyBuckets := []float64{
2225
25,
@@ -244,19 +247,50 @@ func makeBlockchainMetrics() blockchain.EventListener {
244247
}
245248
}
246249

247-
func makeL1Metrics(bcReader blockchain.Reader) l1.EventListener {
248-
l1Height := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
250+
func makeL1Metrics(bcReader blockchain.Reader, l1Subscriber l1.Subscriber) l1.EventListener {
251+
l2BlockFinalizedOnL1 := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
249252
Namespace: "l1",
250-
Name: "latest_verified_l2_block_number",
251-
Help: "Latest L2 block number that has been finalized on L1",
253+
Name: "l2_finalised_height",
254+
Help: "Latest L2 block number that has been finalised on L1",
252255
}, func() float64 {
253256
l1Head, err := bcReader.L1Head()
254257
if err != nil {
255258
return 0
256259
}
257260
return float64(l1Head.BlockNumber)
258261
})
262+
prometheus.MustRegister(l2BlockFinalizedOnL1)
263+
264+
l1Height := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
265+
Namespace: "l1",
266+
Name: "finalised_height",
267+
Help: "Current L1 (Ethereum) finalised blockchain height",
268+
}, func() float64 {
269+
ctx, cancel := context.WithTimeout(context.Background(), l1MetricsTimeout)
270+
defer cancel()
271+
height, err := l1Subscriber.FinalisedHeight(ctx)
272+
if err != nil {
273+
return 0
274+
}
275+
return float64(height)
276+
})
259277
prometheus.MustRegister(l1Height)
278+
279+
l1LatestHeight := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
280+
Namespace: "l1",
281+
Name: "latest_height",
282+
Help: "Current latest L1 (Ethereum) blockchain height",
283+
}, func() float64 {
284+
ctx, cancel := context.WithTimeout(context.Background(), l1MetricsTimeout)
285+
defer cancel()
286+
height, err := l1Subscriber.LatestHeight(ctx)
287+
if err != nil {
288+
return 0
289+
}
290+
return float64(height)
291+
})
292+
prometheus.MustRegister(l1LatestHeight)
293+
260294
requestLatencies := prometheus.NewHistogramVec(prometheus.HistogramOpts{
261295
Namespace: "l1",
262296
Subsystem: "client",

node/node.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ func newL1Client(ethNode string, includeMetrics bool, chain *blockchain.Blockcha
402402
l1Client := l1.NewClient(ethSubscriber, chain, log)
403403

404404
if includeMetrics {
405-
l1Client.WithEventListener(makeL1Metrics(chain))
405+
l1Client.WithEventListener(makeL1Metrics(chain, ethSubscriber))
406406
}
407407
return l1Client, nil
408408
}

0 commit comments

Comments
 (0)