Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
78b54f2
wip: adding tracing
chatton Jan 6, 2026
ae87a59
chore: only having the first tracing decorator
chatton Jan 6, 2026
fdd943b
chore: remove comment
chatton Jan 6, 2026
74d4a5f
deps: adding pin to genproto version
chatton Jan 6, 2026
d796589
chore: ensuring errors reported, adding unit tests
chatton Jan 7, 2026
d52f187
chore: add check to validate basic
chatton Jan 7, 2026
b2b3218
chore: modified default
chatton Jan 7, 2026
41bce54
chore: adding logging of possible error
chatton Jan 7, 2026
fd34073
chore: updated flag test
chatton Jan 7, 2026
fd7425a
chore: bump endpoint to correct port
chatton Jan 7, 2026
f30a577
wip: adding propagating client to engine and eth client
chatton Jan 7, 2026
570509b
chore: simplify construction of rpc opts
chatton Jan 7, 2026
caa0684
chore: address PR feedback
chatton Jan 7, 2026
c154f23
chore: ensure consistent propagation settings
chatton Jan 8, 2026
607f4a3
chore: adding interface for engine client and tracing implementation
chatton Jan 8, 2026
c5d7c41
chore: mrege main
chatton Jan 8, 2026
80e2b17
chore: refactored wiring to use bool
chatton Jan 8, 2026
07a45b6
chore: tidy all fix
chatton Jan 8, 2026
423bb15
chore: fix go mod conflicts
chatton Jan 8, 2026
3e373ce
chore: addressing PR feedback
chatton Jan 8, 2026
ed217d7
chore: adding eth client tracing
chatton Jan 8, 2026
17eb5aa
chore: merge main
chatton Jan 8, 2026
931d2ac
chore: add payload id as attribute
chatton Jan 8, 2026
ee2d158
chore: handle merge conflicts
chatton Jan 8, 2026
8d7fc84
Merge branch 'cian/add-tracing-part-3' into cian/add-tracing-part-4
chatton Jan 8, 2026
32db6c8
chore: merge main
chatton Jan 12, 2026
a3fa329
chore: adding tracing for DA client
chatton Jan 12, 2026
776a2ea
chore: add *.test to gitignore
chatton Jan 12, 2026
d3bdb1e
chore: updated test
chatton Jan 12, 2026
4d1d3ff
chore: adding hex encoded namespace
chatton Jan 13, 2026
ed675a8
feat: add Phase 1 RPC server tracing instrumentation
chatton Jan 13, 2026
b1a827e
chore: make tidy all
chatton Jan 13, 2026
30edc0c
chore: tidy all and add wrappers
chatton Jan 13, 2026
1ff13f3
chore: merge main
chatton Jan 13, 2026
9d726af
chore: merge part 5
chatton Jan 13, 2026
db319d2
chore: removed unused tracer
chatton Jan 13, 2026
2edad3e
chore: remove unused attribute
chatton Jan 13, 2026
b1aaa84
Merge branch 'main' into tracing-part-6
chatton Jan 14, 2026
3ab312c
chore: tidy all
chatton Jan 14, 2026
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
Binary file added da.test
Binary file not shown.
6 changes: 3 additions & 3 deletions execution/grpc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ require (
connectrpc.com/grpcreflect v1.3.0
github.com/evstack/ev-node v1.0.0-beta.11
github.com/evstack/ev-node/core v1.0.0-beta.5
golang.org/x/net v0.49.0
google.golang.org/protobuf v1.36.11
golang.org/x/net v0.47.0
google.golang.org/protobuf v1.36.10
)

