From dd4613e31052871ce6e7f34900e165ef5af3ae12 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 20 Aug 2025 13:53:45 +0530 Subject: [PATCH 01/16] Add stellar tracer to utils package Signed-off-by: Satyam Zode --- go.mod | 11 +++--- go.sum | 4 +-- utils/tracer/tracer.go | 80 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 utils/tracer/tracer.go diff --git a/go.mod b/go.mod index 75a1b56fe1..c6488ad334 100644 --- a/go.mod +++ b/go.mod @@ -69,6 +69,10 @@ require ( github.com/docker/go-connections v0.5.0 github.com/fsouza/fake-gcs-server v1.49.2 github.com/stellar/stellar-rpc v0.9.6-0.20250130160539-be7702aa01ba + go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 + go.opentelemetry.io/otel/sdk v1.28.0 ) require ( @@ -111,6 +115,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -121,7 +126,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/xattr v0.4.9 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -129,11 +134,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.10.0 // indirect diff --git a/go.sum b/go.sum index cedc199994..d32e9908e7 100644 --- a/go.sum +++ b/go.sum @@ -470,8 +470,8 @@ github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqn github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= diff --git a/utils/tracer/tracer.go b/utils/tracer/tracer.go new file mode 100644 index 0000000000..343784e8c8 --- /dev/null +++ b/utils/tracer/tracer.go @@ -0,0 +1,80 @@ +package tracer + +import ( + "context" + "time" + + "github.com/stellar/go/support/errors" + "github.com/stellar/go/support/log" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +type StellarTracer struct { + OtelEndpoint string + ServiceName string + ServiceVersion string +} + +// NewStellarTracer returns updated stellar tracer object with service and endpoint details +func NewStellarTracer(OtelEndpoint, ServiceName, ServiceVersion string) *StellarTracer { + return &StellarTracer{ + OtelEndpoint: OtelEndpoint, + ServiceName: ServiceName, + ServiceVersion: ServiceVersion, + } +} + +// InitializeTracer sets up traceProvider and returns a function to handle traceprovider +func (stellarTracer *StellarTracer) InitializeTracer() (func(), error) { + log.Infof("Initializing tracer") + headers := map[string]string{ + "content-type": "application/json", + } + + exporter, err := otlptrace.New( + context.Background(), + otlptracehttp.NewClient( + otlptracehttp.WithEndpoint(stellarTracer.OtelEndpoint), + otlptracehttp.WithHeaders(headers), + ), + ) + if err != nil { + return nil, errors.Wrap(err, "Error while creating exporter") + } + + res, err := resource.New( + context.Background(), + resource.WithAttributes( + semconv.ServiceName(stellarTracer.ServiceName), + semconv.ServiceVersion(stellarTracer.ServiceVersion), + ), + ) + + if err != nil { + return nil, errors.Wrap(err, "failed to create exporter") + } + + traceProvider := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exporter), + sdktrace.WithResource(res), + ) + + // Set traceprovider for the otel. + otel.SetTracerProvider(traceProvider) + otel.SetTextMapPropagator(propagation.TraceContext{}) + + return func() { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := traceProvider.Shutdown(ctx); err != nil { + log.Error("Error shutting down tracer provider", err) + } + }, nil +} From d844cd67fd3f741c93136ad5fbb431a552a621fd Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 20 Aug 2025 14:52:46 +0530 Subject: [PATCH 02/16] Add otelchi middleware for the go-chi middle Signed-off-by: Satyam Zode --- go.mod | 21 ++++++++++++--------- services/friendbot/main.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index c6488ad334..0603bd9243 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/spf13/viper v1.17.0 github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 github.com/stellar/throttled v2.2.3-0.20190823235211-89d75816f59d+incompatible - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/tyler-smith/go-bip39 v0.0.0-20180618194314-52158e4697b8 github.com/xdrpp/goxdr v0.1.1 google.golang.org/api v0.183.0 @@ -68,11 +68,12 @@ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fsouza/fake-gcs-server v1.49.2 + github.com/riandyrn/otelchi v0.12.1 github.com/stellar/stellar-rpc v0.9.6-0.20250130160539-be7702aa01ba - go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel v1.34.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 - go.opentelemetry.io/otel/sdk v1.28.0 + go.opentelemetry.io/otel/sdk v1.34.0 ) require ( @@ -104,6 +105,7 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -126,21 +128,22 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/xattr v0.4.9 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.10.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/mod v0.18.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect gopkg.in/djherbis/atime.v1 v1.0.0 // indirect @@ -194,7 +197,7 @@ require ( golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 diff --git a/services/friendbot/main.go b/services/friendbot/main.go index c5933c2fc3..96c054442b 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -7,6 +7,8 @@ import ( "os" "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" + "github.com/riandyrn/otelchi" "github.com/spf13/cobra" "github.com/stellar/go/services/friendbot/internal" @@ -16,6 +18,12 @@ import ( "github.com/stellar/go/support/http" "github.com/stellar/go/support/log" "github.com/stellar/go/support/render/problem" + "github.com/stellar/go/utils/tracer" +) + +const ( + serviceName = "stellar-friendbot" + serviceVersion = "1.0.0" //TODO: Change version ) // Config represents the configuration of a friendbot server @@ -31,6 +39,8 @@ type Config struct { MinionBatchSize int `toml:"minion_batch_size" valid:"optional"` SubmitTxRetriesAllowed int `toml:"submit_tx_retries_allowed" valid:"optional"` UseCloudflareIP bool `toml:"use_cloudflare_ip" valid:"optional"` + OtelEnabled bool `toml:"otel_enabled" valid:"optional"` + OtelEndpoint string `toml:"otel_endpoint" valid:"optional"` } func main() { @@ -63,6 +73,15 @@ func run(cmd *cobra.Command, args []string) { os.Exit(1) } + //Setup and intialize tracer + stellarTracer := tracer.NewStellarTracer(cfg.OtelEndpoint, serviceName, serviceVersion) + tracer, err := stellarTracer.InitializeTracer() + if err != nil { + log.Error("Failed to initialize tracer:", err) + } + log.Infof("Tracer initialized") + defer tracer() + fb, err := initFriendbot(cfg.FriendbotSecret, cfg.NetworkPassphrase, cfg.HorizonURL, cfg.StartingBalance, cfg.NumMinions, cfg.BaseFee, cfg.MinionBatchSize, cfg.SubmitTxRetriesAllowed) if err != nil { @@ -104,6 +123,17 @@ func newMux(cfg Config) *chi.Mux { // middlewares mux.Use(http.XFFMiddleware(http.XFFMiddlewareConfig{BehindCloudflare: cfg.UseCloudflareIP})) mux.Use(http.NewAPIMux(log.DefaultLogger).Middlewares()...) + + // Add OpenTelemetry middleware if enabled + if cfg.OtelEnabled { + // Extract information using middleware + mux.Use(middleware.RequestID) + mux.Use(middleware.RealIP) + mux.Use(middleware.Logger) + mux.Use(middleware.Recoverer) + mux.Use(otelchi.Middleware(serviceName, otelchi.WithChiRoutes(mux))) + } + return mux } From 1710eb80cbd4671f953e274d4c38fa97d7ca290f Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 20 Aug 2025 14:55:28 +0530 Subject: [PATCH 03/16] Update go-chi package version Signed-off-by: Satyam Zode --- go.mod | 2 +- go.sum | 42 ++++++++++++++++++++++---------------- services/friendbot/main.go | 2 +- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 0603bd9243..6fb214ac02 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,7 @@ require ( github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 github.com/fsouza/fake-gcs-server v1.49.2 + github.com/go-chi/chi/v5 v5.1.0 github.com/riandyrn/otelchi v0.12.1 github.com/stellar/stellar-rpc v0.9.6-0.20250130160539-be7702aa01ba go.opentelemetry.io/otel v1.34.0 @@ -105,7 +106,6 @@ require ( github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-chi/chi/v5 v5.1.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/go.sum b/go.sum index d32e9908e7..3e5efc72b0 100644 --- a/go.sum +++ b/go.sum @@ -200,6 +200,8 @@ github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JY github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -468,10 +470,12 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/riandyrn/otelchi v0.12.1 h1:FdRKK3/RgZ/T+d+qTH5Uw3MFx0KwRF38SkdfTMMq/m8= +github.com/riandyrn/otelchi v0.12.1/go.mod h1:weZZeUJURvtCcbWsdb7Y6F8KFZGedJlSrgUjq9VirV8= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= @@ -527,8 +531,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tyler-smith/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:g3yQGZK+G6dfF/mw/SOwsTMzUVkpT4hB8pHxpbTXkKw= @@ -574,22 +578,24 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -642,8 +648,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -758,8 +764,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -834,8 +840,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/services/friendbot/main.go b/services/friendbot/main.go index 96c054442b..c90f9aa4d9 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -6,8 +6,8 @@ import ( stdhttp "net/http" "os" - "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" + "github.com/go-chi/chi/v5" "github.com/riandyrn/otelchi" "github.com/spf13/cobra" From c43ef696a1e294c05acdd8e5241872860112c961 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 20 Aug 2025 15:09:46 +0530 Subject: [PATCH 04/16] Add instrumentation for friendbot handler function Signed-off-by: Satyam Zode --- go.mod | 2 +- .../friendbot/internal/friendbot_handler.go | 55 +++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 6fb214ac02..6afa681736 100644 --- a/go.mod +++ b/go.mod @@ -75,6 +75,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/trace v1.34.0 ) require ( @@ -138,7 +139,6 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.18.0 // indirect diff --git a/services/friendbot/internal/friendbot_handler.go b/services/friendbot/internal/friendbot_handler.go index 9e6f3f8d74..93680c14bb 100644 --- a/services/friendbot/internal/friendbot_handler.go +++ b/services/friendbot/internal/friendbot_handler.go @@ -1,6 +1,7 @@ package internal import ( + "context" "net/http" "net/url" @@ -8,16 +9,51 @@ import ( "github.com/stellar/go/strkey" "github.com/stellar/go/support/render/hal" "github.com/stellar/go/support/render/problem" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +const ( + // Tracer name for friendbot service + tracerName = "stellar-friendbot" ) // FriendbotHandler causes an account at `Address` to be created. type FriendbotHandler struct { Friendbot *Bot + tracer trace.Tracer +} + +// NewFriendbotHandler returns friendbot handler based on the tracing enabled +func NewFriendbotHandler(fb *Bot, tracer bool) *FriendbotHandler { + if tracer { + tracer := otel.Tracer(tracerName) + return &FriendbotHandler{ + Friendbot: fb, + tracer: tracer, + } + } else { + return &FriendbotHandler{ + Friendbot: fb, + } + } } // Handle is a method that implements http.HandlerFunc func (handler *FriendbotHandler) Handle(w http.ResponseWriter, r *http.Request) { - result, err := handler.doHandle(r) + ctx, span := handler.tracer.Start(r.Context(), "friendbot.handle_request") + defer span.End() + + // Add request attributes to span + span.SetAttributes( + attribute.String("http.method", r.Method), + attribute.String("http.url", r.URL.String()), + attribute.String("http.user_agent", r.UserAgent()), + ) + + result, err := handler.doHandle(ctx, r) if err != nil { problem.Render(r.Context(), w, err) return @@ -27,7 +63,9 @@ func (handler *FriendbotHandler) Handle(w http.ResponseWriter, r *http.Request) } // doHandle is just a convenience method that returns the object to be rendered -func (handler *FriendbotHandler) doHandle(r *http.Request) (*horizon.Transaction, error) { +func (handler *FriendbotHandler) doHandle(ctx context.Context, r *http.Request) (*horizon.Transaction, error) { + ctx, span := handler.tracer.Start(ctx, "friendbot.do_handle_request") + defer span.End() err := r.ParseForm() if err != nil { p := problem.BadRequest @@ -35,20 +73,29 @@ func (handler *FriendbotHandler) doHandle(r *http.Request) (*horizon.Transaction return nil, &p } - address, err := handler.loadAddress(r) + address, err := handler.loadAddress(ctx, r) if err != nil { return nil, problem.MakeInvalidFieldProblem("addr", err) } return handler.Friendbot.Pay(address) } -func (handler *FriendbotHandler) loadAddress(r *http.Request) (string, error) { +func (handler *FriendbotHandler) loadAddress(ctx context.Context, r *http.Request) (string, error) { + _, span := handler.tracer.Start(ctx, "friendbot.load_address") + defer span.End() + address := r.Form.Get("addr") + if address == "" { + span.SetStatus(codes.Error, "missing destination account address") + span.SetAttributes(attribute.String("error.type", "missing_parameter")) + } + unescaped, err := url.QueryUnescape(address) if err != nil { return unescaped, err } _, err = strkey.Decode(strkey.VersionByteAccountID, unescaped) + span.SetAttributes(attribute.String("destination.account", address)) return unescaped, err } From 12d1a9117da0e08196e6ba36735056283cfb747a Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 20 Aug 2025 15:56:09 +0530 Subject: [PATCH 05/16] Add insecure flag for exporter Signed-off-by: Satyam Zode --- utils/tracer/tracer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/tracer/tracer.go b/utils/tracer/tracer.go index 343784e8c8..28cc22e11d 100644 --- a/utils/tracer/tracer.go +++ b/utils/tracer/tracer.go @@ -43,6 +43,7 @@ func (stellarTracer *StellarTracer) InitializeTracer() (func(), error) { otlptracehttp.NewClient( otlptracehttp.WithEndpoint(stellarTracer.OtelEndpoint), otlptracehttp.WithHeaders(headers), + otlptracehttp.WithInsecure(), ), ) if err != nil { From 0a6b3b453e24a468582bd143f9cb89530bd9c863 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 20 Aug 2025 18:58:20 +0530 Subject: [PATCH 06/16] Use handler with tracer Signed-off-by: Satyam Zode --- services/friendbot/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/friendbot/main.go b/services/friendbot/main.go index c90f9aa4d9..219c11b213 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -106,8 +106,7 @@ func run(cmd *cobra.Command, args []string) { func initRouter(cfg Config, fb *internal.Bot) *chi.Mux { mux := newMux(cfg) - - handler := &internal.FriendbotHandler{Friendbot: fb} + handler := internal.NewFriendbotHandler(fb, cfg.OtelEnabled) mux.Get("/", handler.Handle) mux.Post("/", handler.Handle) mux.NotFound(stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) { From a1fbcc91e6a12107f862d58c89b3d5cb78564341 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Fri, 22 Aug 2025 18:17:32 +0530 Subject: [PATCH 07/16] Update instrumentation for friendbot Signed-off-by: Satyam Zode --- services/friendbot/friendbot.cfg | 2 +- services/friendbot/internal/friendbot.go | 8 +++-- .../friendbot/internal/friendbot_handler.go | 32 +++++++++--------- services/friendbot/internal/friendbot_test.go | 24 ++++++++------ services/friendbot/internal/minion.go | 33 ++++++++++++++++--- services/friendbot/internal/minion_test.go | 12 ++++--- services/friendbot/main.go | 18 ++++------ 7 files changed, 82 insertions(+), 47 deletions(-) diff --git a/services/friendbot/friendbot.cfg b/services/friendbot/friendbot.cfg index da8f7aefcf..138742041c 100644 --- a/services/friendbot/friendbot.cfg +++ b/services/friendbot/friendbot.cfg @@ -7,4 +7,4 @@ num_minions = 1000 base_fee = 100000 minion_batch_size = 50 submit_tx_retries_allowed = 5 - +otel_endpoint = "alloy.kube-system.svc.cluster.local:4318" diff --git a/services/friendbot/internal/friendbot.go b/services/friendbot/internal/friendbot.go index 1ad9af63d2..b8b75f763e 100644 --- a/services/friendbot/internal/friendbot.go +++ b/services/friendbot/internal/friendbot.go @@ -1,12 +1,16 @@ package internal import ( + "context" "log" "sync" hProtocol "github.com/stellar/go/protocols/horizon" + "go.opentelemetry.io/otel" ) +var botTracer = otel.Tracer("bot_tracer") + // Bot represents the friendbot subsystem and primarily delegates work // to its Minions. type Bot struct { @@ -22,14 +26,14 @@ type SubmitResult struct { } // Pay funds the account at `destAddress`. -func (bot *Bot) Pay(destAddress string) (*hProtocol.Transaction, error) { +func (bot *Bot) Pay(ctx context.Context, destAddress string) (*hProtocol.Transaction, error) { bot.indexMux.Lock() log.Printf("Selecting minion at index %d of max length %d", bot.nextMinionIndex, len(bot.Minions)) minion := bot.Minions[bot.nextMinionIndex] bot.nextMinionIndex = (bot.nextMinionIndex + 1) % len(bot.Minions) bot.indexMux.Unlock() resultChan := make(chan SubmitResult) - go minion.Run(destAddress, resultChan) + go minion.Run(ctx, destAddress, resultChan) maybeSubmitResult := <-resultChan close(resultChan) return maybeSubmitResult.maybeTransactionSuccess, maybeSubmitResult.maybeErr diff --git a/services/friendbot/internal/friendbot_handler.go b/services/friendbot/internal/friendbot_handler.go index 93680c14bb..aa85f5d026 100644 --- a/services/friendbot/internal/friendbot_handler.go +++ b/services/friendbot/internal/friendbot_handler.go @@ -27,23 +27,18 @@ type FriendbotHandler struct { } // NewFriendbotHandler returns friendbot handler based on the tracing enabled -func NewFriendbotHandler(fb *Bot, tracer bool) *FriendbotHandler { - if tracer { - tracer := otel.Tracer(tracerName) - return &FriendbotHandler{ - Friendbot: fb, - tracer: tracer, - } - } else { - return &FriendbotHandler{ - Friendbot: fb, - } +func NewFriendbotHandler(fb *Bot) *FriendbotHandler { + tracer := otel.Tracer(tracerName) + return &FriendbotHandler{ + Friendbot: fb, + tracer: tracer, } + } // Handle is a method that implements http.HandlerFunc func (handler *FriendbotHandler) Handle(w http.ResponseWriter, r *http.Request) { - ctx, span := handler.tracer.Start(r.Context(), "friendbot.handle_request") + ctx, span := handler.tracer.Start(r.Context(), "friendbot.init_http_request") defer span.End() // Add request attributes to span @@ -56,32 +51,37 @@ func (handler *FriendbotHandler) Handle(w http.ResponseWriter, r *http.Request) result, err := handler.doHandle(ctx, r) if err != nil { problem.Render(r.Context(), w, err) + span.SetStatus(codes.Error, err.Error()) return } + span.SetStatus(codes.Ok, codes.Ok.String()) hal.Render(w, *result) } // doHandle is just a convenience method that returns the object to be rendered func (handler *FriendbotHandler) doHandle(ctx context.Context, r *http.Request) (*horizon.Transaction, error) { - ctx, span := handler.tracer.Start(ctx, "friendbot.do_handle_request") + ctx, span := handler.tracer.Start(ctx, "friendbot.parse_http_request") defer span.End() err := r.ParseForm() if err != nil { p := problem.BadRequest p.Detail = "Request parameters are not escaped or incorrectly formatted." + span.SetStatus(codes.Error, err.Error()) return nil, &p } address, err := handler.loadAddress(ctx, r) if err != nil { + span.SetStatus(codes.Error, err.Error()) return nil, problem.MakeInvalidFieldProblem("addr", err) } - return handler.Friendbot.Pay(address) + span.SetStatus(codes.Ok, codes.Ok.String()) + return handler.Friendbot.Pay(ctx, address) } func (handler *FriendbotHandler) loadAddress(ctx context.Context, r *http.Request) (string, error) { - _, span := handler.tracer.Start(ctx, "friendbot.load_address") + _, span := handler.tracer.Start(ctx, "minion.destination_address") defer span.End() address := r.Form.Get("addr") @@ -92,10 +92,12 @@ func (handler *FriendbotHandler) loadAddress(ctx context.Context, r *http.Reques unescaped, err := url.QueryUnescape(address) if err != nil { + span.SetStatus(codes.Error, err.Error()) return unescaped, err } _, err = strkey.Decode(strkey.VersionByteAccountID, unescaped) span.SetAttributes(attribute.String("destination.account", address)) + span.SetStatus(codes.Ok, codes.Ok.String()) return unescaped, err } diff --git a/services/friendbot/internal/friendbot_test.go b/services/friendbot/internal/friendbot_test.go index 6313dd52dc..be145afa2a 100644 --- a/services/friendbot/internal/friendbot_test.go +++ b/services/friendbot/internal/friendbot_test.go @@ -1,6 +1,7 @@ package internal import ( + "context" "sync" "testing" @@ -13,7 +14,8 @@ import ( ) func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { - mockSubmitTransaction := func(minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { + ctx := context.Background() + mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} return &txSuccess, nil @@ -56,7 +58,7 @@ func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { fb := &Bot{Minions: []Minion{minion}} recipientAddress := "GDJIN6W6PLTPKLLM57UW65ZH4BITUXUMYQHIMAZFYXF45PZVAWDBI77Z" - txSuccess, err := fb.Pay(recipientAddress) + txSuccess, err := fb.Pay(ctx, recipientAddress) if !assert.NoError(t, err) { return } @@ -67,12 +69,12 @@ func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { var wg sync.WaitGroup wg.Add(2) go func() { - _, err := fb.Pay(recipientAddress) + _, err := fb.Pay(ctx, recipientAddress) assert.NoError(t, err) wg.Done() }() go func() { - _, err := fb.Pay(recipientAddress) + _, err := fb.Pay(ctx, recipientAddress) assert.NoError(t, err) wg.Done() }() @@ -80,7 +82,8 @@ func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { } func TestFriendbot_Pay_accountExists(t *testing.T) { - mockSubmitTransaction := func(minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { + ctx := context.Background() + mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} return &txSuccess, nil @@ -123,7 +126,7 @@ func TestFriendbot_Pay_accountExists(t *testing.T) { fb := &Bot{Minions: []Minion{minion}} recipientAddress := "GDJIN6W6PLTPKLLM57UW65ZH4BITUXUMYQHIMAZFYXF45PZVAWDBI77Z" - txSuccess, err := fb.Pay(recipientAddress) + txSuccess, err := fb.Pay(ctx, recipientAddress) if !assert.NoError(t, err) { return } @@ -134,12 +137,12 @@ func TestFriendbot_Pay_accountExists(t *testing.T) { var wg sync.WaitGroup wg.Add(2) go func() { - _, err := fb.Pay(recipientAddress) + _, err := fb.Pay(ctx, recipientAddress) assert.NoError(t, err) wg.Done() }() go func() { - _, err := fb.Pay(recipientAddress) + _, err := fb.Pay(ctx, recipientAddress) assert.NoError(t, err) wg.Done() }() @@ -147,7 +150,8 @@ func TestFriendbot_Pay_accountExists(t *testing.T) { } func TestFriendbot_Pay_accountExistsAlreadyFunded(t *testing.T) { - mockSubmitTransaction := func(minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { + ctx := context.Background() + mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} return &txSuccess, nil @@ -190,6 +194,6 @@ func TestFriendbot_Pay_accountExistsAlreadyFunded(t *testing.T) { fb := &Bot{Minions: []Minion{minion}} recipientAddress := "GDJIN6W6PLTPKLLM57UW65ZH4BITUXUMYQHIMAZFYXF45PZVAWDBI77Z" - _, err = fb.Pay(recipientAddress) + _, err = fb.Pay(ctx, recipientAddress) assert.ErrorIs(t, err, ErrAccountFunded) } diff --git a/services/friendbot/internal/minion.go b/services/friendbot/internal/minion.go index 697806a1f9..0889a0880c 100644 --- a/services/friendbot/internal/minion.go +++ b/services/friendbot/internal/minion.go @@ -1,6 +1,7 @@ package internal import ( + "context" "fmt" "github.com/stellar/go/amount" @@ -9,6 +10,8 @@ import ( hProtocol "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/support/errors" "github.com/stellar/go/txnbuild" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" ) const createAccountAlreadyExistXDR = "AAAAAAAAAGT/////AAAAAQAAAAAAAAAA/////AAAAAA=" @@ -29,7 +32,7 @@ type Minion struct { BaseFee int64 // Mockable functions - SubmitTransaction func(minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) + SubmitTransaction func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) CheckSequenceRefresh func(minion *Minion, hclient horizonclient.ClientInterface) error CheckAccountExists func(minion *Minion, hclient horizonclient.ClientInterface, destAddress string) (bool, string, error) @@ -39,7 +42,10 @@ type Minion struct { // Run reads a payment destination address and an output channel. It attempts // to pay that address and submits the result to the channel. -func (minion *Minion) Run(destAddress string, resultChan chan SubmitResult) { +func (minion *Minion) Run(ctx context.Context, destAddress string, resultChan chan SubmitResult) { + ctx, span := botTracer.Start(ctx, "minion.run.pay_minion") + defer span.End() + span.SetAttributes(attribute.String("minion.account_id", minion.Account.AccountID)) err := minion.CheckSequenceRefresh(minion, minion.Horizon) if err != nil { resultChan <- SubmitResult{ @@ -56,6 +62,11 @@ func (minion *Minion) Run(destAddress string, resultChan chan SubmitResult) { } return } + if exists { + span.AddEvent("Minion account exists") + span.SetAttributes(attribute.String("minion.account_exists", minion.Account.AccountID), + attribute.String("minion.account_balance", balance)) + } err = minion.checkBalance(balance) if err != nil { resultChan <- SubmitResult{ @@ -80,15 +91,20 @@ func (minion *Minion) Run(destAddress string, resultChan chan SubmitResult) { } return } - succ, err := minion.SubmitTransaction(minion, minion.Horizon, txStr) + succ, err := minion.SubmitTransaction(ctx, minion, minion.Horizon, txStr) resultChan <- SubmitResult{ maybeTransactionSuccess: succ, maybeErr: errors.Wrapf(err, "submitting tx to minion %x", txHash), } + span.SetAttributes(attribute.Bool("minion.tx_success_status", succ.Successful)) + span.SetStatus(codes.Ok, codes.Ok.String()) } // SubmitTransaction should be passed to the Minion. -func SubmitTransaction(minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { +func SubmitTransaction(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { + _, span := botTracer.Start(ctx, "minion.submit_transaction") + defer span.End() + result, err := hclient.SubmitTransactionXDR(tx) if err != nil { errStr := "submitting tx to horizon" @@ -99,14 +115,23 @@ func SubmitTransaction(minion *Minion, hclient horizonclient.ClientInterface, tx if resErr != nil { errStr += ": error getting horizon error code: " + resErr.Error() } else if resStr == createAccountAlreadyExistXDR { + span.SetStatus(codes.Error, errStr) + span.AddEvent("transaction submission failed") return nil, errors.Wrap(ErrAccountExists, errStr) } else { errStr += ": horizon error string: " + resStr } + span.SetStatus(codes.Error, errStr) + span.AddEvent("transaction submission failed") return nil, errors.New(errStr) } + span.SetStatus(codes.Error, err.Error()) + span.AddEvent("transaction submission failed") return nil, errors.Wrap(err, errStr) } + span.SetAttributes(attribute.String("minion.tx_hash", result.Hash)) + span.AddEvent("transaction submission success") + span.SetStatus(codes.Ok, codes.Ok.String()) return &result, nil } diff --git a/services/friendbot/internal/minion_test.go b/services/friendbot/internal/minion_test.go index 272bb64afb..08bdafdcd6 100644 --- a/services/friendbot/internal/minion_test.go +++ b/services/friendbot/internal/minion_test.go @@ -1,6 +1,7 @@ package internal import ( + "context" "sync" "testing" @@ -16,7 +17,8 @@ import ( // in which Minion.Run() will try to send multiple messages to a channel that gets closed // immediately after receiving one message. func TestMinion_NoChannelErrors(t *testing.T) { - mockSubmitTransaction := func(minion *Minion, hclient horizonclient.ClientInterface, tx string) (txn *hProtocol.Transaction, err error) { + ctx := context.Background() + mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (txn *hProtocol.Transaction, err error) { return txn, nil } @@ -70,7 +72,7 @@ func TestMinion_NoChannelErrors(t *testing.T) { for i := 0; i < numTests; i++ { go func() { - fb.Pay(recipientAddress) + fb.Pay(ctx, recipientAddress) wg.Done() }() } @@ -78,12 +80,14 @@ func TestMinion_NoChannelErrors(t *testing.T) { } func TestMinion_CorrectNumberOfTxSubmissions(t *testing.T) { + ctx := context.Background() + var ( numTxSubmits int mux sync.Mutex ) - mockSubmitTransaction := func(minion *Minion, hclient horizonclient.ClientInterface, tx string) (txn *hProtocol.Transaction, err error) { + mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (txn *hProtocol.Transaction, err error) { mux.Lock() numTxSubmits++ mux.Unlock() @@ -138,7 +142,7 @@ func TestMinion_CorrectNumberOfTxSubmissions(t *testing.T) { for i := 0; i < numTests; i++ { go func() { - fb.Pay(recipientAddress) + fb.Pay(ctx, recipientAddress) wg.Done() }() } diff --git a/services/friendbot/main.go b/services/friendbot/main.go index 219c11b213..7b2e524730 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -39,7 +39,6 @@ type Config struct { MinionBatchSize int `toml:"minion_batch_size" valid:"optional"` SubmitTxRetriesAllowed int `toml:"submit_tx_retries_allowed" valid:"optional"` UseCloudflareIP bool `toml:"use_cloudflare_ip" valid:"optional"` - OtelEnabled bool `toml:"otel_enabled" valid:"optional"` OtelEndpoint string `toml:"otel_endpoint" valid:"optional"` } @@ -106,7 +105,7 @@ func run(cmd *cobra.Command, args []string) { func initRouter(cfg Config, fb *internal.Bot) *chi.Mux { mux := newMux(cfg) - handler := internal.NewFriendbotHandler(fb, cfg.OtelEnabled) + handler := internal.NewFriendbotHandler(fb) mux.Get("/", handler.Handle) mux.Post("/", handler.Handle) mux.NotFound(stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) { @@ -123,15 +122,12 @@ func newMux(cfg Config) *chi.Mux { mux.Use(http.XFFMiddleware(http.XFFMiddlewareConfig{BehindCloudflare: cfg.UseCloudflareIP})) mux.Use(http.NewAPIMux(log.DefaultLogger).Middlewares()...) - // Add OpenTelemetry middleware if enabled - if cfg.OtelEnabled { - // Extract information using middleware - mux.Use(middleware.RequestID) - mux.Use(middleware.RealIP) - mux.Use(middleware.Logger) - mux.Use(middleware.Recoverer) - mux.Use(otelchi.Middleware(serviceName, otelchi.WithChiRoutes(mux))) - } + // Add OpenTelemetry middleware + mux.Use(middleware.RequestID) + mux.Use(middleware.RealIP) + mux.Use(middleware.Logger) + mux.Use(middleware.Recoverer) + mux.Use(otelchi.Middleware(serviceName, otelchi.WithChiRoutes(mux))) return mux } From e233146fefe521f589eb647806c1a1ef7d24c582 Mon Sep 17 00:00:00 2001 From: Satyam Zode <5508956+satyamz@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:47:35 +0530 Subject: [PATCH 08/16] Update utils/tracer/tracer.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- utils/tracer/tracer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/tracer/tracer.go b/utils/tracer/tracer.go index 28cc22e11d..d779001d88 100644 --- a/utils/tracer/tracer.go +++ b/utils/tracer/tracer.go @@ -59,7 +59,7 @@ func (stellarTracer *StellarTracer) InitializeTracer() (func(), error) { ) if err != nil { - return nil, errors.Wrap(err, "failed to create exporter") + return nil, errors.Wrap(err, "Error while creating resource") } traceProvider := sdktrace.NewTracerProvider( From fa8162cbd2cb72ce72bad660f6e98595647b7a9d Mon Sep 17 00:00:00 2001 From: Satyam Zode <5508956+satyamz@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:08:52 +0530 Subject: [PATCH 09/16] Update services/friendbot/main.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- services/friendbot/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/friendbot/main.go b/services/friendbot/main.go index 7b2e524730..ec52edb84f 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -72,7 +72,7 @@ func run(cmd *cobra.Command, args []string) { os.Exit(1) } - //Setup and intialize tracer + //Setup and initialize tracer stellarTracer := tracer.NewStellarTracer(cfg.OtelEndpoint, serviceName, serviceVersion) tracer, err := stellarTracer.InitializeTracer() if err != nil { From 446c59c125a3fdb82f37fa1a01cd7c2b64b0d292 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Thu, 18 Sep 2025 16:13:34 +0530 Subject: [PATCH 10/16] Remove TODO from version Signed-off-by: Satyam Zode --- services/friendbot/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/friendbot/main.go b/services/friendbot/main.go index ec52edb84f..0b0e38f099 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -23,7 +23,7 @@ import ( const ( serviceName = "stellar-friendbot" - serviceVersion = "1.0.0" //TODO: Change version + serviceVersion = "1.0.0" ) // Config represents the configuration of a friendbot server From 139d73c14f0aeaaca9da2cfc5bdde217be33d1fc Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Thu, 18 Sep 2025 17:12:28 +0530 Subject: [PATCH 11/16] Add condition if tx is not success Signed-off-by: Satyam Zode --- services/friendbot/internal/minion.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/friendbot/internal/minion.go b/services/friendbot/internal/minion.go index 0889a0880c..8b606825ca 100644 --- a/services/friendbot/internal/minion.go +++ b/services/friendbot/internal/minion.go @@ -96,8 +96,10 @@ func (minion *Minion) Run(ctx context.Context, destAddress string, resultChan ch maybeTransactionSuccess: succ, maybeErr: errors.Wrapf(err, "submitting tx to minion %x", txHash), } - span.SetAttributes(attribute.Bool("minion.tx_success_status", succ.Successful)) - span.SetStatus(codes.Ok, codes.Ok.String()) + if succ != nil { + span.SetAttributes(attribute.Bool("minion.tx_success_status", succ.Successful)) + span.SetStatus(codes.Ok, codes.Ok.String()) + } } // SubmitTransaction should be passed to the Minion. From 6b95c118a7b1bd1aeff78489394de3ac283ad057 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Thu, 25 Sep 2025 16:52:58 +0530 Subject: [PATCH 12/16] Update friendbot package with otel modifications Signed-off-by: Satyam Zode --- services/friendbot/docker/Dockerfile | 2 +- services/friendbot/friendbot.cfg | 1 - services/friendbot/internal/friendbot.go | 3 -- services/friendbot/internal/friendbot_test.go | 6 ++-- services/friendbot/internal/minion.go | 9 ++++-- services/friendbot/internal/minion_test.go | 4 +-- services/friendbot/main.go | 4 +-- utils/tracer/tracer.go | 30 ++++++++----------- 8 files changed, 27 insertions(+), 32 deletions(-) diff --git a/services/friendbot/docker/Dockerfile b/services/friendbot/docker/Dockerfile index 5dab285e83..8b97d35c83 100644 --- a/services/friendbot/docker/Dockerfile +++ b/services/friendbot/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.23-bullseye as build +FROM golang:1.23-bullseye AS build ADD . /src/friendbot WORKDIR /src/friendbot diff --git a/services/friendbot/friendbot.cfg b/services/friendbot/friendbot.cfg index 138742041c..817f82a40c 100644 --- a/services/friendbot/friendbot.cfg +++ b/services/friendbot/friendbot.cfg @@ -7,4 +7,3 @@ num_minions = 1000 base_fee = 100000 minion_batch_size = 50 submit_tx_retries_allowed = 5 -otel_endpoint = "alloy.kube-system.svc.cluster.local:4318" diff --git a/services/friendbot/internal/friendbot.go b/services/friendbot/internal/friendbot.go index b8b75f763e..bb09580fe0 100644 --- a/services/friendbot/internal/friendbot.go +++ b/services/friendbot/internal/friendbot.go @@ -6,11 +6,8 @@ import ( "sync" hProtocol "github.com/stellar/go/protocols/horizon" - "go.opentelemetry.io/otel" ) -var botTracer = otel.Tracer("bot_tracer") - // Bot represents the friendbot subsystem and primarily delegates work // to its Minions. type Bot struct { diff --git a/services/friendbot/internal/friendbot_test.go b/services/friendbot/internal/friendbot_test.go index be145afa2a..8f411d20e2 100644 --- a/services/friendbot/internal/friendbot_test.go +++ b/services/friendbot/internal/friendbot_test.go @@ -14,7 +14,7 @@ import ( ) func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { - ctx := context.Background() + ctx := t.Context() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} @@ -82,7 +82,7 @@ func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { } func TestFriendbot_Pay_accountExists(t *testing.T) { - ctx := context.Background() + ctx := t.Context() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} @@ -150,7 +150,7 @@ func TestFriendbot_Pay_accountExists(t *testing.T) { } func TestFriendbot_Pay_accountExistsAlreadyFunded(t *testing.T) { - ctx := context.Background() + ctx := t.Context() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} diff --git a/services/friendbot/internal/minion.go b/services/friendbot/internal/minion.go index 8b606825ca..52ebcfbacf 100644 --- a/services/friendbot/internal/minion.go +++ b/services/friendbot/internal/minion.go @@ -10,6 +10,7 @@ import ( hProtocol "github.com/stellar/go/protocols/horizon" "github.com/stellar/go/support/errors" "github.com/stellar/go/txnbuild" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) @@ -20,6 +21,8 @@ var ErrAccountExists error = errors.New(fmt.Sprintf("createAccountAlreadyExist ( var ErrAccountFunded error = errors.New("account already funded to starting balance") +var botTracer = otel.Tracer("bot_tracer") + // Minion contains a Stellar channel account and Go channels to communicate with friendbot. type Minion struct { Account Account @@ -63,9 +66,9 @@ func (minion *Minion) Run(ctx context.Context, destAddress string, resultChan ch return } if exists { - span.AddEvent("Minion account exists") - span.SetAttributes(attribute.String("minion.account_exists", minion.Account.AccountID), - attribute.String("minion.account_balance", balance)) + span.AddEvent("Destination account exists") + span.SetAttributes(attribute.String("destination.account_address", destAddress), + attribute.String("destination.account_balance", balance)) } err = minion.checkBalance(balance) if err != nil { diff --git a/services/friendbot/internal/minion_test.go b/services/friendbot/internal/minion_test.go index 08bdafdcd6..fb9ed7489d 100644 --- a/services/friendbot/internal/minion_test.go +++ b/services/friendbot/internal/minion_test.go @@ -17,7 +17,7 @@ import ( // in which Minion.Run() will try to send multiple messages to a channel that gets closed // immediately after receiving one message. func TestMinion_NoChannelErrors(t *testing.T) { - ctx := context.Background() + ctx := t.Context() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (txn *hProtocol.Transaction, err error) { return txn, nil } @@ -80,7 +80,7 @@ func TestMinion_NoChannelErrors(t *testing.T) { } func TestMinion_CorrectNumberOfTxSubmissions(t *testing.T) { - ctx := context.Background() + ctx := t.Context() var ( numTxSubmits int diff --git a/services/friendbot/main.go b/services/friendbot/main.go index 0b0e38f099..9b2979ed9f 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -40,6 +40,7 @@ type Config struct { SubmitTxRetriesAllowed int `toml:"submit_tx_retries_allowed" valid:"optional"` UseCloudflareIP bool `toml:"use_cloudflare_ip" valid:"optional"` OtelEndpoint string `toml:"otel_endpoint" valid:"optional"` + OtelEnabled bool `toml:"otel_enabled" valid:"optional"` } func main() { @@ -73,8 +74,7 @@ func run(cmd *cobra.Command, args []string) { } //Setup and initialize tracer - stellarTracer := tracer.NewStellarTracer(cfg.OtelEndpoint, serviceName, serviceVersion) - tracer, err := stellarTracer.InitializeTracer() + tracer, err := tracer.InitializeTracer(cfg.OtelEnabled, cfg.OtelEndpoint, serviceName, serviceVersion) if err != nil { log.Error("Failed to initialize tracer:", err) } diff --git a/utils/tracer/tracer.go b/utils/tracer/tracer.go index d779001d88..78e2450978 100644 --- a/utils/tracer/tracer.go +++ b/utils/tracer/tracer.go @@ -14,25 +14,21 @@ import ( "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" + "go.opentelemetry.io/otel/trace/noop" ) -type StellarTracer struct { - OtelEndpoint string - ServiceName string - ServiceVersion string -} +// InitializeTracer sets up traceProvider and returns a function to shutdown traceprovider +func InitializeTracer(enabled bool, OtelEndpoint, ServiceName, ServiceVersion string) (func(), error) { + if !enabled { + log.Info("Tracing disabled - using no-op tracer") + // Set no-op tracer provider + otel.SetTracerProvider(noop.NewTracerProvider()) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator()) -// NewStellarTracer returns updated stellar tracer object with service and endpoint details -func NewStellarTracer(OtelEndpoint, ServiceName, ServiceVersion string) *StellarTracer { - return &StellarTracer{ - OtelEndpoint: OtelEndpoint, - ServiceName: ServiceName, - ServiceVersion: ServiceVersion, + // Return a no-op shutdown function + return func() {}, nil } -} -// InitializeTracer sets up traceProvider and returns a function to handle traceprovider -func (stellarTracer *StellarTracer) InitializeTracer() (func(), error) { log.Infof("Initializing tracer") headers := map[string]string{ "content-type": "application/json", @@ -41,7 +37,7 @@ func (stellarTracer *StellarTracer) InitializeTracer() (func(), error) { exporter, err := otlptrace.New( context.Background(), otlptracehttp.NewClient( - otlptracehttp.WithEndpoint(stellarTracer.OtelEndpoint), + otlptracehttp.WithEndpoint(OtelEndpoint), otlptracehttp.WithHeaders(headers), otlptracehttp.WithInsecure(), ), @@ -53,8 +49,8 @@ func (stellarTracer *StellarTracer) InitializeTracer() (func(), error) { res, err := resource.New( context.Background(), resource.WithAttributes( - semconv.ServiceName(stellarTracer.ServiceName), - semconv.ServiceVersion(stellarTracer.ServiceVersion), + semconv.ServiceName(ServiceName), + semconv.ServiceVersion(ServiceVersion), ), ) From 5eefe39f9c597dcbe1891b51aa5fcb74df747fc7 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Thu, 25 Sep 2025 17:09:27 +0530 Subject: [PATCH 13/16] Remove t.context until go 1.24 Signed-off-by: Satyam Zode --- services/friendbot/internal/friendbot_test.go | 7 +++++-- services/friendbot/internal/minion_test.go | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/services/friendbot/internal/friendbot_test.go b/services/friendbot/internal/friendbot_test.go index 8f411d20e2..cf9f55a300 100644 --- a/services/friendbot/internal/friendbot_test.go +++ b/services/friendbot/internal/friendbot_test.go @@ -4,6 +4,7 @@ import ( "context" "sync" "testing" + "time" "github.com/stellar/go/txnbuild" @@ -14,7 +15,8 @@ import ( ) func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { - ctx := t.Context() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} @@ -82,7 +84,8 @@ func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { } func TestFriendbot_Pay_accountExists(t *testing.T) { - ctx := t.Context() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} diff --git a/services/friendbot/internal/minion_test.go b/services/friendbot/internal/minion_test.go index fb9ed7489d..bc21de896f 100644 --- a/services/friendbot/internal/minion_test.go +++ b/services/friendbot/internal/minion_test.go @@ -4,6 +4,7 @@ import ( "context" "sync" "testing" + "time" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" @@ -17,7 +18,9 @@ import ( // in which Minion.Run() will try to send multiple messages to a channel that gets closed // immediately after receiving one message. func TestMinion_NoChannelErrors(t *testing.T) { - ctx := t.Context() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (txn *hProtocol.Transaction, err error) { return txn, nil } @@ -80,7 +83,8 @@ func TestMinion_NoChannelErrors(t *testing.T) { } func TestMinion_CorrectNumberOfTxSubmissions(t *testing.T) { - ctx := t.Context() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() var ( numTxSubmits int From efefde8f2892e0c84ab6321f1208feaf1321404e Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Thu, 25 Sep 2025 17:13:18 +0530 Subject: [PATCH 14/16] Remove t.context from tests Signed-off-by: Satyam Zode --- services/friendbot/internal/friendbot_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/friendbot/internal/friendbot_test.go b/services/friendbot/internal/friendbot_test.go index cf9f55a300..ac5adef170 100644 --- a/services/friendbot/internal/friendbot_test.go +++ b/services/friendbot/internal/friendbot_test.go @@ -153,7 +153,8 @@ func TestFriendbot_Pay_accountExists(t *testing.T) { } func TestFriendbot_Pay_accountExistsAlreadyFunded(t *testing.T) { - ctx := t.Context() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} From 8eb22922643bcf0428397873a580bb5ad59ca835 Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 1 Oct 2025 20:48:44 +0530 Subject: [PATCH 15/16] Update context handling in tests and remove mux params Signed-off-by: Satyam Zode --- services/friendbot/internal/friendbot_test.go | 11 ++++------- services/friendbot/internal/minion_test.go | 7 ++----- services/friendbot/main.go | 7 ------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/services/friendbot/internal/friendbot_test.go b/services/friendbot/internal/friendbot_test.go index ac5adef170..172aa4f81e 100644 --- a/services/friendbot/internal/friendbot_test.go +++ b/services/friendbot/internal/friendbot_test.go @@ -4,7 +4,6 @@ import ( "context" "sync" "testing" - "time" "github.com/stellar/go/txnbuild" @@ -15,8 +14,8 @@ import ( ) func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + ctx := context.Background() + mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} @@ -84,8 +83,7 @@ func TestFriendbot_Pay_accountDoesNotExist(t *testing.T) { } func TestFriendbot_Pay_accountExists(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + ctx := context.Background() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} @@ -153,8 +151,7 @@ func TestFriendbot_Pay_accountExists(t *testing.T) { } func TestFriendbot_Pay_accountExistsAlreadyFunded(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + ctx := context.Background() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (*hProtocol.Transaction, error) { // Instead of submitting the tx, we emulate a success. txSuccess := hProtocol.Transaction{EnvelopeXdr: tx, Successful: true} diff --git a/services/friendbot/internal/minion_test.go b/services/friendbot/internal/minion_test.go index bc21de896f..9786c735bb 100644 --- a/services/friendbot/internal/minion_test.go +++ b/services/friendbot/internal/minion_test.go @@ -4,7 +4,6 @@ import ( "context" "sync" "testing" - "time" "github.com/stellar/go/clients/horizonclient" "github.com/stellar/go/keypair" @@ -18,8 +17,7 @@ import ( // in which Minion.Run() will try to send multiple messages to a channel that gets closed // immediately after receiving one message. func TestMinion_NoChannelErrors(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + ctx := context.Background() mockSubmitTransaction := func(ctx context.Context, minion *Minion, hclient horizonclient.ClientInterface, tx string) (txn *hProtocol.Transaction, err error) { return txn, nil @@ -83,8 +81,7 @@ func TestMinion_NoChannelErrors(t *testing.T) { } func TestMinion_CorrectNumberOfTxSubmissions(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() + ctx := context.Background() var ( numTxSubmits int diff --git a/services/friendbot/main.go b/services/friendbot/main.go index 9b2979ed9f..468a922f33 100644 --- a/services/friendbot/main.go +++ b/services/friendbot/main.go @@ -6,7 +6,6 @@ import ( stdhttp "net/http" "os" - "github.com/go-chi/chi/middleware" "github.com/go-chi/chi/v5" "github.com/riandyrn/otelchi" "github.com/spf13/cobra" @@ -121,12 +120,6 @@ func newMux(cfg Config) *chi.Mux { // middlewares mux.Use(http.XFFMiddleware(http.XFFMiddlewareConfig{BehindCloudflare: cfg.UseCloudflareIP})) mux.Use(http.NewAPIMux(log.DefaultLogger).Middlewares()...) - - // Add OpenTelemetry middleware - mux.Use(middleware.RequestID) - mux.Use(middleware.RealIP) - mux.Use(middleware.Logger) - mux.Use(middleware.Recoverer) mux.Use(otelchi.Middleware(serviceName, otelchi.WithChiRoutes(mux))) return mux From 9194d20859599936e3dc77359dc6c83a02033d7f Mon Sep 17 00:00:00 2001 From: Satyam Zode Date: Wed, 1 Oct 2025 20:50:20 +0530 Subject: [PATCH 16/16] Update tracer name in friendbot minion Signed-off-by: Satyam Zode --- services/friendbot/internal/minion.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/friendbot/internal/minion.go b/services/friendbot/internal/minion.go index 52ebcfbacf..58e80c259a 100644 --- a/services/friendbot/internal/minion.go +++ b/services/friendbot/internal/minion.go @@ -21,7 +21,7 @@ var ErrAccountExists error = errors.New(fmt.Sprintf("createAccountAlreadyExist ( var ErrAccountFunded error = errors.New("account already funded to starting balance") -var botTracer = otel.Tracer("bot_tracer") +var botTracer = otel.Tracer("stellar_friendbot_minion") // Minion contains a Stellar channel account and Go channels to communicate with friendbot. type Minion struct {