require golang.org/x/text v0.33.0 // indirect
require golang.org/x/text v0.31.0 // indirect
12 changes: 6 additions & 6 deletions execution/grpc/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ github.com/evstack/ev-node/core v1.0.0-beta.5 h1:lgxE8XiF3U9pcFgh7xuKMgsOGvLBGRy
github.com/evstack/ev-node/core v1.0.0-beta.5/go.mod h1:n2w/LhYQTPsi48m6lMj16YiIqsaQw6gxwjyJvR+B3sY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
12 changes: 9 additions & 3 deletions pkg/rpc/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,15 @@ func NewServiceHandler(
config config.Config,
bestKnown BestKnownHeightProvider,
) (http.Handler, error) {
storeServer := NewStoreServer(store, headerStore, dataStore, logger)
p2pServer := NewP2PServer(peerManager)
configServer := NewConfigServer(config, proposerAddress, logger)
var storeServer rpc.StoreServiceHandler = NewStoreServer(store, headerStore, dataStore, logger)
var p2pServer rpc.P2PServiceHandler = NewP2PServer(peerManager)
var configServer rpc.ConfigServiceHandler = NewConfigServer(config, proposerAddress, logger)

if config.Instrumentation.IsTracingEnabled() {
storeServer = WithTracingStoreServer(storeServer)
p2pServer = WithTracingP2PServer(p2pServer)
configServer = WithTracingConfigServer(configServer)
}

mux := http.NewServeMux()

Expand Down
277 changes: 277 additions & 0 deletions pkg/rpc/server/tracing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
package server

import (
"context"
"encoding/hex"

"connectrpc.com/connect"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"google.golang.org/protobuf/types/known/emptypb"

pb "github.com/evstack/ev-node/types/pb/evnode/v1"
"github.com/evstack/ev-node/types/pb/evnode/v1/v1connect"
)

// tracedStoreServer decorates a StoreServiceHandler with OpenTelemetry spans.
type tracedStoreServer struct {
inner v1connect.StoreServiceHandler
tracer trace.Tracer
}

// WithTracingStoreServer decorates the provided store service handler with tracing spans.
func WithTracingStoreServer(inner v1connect.StoreServiceHandler) v1connect.StoreServiceHandler {
return &tracedStoreServer{
inner: inner,
tracer: otel.Tracer("ev-node/store-service"),
}
}

func (t *tracedStoreServer) GetBlock(
ctx context.Context,
req *connect.Request[pb.GetBlockRequest],
) (*connect.Response[pb.GetBlockResponse], error) {
var height uint64
switch identifier := req.Msg.Identifier.(type) {
case *pb.GetBlockRequest_Height:
height = identifier.Height
case *pb.GetBlockRequest_Hash:
// for hash-based queries, we'll add the hash as an attribute
}

ctx, span := t.tracer.Start(ctx, "StoreService.GetBlock",
trace.WithAttributes(
attribute.Int64("height", int64(height)),
),
)
defer span.End()

// add hash attribute if hash-based query
if hashIdentifier, ok := req.Msg.Identifier.(*pb.GetBlockRequest_Hash); ok {
span.SetAttributes(attribute.String("hash", hex.EncodeToString(hashIdentifier.Hash)))
}
Comment on lines +36 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for setting span attributes based on the request identifier can be simplified. Currently, you're using a switch to get the height, and then a separate if to get the hash, which involves checking the type of req.Msg.Identifier twice. This can be done more cleanly in a single switch statement that builds the attributes before starting the span. This will make the code more readable and efficient.

	var attrs []attribute.KeyValue
	switch identifier := req.Msg.Identifier.(type) {
	case *pb.GetBlockRequest_Height:
		attrs = append(attrs, attribute.Int64("height", int64(identifier.Height)))
	case *pb.GetBlockRequest_Hash:
		if identifier.Hash != nil {
			attrs = append(attrs, attribute.String("hash", hex.EncodeToString(identifier.Hash)))
		}
	}

	ctx, span := t.tracer.Start(ctx, "StoreService.GetBlock", trace.WithAttributes(attrs...))
	defer span.End()


res, err := t.inner.GetBlock(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

span.SetAttributes(
attribute.Bool("found", res.Msg.Block != nil),
)
if res.Msg.Block != nil && res.Msg.Block.Data != nil {
totalSize := 0
for _, tx := range res.Msg.Block.Data.Txs {
totalSize += len(tx)
}
span.SetAttributes(
attribute.Int("block_size_bytes", totalSize),
attribute.Int("tx_count", len(res.Msg.Block.Data.Txs)),
)
}
return res, nil
}

func (t *tracedStoreServer) GetState(
ctx context.Context,
req *connect.Request[emptypb.Empty],
) (*connect.Response[pb.GetStateResponse], error) {
ctx, span := t.tracer.Start(ctx, "StoreService.GetState")
defer span.End()

res, err := t.inner.GetState(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}
Comment on lines +86 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code for handling errors from the inner service call and updating the span is repeated in almost every traced method in this file. To improve maintainability and reduce boilerplate, consider extracting this logic into a generic helper function.

For example, you could create a function that takes the context, request, the inner function call, and returns the response and error, handling the span creation and error recording internally. Here's a conceptual example:

func traceUnary[Req, Res any](
    tracer trace.Tracer,
    ctx context.Context,
    spanName string,
    req *connect.Request[Req],
    call func(context.Context, *connect.Request[Req]) (*connect.Response[Res], error),
    // ... other params for attributes
) (*connect.Response[Res], error) {
    ctx, span := tracer.Start(ctx, spanName)
    defer span.End()

    res, err := call(ctx, req)
    if err != nil {
        span.RecordError(err)
        span.SetStatus(codes.Error, err.Error())
        return nil, err
    }

    // ... set success attributes

    return res, nil
}

Applying this pattern would make the tracing decorators much more concise.


if res.Msg.State != nil {
span.SetAttributes(
attribute.Int64("height", int64(res.Msg.State.LastBlockHeight)),
attribute.String("app_hash", hex.EncodeToString(res.Msg.State.AppHash)),
attribute.Int64("da_height", int64(res.Msg.State.DaHeight)),
)
}
return res, nil
}

func (t *tracedStoreServer) GetMetadata(
ctx context.Context,
req *connect.Request[pb.GetMetadataRequest],
) (*connect.Response[pb.GetMetadataResponse], error) {
ctx, span := t.tracer.Start(ctx, "StoreService.GetMetadata",
trace.WithAttributes(
attribute.String("key", req.Msg.Key),
),
)
defer span.End()

res, err := t.inner.GetMetadata(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

span.SetAttributes(
attribute.Int("value_size_bytes", len(res.Msg.Value)),
)
return res, nil
}

func (t *tracedStoreServer) GetGenesisDaHeight(
ctx context.Context,
req *connect.Request[emptypb.Empty],
) (*connect.Response[pb.GetGenesisDaHeightResponse], error) {
ctx, span := t.tracer.Start(ctx, "StoreService.GetGenesisDaHeight")
defer span.End()

res, err := t.inner.GetGenesisDaHeight(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

span.SetAttributes(
attribute.Int64("genesis_da_height", int64(res.Msg.Height)),
)
return res, nil
}

func (t *tracedStoreServer) GetP2PStoreInfo(
ctx context.Context,
req *connect.Request[emptypb.Empty],
) (*connect.Response[pb.GetP2PStoreInfoResponse], error) {
ctx, span := t.tracer.Start(ctx, "StoreService.GetP2PStoreInfo")
defer span.End()

res, err := t.inner.GetP2PStoreInfo(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

span.SetAttributes(
attribute.Int("store_count", len(res.Msg.Stores)),
)
return res, nil
}

// tracedP2PServer decorates a P2PServiceHandler with OpenTelemetry spans.
type tracedP2PServer struct {
inner v1connect.P2PServiceHandler
tracer trace.Tracer
}

// WithTracingP2PServer decorates the provided P2P service handler with tracing spans.
func WithTracingP2PServer(inner v1connect.P2PServiceHandler) v1connect.P2PServiceHandler {
return &tracedP2PServer{
inner: inner,
tracer: otel.Tracer("ev-node/p2p-service"),
}
}

func (t *tracedP2PServer) GetPeerInfo(
ctx context.Context,
req *connect.Request[emptypb.Empty],
) (*connect.Response[pb.GetPeerInfoResponse], error) {
ctx, span := t.tracer.Start(ctx, "P2PService.GetPeerInfo")
defer span.End()

res, err := t.inner.GetPeerInfo(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

span.SetAttributes(
attribute.Int("peer_count", len(res.Msg.Peers)),
)
return res, nil
}

func (t *tracedP2PServer) GetNetInfo(
ctx context.Context,
req *connect.Request[emptypb.Empty],
) (*connect.Response[pb.GetNetInfoResponse], error) {
ctx, span := t.tracer.Start(ctx, "P2PService.GetNetInfo")
defer span.End()

res, err := t.inner.GetNetInfo(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

if res.Msg.NetInfo != nil {
span.SetAttributes(
attribute.String("node_id", res.Msg.NetInfo.Id),
attribute.Int("listen_address_count", len(res.Msg.NetInfo.ListenAddresses)),
)
}
return res, nil
}

// tracedConfigServer decorates a ConfigServiceHandler with OpenTelemetry spans.
type tracedConfigServer struct {
inner v1connect.ConfigServiceHandler
tracer trace.Tracer
}

// WithTracingConfigServer decorates the provided config service handler with tracing spans.
func WithTracingConfigServer(inner v1connect.ConfigServiceHandler) v1connect.ConfigServiceHandler {
return &tracedConfigServer{
inner: inner,
tracer: otel.Tracer("ev-node/config-service"),
}
}

func (t *tracedConfigServer) GetNamespace(
ctx context.Context,
req *connect.Request[emptypb.Empty],
) (*connect.Response[pb.GetNamespaceResponse], error) {
ctx, span := t.tracer.Start(ctx, "ConfigService.GetNamespace")
defer span.End()

res, err := t.inner.GetNamespace(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

span.SetAttributes(
attribute.String("header_namespace", res.Msg.HeaderNamespace),
attribute.String("data_namespace", res.Msg.DataNamespace),
)
return res, nil
}

func (t *tracedConfigServer) GetSignerInfo(
ctx context.Context,
req *connect.Request[emptypb.Empty],
) (*connect.Response[pb.GetSignerInfoResponse], error) {
ctx, span := t.tracer.Start(ctx, "ConfigService.GetSignerInfo")
defer span.End()

res, err := t.inner.GetSignerInfo(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}

span.SetAttributes(
attribute.String("signer_address", hex.EncodeToString(res.Msg.Address)),
)
return res, nil
}
Loading
Loading