diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9c18e633 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# build folder +build/ +bin/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +*.db +.DS_STORE + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..a72345bb --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": ["bradymholt.pgformatter", "golang.go"], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a73a41b..f11a28f2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1,7 @@ { -} \ No newline at end of file + "pgFormatter.typeCase": "uppercase", + "pgFormatter.tabs": true, + "[sql]": { + "editor.defaultFormatter": "bradymholt.pgformatter" + } +} diff --git a/cmd/replication/main.go b/cmd/replication/main.go new file mode 100644 index 00000000..7a981706 --- /dev/null +++ b/cmd/replication/main.go @@ -0,0 +1,116 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + "sync" + "syscall" + + "github.com/jessevdk/go-flags" + "github.com/xmtp/xmtpd/pkg/registry" + "github.com/xmtp/xmtpd/pkg/server" + "github.com/xmtp/xmtpd/pkg/tracing" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var Commit string + +var options server.Options + +func main() { + if _, err := flags.Parse(&options); err != nil { + if err, ok := err.(*flags.Error); !ok || err.Type != flags.ErrHelp { + fatal("Could not parse options: %s", err) + } + return + } + addEnvVars() + + log, _, err := buildLogger(options) + if err != nil { + fatal("Could not build logger: %s", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + var wg sync.WaitGroup + doneC := make(chan bool, 1) + tracing.GoPanicWrap(ctx, &wg, "main", func(ctx context.Context) { + s, err := server.New(ctx, log, options, registry.NewFixedNodeRegistry([]registry.Node{})) + if err != nil { + log.Fatal("initializing server", zap.Error(err)) + } + s.WaitForShutdown() + doneC <- true + }) + + sigC := make(chan os.Signal, 1) + signal.Notify(sigC, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + select { + case sig := <-sigC: + log.Info("ending on signal", zap.String("signal", sig.String())) + case <-doneC: + } + cancel() + wg.Wait() +} + +func addEnvVars() { + if connStr, hasConnstr := os.LookupEnv("WRITER_DB_CONNECTION_STRING"); hasConnstr { + options.DB.WriterConnectionString = connStr + } + + if connStr, hasConnstr := os.LookupEnv("READER_DB_CONNECTION_STRING"); hasConnstr { + options.DB.WriterConnectionString = connStr + } + + if privKey, hasPrivKey := os.LookupEnv("PRIVATE_KEY"); hasPrivKey { + options.PrivateKeyString = privKey + } +} + +func fatal(msg string, args ...any) { + log.Fatalf(msg, args...) +} + +func buildLogger(options server.Options) (*zap.Logger, *zap.Config, error) { + atom := zap.NewAtomicLevel() + level := zapcore.InfoLevel + err := level.Set(options.LogLevel) + if err != nil { + return nil, nil, err + } + atom.SetLevel(level) + + cfg := zap.Config{ + Encoding: options.LogEncoding, + Level: atom, + OutputPaths: []string{"stdout"}, + ErrorOutputPaths: []string{"stderr"}, + EncoderConfig: zapcore.EncoderConfig{ + MessageKey: "message", + LevelKey: "level", + EncodeLevel: zapcore.CapitalLevelEncoder, + TimeKey: "time", + EncodeTime: zapcore.ISO8601TimeEncoder, + NameKey: "caller", + EncodeCaller: zapcore.ShortCallerEncoder, + }, + } + log, err := cfg.Build() + if err != nil { + return nil, nil, err + } + + log = log.Named("replication") + + return log, &cfg, nil +} diff --git a/dev/.golangci.yaml b/dev/.golangci.yaml new file mode 100644 index 00000000..c6f3c8e6 --- /dev/null +++ b/dev/.golangci.yaml @@ -0,0 +1,14 @@ +linters: + # Disable all linters. + disable-all: true + # Enable specific linter + # https://golangci-lint.run/usage/linters/#enabled-by-default-linters + enable: + - nakedret + - nilerr + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused diff --git a/dev/docker/Dockerfile b/dev/docker/Dockerfile new file mode 100644 index 00000000..d336d374 --- /dev/null +++ b/dev/docker/Dockerfile @@ -0,0 +1,36 @@ +# BUILD IMAGE -------------------------------------------------------- +ARG GO_VERSION=unknown +FROM golang:${GO_VERSION}-alpine as builder + +# Get build tools and required header files +RUN apk add --no-cache build-base + +WORKDIR /app +COPY . . + +# Build the final node binary +ARG GIT_COMMIT=unknown +RUN go build -ldflags="-X 'main.Commit=$GIT_COMMIT'" -o bin/xmtpd cmd/replication/main.go + +# ACTUAL IMAGE ------------------------------------------------------- + +FROM alpine:3.12 + +ARG GIT_COMMIT=unknown + +LABEL maintainer="engineering@xmtp.com" +LABEL source="https://github.com/xmtp/xmtpd" +LABEL description="XMTP Node Software" +LABEL commit=$GIT_COMMIT + +# color, nocolor, json +ENV GOLOG_LOG_FMT=nocolor + +# go-waku default port +EXPOSE 9000 + +COPY --from=builder /app/bin/xmtpd /usr/bin/ + +ENTRYPOINT ["/usr/bin/xmtpd"] +# By default just show help if called without arguments +CMD ["--help"] diff --git a/dev/docker/compose b/dev/docker/compose new file mode 100755 index 00000000..ccc617d3 --- /dev/null +++ b/dev/docker/compose @@ -0,0 +1,5 @@ +#!/bin/bash +set -e +. dev/docker/env + +docker_compose "$@" diff --git a/dev/docker/docker-compose.yml b/dev/docker/docker-compose.yml new file mode 100644 index 00000000..efd3c547 --- /dev/null +++ b/dev/docker/docker-compose.yml @@ -0,0 +1,14 @@ +services: + db: + image: postgres:16 + environment: + POSTGRES_PASSWORD: xmtp + ports: + - 8765:5432 + + prometheus: + image: prom/prometheus + ports: + - 9090:9090 + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml diff --git a/dev/docker/env b/dev/docker/env new file mode 100755 index 00000000..f4b6cead --- /dev/null +++ b/dev/docker/env @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +function docker_compose() { + docker-compose -f dev/docker/docker-compose.yml -p xmtpd "$@" +} diff --git a/dev/docker/prometheus.yml b/dev/docker/prometheus.yml new file mode 100644 index 00000000..2a56bc79 --- /dev/null +++ b/dev/docker/prometheus.yml @@ -0,0 +1,9 @@ +global: + scrape_interval: 10s + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: + - "host.docker.internal:8008" + - "host.docker.internal:8009" diff --git a/dev/docker/up b/dev/docker/up new file mode 100755 index 00000000..726258bb --- /dev/null +++ b/dev/docker/up @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +. dev/docker/env + +docker_compose build +docker_compose up -d --remove-orphans diff --git a/dev/up b/dev/up new file mode 100755 index 00000000..44e85150 --- /dev/null +++ b/dev/up @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +go mod tidy + +if ! which golangci-lint &>/dev/null; then brew install golangci-lint; fi +if ! which shellcheck &>/dev/null; then brew install shellcheck; fi +if ! which mockery &>/dev/null; then brew install mockery; fi + +dev/generate +dev/docker/up diff --git a/go.mod b/go.mod index 768634ea..4af5a748 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,53 @@ module github.com/xmtp/xmtpd -go 1.21.11 +go 1.22 + +require ( + github.com/ethereum/go-ethereum v1.14.7 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 + github.com/jessevdk/go-flags v1.6.1 + github.com/pires/go-proxyproto v0.7.0 + github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f + google.golang.org/grpc v1.65.0 + google.golang.org/protobuf v1.34.2 + gopkg.in/DataDog/dd-trace-go.v1 v1.66.0 +) + +require ( + github.com/DataDog/appsec-internal-go v1.6.0 // indirect + github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 // indirect + github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 // indirect + github.com/DataDog/datadog-go/v5 v5.3.0 // indirect + github.com/DataDog/go-libddwaf/v3 v3.2.1 // indirect + github.com/DataDog/go-tuf v1.0.2-0.5.2 // indirect + github.com/DataDog/sketches-go v1.4.5 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.6.0-alpha.5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/holiman/uint256 v1.3.0 // indirect + github.com/outcaste-io/ristretto v0.2.3 // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect + github.com/tinylib/msgp v1.1.8 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..e8486299 --- /dev/null +++ b/go.sum @@ -0,0 +1,210 @@ +github.com/DataDog/appsec-internal-go v1.6.0 h1:QHvPOv/O0s2fSI/BraZJNpRDAtdlrRm5APJFZNBxjAw= +github.com/DataDog/appsec-internal-go v1.6.0/go.mod h1:pEp8gjfNLtEOmz+iZqC8bXhu0h4k7NUsW/qiQb34k1U= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0 h1:bUMSNsw1iofWiju9yc1f+kBd33E3hMJtq9GuU602Iy8= +github.com/DataDog/datadog-agent/pkg/obfuscate v0.48.0/go.mod h1:HzySONXnAgSmIQfL6gOv9hWprKJkx8CicuXuUbmgWfo= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1 h1:5nE6N3JSs2IG3xzMthNFhXfOaXlrsdgqmJ73lndFf8c= +github.com/DataDog/datadog-agent/pkg/remoteconfig/state v0.48.1/go.mod h1:Vc+snp0Bey4MrrJyiV2tVxxJb6BmLomPvN1RgAvjGaQ= +github.com/DataDog/datadog-go/v5 v5.3.0 h1:2q2qjFOb3RwAZNU+ez27ZVDwErJv5/VpbBPprz7Z+s8= +github.com/DataDog/datadog-go/v5 v5.3.0/go.mod h1:XRDJk1pTc00gm+ZDiBKsjh7oOOtJfYfglVCmFb8C2+Q= +github.com/DataDog/go-libddwaf/v3 v3.2.1 h1:lZPc6UxCOwioHc++nsldKR50FpIrRh1uGnGLuryqnE8= +github.com/DataDog/go-libddwaf/v3 v3.2.1/go.mod h1:AP+7Atb8ftSsrha35wht7+K3R+xuzfVSQhabSO4w6CY= +github.com/DataDog/go-tuf v1.0.2-0.5.2 h1:EeZr937eKAWPxJ26IykAdWA4A0jQXJgkhUjqEI/w7+I= +github.com/DataDog/go-tuf v1.0.2-0.5.2/go.mod h1:zBcq6f654iVqmkk8n2Cx81E1JnNTMOAx1UEO/wZR+P0= +github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4= +github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= +github.com/DataDog/sketches-go v1.4.5 h1:ki7VfeNz7IcNafq7yI/j5U/YCkO3LJiMDtXz9OMQbyE= +github.com/DataDog/sketches-go v1.4.5/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/btcsuite/btcd/btcec/v2 v2.2.1 h1:xP60mv8fvp+0khmrN0zTdPC3cNm24rfeE6lh2R/Yv3E= +github.com/btcsuite/btcd/btcec/v2 v2.2.1/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4 h1:8EXxF+tCLqaVk8AOC29zl2mnhQjwyLxxOTuhUazWRsg= +github.com/eapache/queue/v2 v2.0.0-20230407133247-75960ed334e4/go.mod h1:I5sHm0Y0T1u5YjlyqC5GVArM7aNZRUYtTjmJ8mPJFds= +github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= +github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= +github.com/ethereum/go-ethereum v1.14.7 h1:EHpv3dE8evQmpVEQ/Ne2ahB06n2mQptdwqaMNhAT29g= +github.com/ethereum/go-ethereum v1.14.7/go.mod h1:Mq0biU2jbdmKSZoqOj29017ygFrMnB5/Rifwp980W4o= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= +github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 h1:CWyXh/jylQWp2dtiV33mY4iSSp6yf4lmn+c7/tN+ObI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0/go.mod h1:nCLIt0w3Ept2NwF8ThLmrppXsfT07oC8k0XNDxd8sVU= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/holiman/uint256 v1.3.0 h1:4wdcm/tnd0xXdu7iS3ruNvxkWwrb4aeBQv19ayYn8F4= +github.com/holiman/uint256 v1.3.0/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= +github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= +github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3 h1:4+LEVOB87y175cLJC/mbsgKmoDOjrBldtXvioEy96WY= +github.com/richardartoul/molecule v1.0.1-0.20240531184615-7ca0df43c0b3/go.mod h1:vl5+MqJ1nBINuSsUI2mGgH79UweUT/B5Fy8857PqyyI= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= +github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/mod v0.4.2/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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk= +google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a h1:hqK4+jJZXCU4pW7jsAdGOVFIfLHQeV7LaizZKnZ84HI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/DataDog/dd-trace-go.v1 v1.66.0 h1:025+lLubGtpiDWrRmSOxoFBPIiVRVYRcqP9oLabVOeg= +gopkg.in/DataDog/dd-trace-go.v1 v1.66.0/go.mod h1:Av6AXGmQCQAbDnwNoPiuUz1k3GS8TwQjj+vEdwmEpmM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/gotraceui v0.2.0 h1:dmNsfQ9Vl3GwbiVD7Z8d/osC6WtGGrasyrC2suc4ZIQ= +honnef.co/go/gotraceui v0.2.0/go.mod h1:qHo4/W75cA3bX0QQoSvDjbJa4R8mAyyFjbWAj63XElc= +modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= +modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= +modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= diff --git a/pkg/api/server.go b/pkg/api/server.go new file mode 100644 index 00000000..b60817c7 --- /dev/null +++ b/pkg/api/server.go @@ -0,0 +1,99 @@ +package api + +import ( + "context" + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/pires/go-proxyproto" + "github.com/xmtp/xmtpd/pkg/proto/xmtpv4/message_api" + "github.com/xmtp/xmtpd/pkg/tracing" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/health" + healthgrpc "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/keepalive" +) + +type ApiServer struct { + log *zap.Logger + wg sync.WaitGroup + grpcListener net.Listener + ctx context.Context + service *message_api.ReplicationApiServer +} + +func NewAPIServer(ctx context.Context, log *zap.Logger, port int) (*ApiServer, error) { + grpcListener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) + + if err != nil { + return nil, err + } + s := &ApiServer{ + log: log.Named("api"), + ctx: ctx, + wg: sync.WaitGroup{}, + grpcListener: &proxyproto.Listener{Listener: grpcListener, ReadHeaderTimeout: 10 * time.Second}, + } + + // TODO: Add interceptors + + options := []grpc.ServerOption{ + grpc.Creds(insecure.NewCredentials()), + grpc.KeepaliveParams(keepalive.ServerParameters{ + Time: 5 * time.Minute, + }), + grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ + PermitWithoutStream: true, + MinTime: 15 * time.Second, + }), + // grpc.MaxRecvMsgSize(s.Config.Options.MaxMsgSize), + } + grpcServer := grpc.NewServer(options...) + + healthcheck := health.NewServer() + healthgrpc.RegisterHealthServer(grpcServer, healthcheck) + + replicationService, err := NewReplicationApiService(ctx, log) + if err != nil { + return nil, err + } + s.service = &replicationService + + tracing.GoPanicWrap(s.ctx, &s.wg, "grpc", func(ctx context.Context) { + s.log.Info("serving grpc", zap.String("address", s.grpcListener.Addr().String())) + err := grpcServer.Serve(s.grpcListener) + if err != nil && !isErrUseOfClosedConnection(err) { + s.log.Error("serving grpc", zap.Error(err)) + } + }) + + return s, nil +} + +func (s *ApiServer) Addr() net.Addr { + return s.grpcListener.Addr() +} + +func (s *ApiServer) Close() { + s.log.Info("closing") + + if s.grpcListener != nil { + err := s.grpcListener.Close() + if err != nil { + s.log.Error("closing grpc listener", zap.Error(err)) + } + s.grpcListener = nil + } + + s.wg.Wait() + s.log.Info("closed") +} + +func isErrUseOfClosedConnection(err error) bool { + return strings.Contains(err.Error(), "use of closed network connection") +} diff --git a/pkg/api/service.go b/pkg/api/service.go new file mode 100644 index 00000000..301361e4 --- /dev/null +++ b/pkg/api/service.go @@ -0,0 +1,30 @@ +package api + +import ( + "context" + + "github.com/xmtp/xmtpd/pkg/proto/xmtpv4/message_api" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "go.uber.org/zap" +) + +type Service struct { + message_api.UnimplementedReplicationApiServer + + ctx context.Context + log *zap.Logger +} + +func NewReplicationApiService(ctx context.Context, log *zap.Logger) (message_api.ReplicationApiServer, error) { + return &Service{ctx: ctx, log: log}, nil +} + +func (s *Service) SubscribeEnvelopes(req *message_api.BatchSubscribeEnvelopesRequest, server message_api.ReplicationApi_SubscribeEnvelopesServer) error { + return status.Errorf(codes.Unimplemented, "method SubscribeEnvelopes not implemented") +} + +func (s *Service) QueryEnvelopes(ctx context.Context, req *message_api.QueryEnvelopesRequest) (*message_api.QueryEnvelopesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method QueryEnvelopes not implemented") +} diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go new file mode 100644 index 00000000..56a33cbc --- /dev/null +++ b/pkg/registry/registry.go @@ -0,0 +1,32 @@ +package registry + +type Node struct { + Index int + PublicKey []byte + GrpcAddress string + DisabledBlock *uint64 + // Maybe add mTLS cert here +} + +type NodeRegistry interface { + GetNodes() ([]Node, error) + // OnChange() +} + +// TODO: Delete this or move to a test file + +type FixedNodeRegistry struct { + nodes []Node +} + +func NewFixedNodeRegistry(nodes []Node) *FixedNodeRegistry { + return &FixedNodeRegistry{nodes: nodes} +} + +func (r *FixedNodeRegistry) GetNodes() ([]Node, error) { + return r.nodes, nil +} + +func (f *FixedNodeRegistry) AddNode(node Node) { + f.nodes = append(f.nodes, node) +} diff --git a/pkg/server/options.go b/pkg/server/options.go new file mode 100644 index 00000000..3fce6023 --- /dev/null +++ b/pkg/server/options.go @@ -0,0 +1,27 @@ +package server + +import "time" + +type ApiOptions struct { + Port int `short:"p" long:"port" description:"Port to listen on" default:"5050"` +} + +type DbOptions struct { + ReaderConnectionString string `long:"reader-connection-string" description:"Reader connection string"` + WriterConnectionString string `long:"writer-connection-string" description:"Writer connection string" required:"true"` + ReadTimeout time.Duration `long:"read-timeout" description:"Timeout for reading from the database" default:"10s"` + WriteTimeout time.Duration `long:"write-timeout" description:"Timeout for writing to the database" default:"10s"` + MaxOpenConns int `long:"max-open-conns" description:"Maximum number of open connections" default:"80"` + WaitForDB time.Duration `long:"wait-for" description:"wait for DB on start, up to specified duration"` +} + +type Options struct { + LogLevel string `short:"l" long:"log-level" description:"Define the logging level, supported strings are: DEBUG, INFO, WARN, ERROR, DPANIC, PANIC, FATAL, and their lower-case forms." default:"INFO"` + //nolint:staticcheck + LogEncoding string `long:"log-encoding" description:"Log encoding format. Either console or json" choice:"console" choice:"json" default:"console"` + + PrivateKeyString string `long:"private-key" description:"Private key to use for the node"` + + API ApiOptions `group:"API Options" namespace:"api"` + DB DbOptions `group:"Database Options" namespace:"db"` +} diff --git a/pkg/server/server.go b/pkg/server/server.go new file mode 100644 index 00000000..0e682557 --- /dev/null +++ b/pkg/server/server.go @@ -0,0 +1,76 @@ +package server + +import ( + "context" + "crypto/ecdsa" + "database/sql" + "net" + "os" + "os/signal" + "syscall" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/xmtp/xmtpd/pkg/api" + "github.com/xmtp/xmtpd/pkg/registry" + "go.uber.org/zap" +) + +type Server struct { + options Options + log *zap.Logger + ctx context.Context + cancel context.CancelFunc + apiServer *api.ApiServer + nodeRegistry registry.NodeRegistry + privateKey *ecdsa.PrivateKey + writerDb *sql.DB + // Can add reader DB later if needed +} + +func New(ctx context.Context, log *zap.Logger, options Options, nodeRegistry registry.NodeRegistry) (*Server, error) { + var err error + s := &Server{ + options: options, + log: log, + nodeRegistry: nodeRegistry, + } + s.privateKey, err = parsePrivateKey(options.PrivateKeyString) + if err != nil { + return nil, err + } + // Commenting out the DB stuff until I get the new migrations in + // s.writerDb, err = getWriterDb(options.DB) + // if err != nil { + // return nil, err + // } + + s.ctx, s.cancel = context.WithCancel(ctx) + s.apiServer, err = api.NewAPIServer(ctx, log, options.API.Port) + if err != nil { + return nil, err + } + log.Info("Replication server started", zap.Int("port", options.API.Port)) + return s, nil +} + +func (s *Server) Addr() net.Addr { + return s.apiServer.Addr() +} + +func (s *Server) WaitForShutdown() { + termChannel := make(chan os.Signal, 1) + signal.Notify(termChannel, syscall.SIGINT, syscall.SIGTERM) + <-termChannel + s.Shutdown() +} + +func (s *Server) Shutdown() { + s.cancel() + if s.apiServer != nil { + s.apiServer.Close() + } +} + +func parsePrivateKey(privateKeyString string) (*ecdsa.PrivateKey, error) { + return crypto.HexToECDSA(privateKeyString) +} diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go new file mode 100644 index 00000000..a47a80c0 --- /dev/null +++ b/pkg/server/server_test.go @@ -0,0 +1,45 @@ +package server + +import ( + "context" + "encoding/hex" + "testing" + "time" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" + "github.com/xmtp/xmtpd/pkg/registry" + test "github.com/xmtp/xmtpd/pkg/testing" +) + +const WRITER_DB_CONNECTION_STRING = "postgres://postgres:xmtp@localhost:8765/postgres?sslmode=disable" + +func NewTestServer(t *testing.T, registry registry.NodeRegistry) *Server { + log := test.NewLog(t) + privateKey, err := crypto.GenerateKey() + require.NoError(t, err) + + server, err := New(context.Background(), log, Options{ + PrivateKeyString: hex.EncodeToString(crypto.FromECDSA(privateKey)), + API: ApiOptions{ + Port: 0, + }, + DB: DbOptions{ + WriterConnectionString: WRITER_DB_CONNECTION_STRING, + ReadTimeout: time.Second * 10, + WriteTimeout: time.Second * 10, + MaxOpenConns: 10, + WaitForDB: time.Second * 10, + }, + }, registry) + require.NoError(t, err) + + return server +} + +func TestCreateServer(t *testing.T) { + registry := registry.NewFixedNodeRegistry([]registry.Node{}) + server1 := NewTestServer(t, registry) + server2 := NewTestServer(t, registry) + require.NotEqual(t, server1.Addr(), server2.Addr()) +} diff --git a/pkg/testing/log.go b/pkg/testing/log.go new file mode 100644 index 00000000..cbfd3fd0 --- /dev/null +++ b/pkg/testing/log.go @@ -0,0 +1,25 @@ +package testing + +import ( + "flag" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +var debug bool + +func init() { + flag.BoolVar(&debug, "debug", false, "debug level logging in tests") +} + +func NewLog(t testing.TB) *zap.Logger { + cfg := zap.NewDevelopmentConfig() + if !debug { + cfg.Level = zap.NewAtomicLevelAt(zap.InfoLevel) + } + log, err := cfg.Build() + require.NoError(t, err) + return log +} diff --git a/pkg/testing/random.go b/pkg/testing/random.go new file mode 100644 index 00000000..5d20b68e --- /dev/null +++ b/pkg/testing/random.go @@ -0,0 +1,35 @@ +package testing + +import ( + cryptoRand "crypto/rand" + "math/rand" + "strings" + + "github.com/xmtp/xmtpd/pkg/utils" +) + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func RandomString(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +func RandomStringLower(n int) string { + return strings.ToLower(RandomString(n)) +} + +func RandomBytes(n int) []byte { + b := make([]byte, n) + _, _ = cryptoRand.Read(b) + return b +} + +func RandomInboxId() string { + bytes := RandomBytes(32) + + return utils.HexEncode(bytes) +} diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go new file mode 100644 index 00000000..78d5ca3a --- /dev/null +++ b/pkg/tracing/tracing.go @@ -0,0 +1,113 @@ +// Package tracing enables [Datadog APM tracing](https://docs.datadoghq.com/tracing/) capabilities, +// focusing specifically on [Error Tracking](https://docs.datadoghq.com/tracing/error_tracking/) +package tracing + +import ( + "context" + "os" + "sync" + + "go.uber.org/zap" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" +) + +// reimport relevant bits of the tracer API +var ( + StartSpanFromContext = tracer.StartSpanFromContext + StartSpan = tracer.StartSpan + ChildOf = tracer.ChildOf + WithError = tracer.WithError + ContextWithSpan = tracer.ContextWithSpan +) + +type Span = tracer.Span + +type logger struct{ *zap.Logger } + +func (l logger) Log(msg string) { + l.Error(msg) +} + +// Start boots the datadog tracer, run this once early in the startup sequence. +func Start(version string, l *zap.Logger) { + env := os.Getenv("ENV") + if env == "" { + env = "test" + } + tracer.Start( + tracer.WithEnv(env), + tracer.WithService("xmtpd"), + tracer.WithServiceVersion(version), + tracer.WithLogger(logger{l}), + tracer.WithRuntimeMetrics(), + ) +} + +// Stop shuts down the datadog tracer, defer this right after Start(). +func Stop() { + tracer.Stop() +} + +// Wrap executes action in the context of a span. +// Tags the span with the error if action returns one. +func Wrap(ctx context.Context, log *zap.Logger, operation string, action func(context.Context, *zap.Logger, Span) error) error { + span, ctx := tracer.StartSpanFromContext(ctx, operation) + defer span.Finish() + log = Link(span, log.With(zap.String("span", operation))) + err := action(ctx, log, span) + if err != nil { + span.Finish(WithError(err)) + } + return err +} + +// PanicWrap executes the body guarding for panics. +// If panic happens it emits a span with the error attached. +// This should trigger DD APM's Error Tracking to record the error. +func PanicWrap(ctx context.Context, name string, body func(context.Context)) { + defer func() { + r := recover() + if err, ok := r.(error); ok { + StartSpan("panic: " + name).Finish( + WithError(err), + ) + } + if r != nil { + // Repanic so that we don't suppress normal panic behavior. + panic(r) + } + }() + body(ctx) +} + +// Link connects a logger to a particular trace and span. +// DD APM should provide some additional functionality based on that. +func Link(span tracer.Span, l *zap.Logger) *zap.Logger { + return l.With( + zap.Uint64("dd.trace_id", span.Context().TraceID()), + zap.Uint64("dd.span_id", span.Context().SpanID())) +} + +func SpanType(span Span, typ string) { + span.SetTag(ext.SpanType, typ) +} + +func SpanResource(span Span, resource string) { + span.SetTag(ext.ResourceName, resource) +} + +func SpanTag(span Span, key string, value interface{}) { + span.SetTag(key, value) +} + +// GoPanicWrap extends PanicWrap by running the body in a goroutine and +// synchronizing the goroutine exit with the WaitGroup. +// The body must respect cancellation of the Context. +func GoPanicWrap(ctx context.Context, wg *sync.WaitGroup, name string, body func(context.Context)) { + wg.Add(1) + go func() { + defer wg.Done() + PanicWrap(ctx, name, body) + }() +} diff --git a/pkg/tracing/tracing_test.go b/pkg/tracing/tracing_test.go new file mode 100644 index 00000000..60aa8939 --- /dev/null +++ b/pkg/tracing/tracing_test.go @@ -0,0 +1,40 @@ +package tracing + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func Test_GoPanicWrap_WaitGroup(t *testing.T) { + var wg sync.WaitGroup + ctx, cancel := context.WithCancel(context.Background()) + finished := false + var finishedLock sync.RWMutex + GoPanicWrap(ctx, &wg, "test", func(ctx context.Context) { + <-ctx.Done() + finishedLock.Lock() + defer finishedLock.Unlock() + finished = true + }) + done := false + var doneLock sync.RWMutex + go func() { + wg.Wait() + doneLock.Lock() + defer doneLock.Unlock() + done = true + }() + go func() { time.Sleep(time.Millisecond); cancel() }() + + assert.Eventually(t, func() bool { + finishedLock.RLock() + defer finishedLock.RUnlock() + doneLock.RLock() + defer doneLock.RUnlock() + return finished && done + }, time.Second, 10*time.Millisecond) +} diff --git a/pkg/utils/hex.go b/pkg/utils/hex.go new file mode 100644 index 00000000..02fc8f47 --- /dev/null +++ b/pkg/utils/hex.go @@ -0,0 +1,19 @@ +package utils + +import "encoding/hex" + +func HexEncode(data []byte) string { + return hex.EncodeToString(data) +} + +func HexDecode(s string) ([]byte, error) { + return hex.DecodeString(s) +} + +func AssertHexDecode(s string) []byte { + data, err := HexDecode(s) + if err != nil { + panic(err) + } + return data +} diff --git a/pkg/utils/sleep.go b/pkg/utils/sleep.go new file mode 100644 index 00000000..c4120540 --- /dev/null +++ b/pkg/utils/sleep.go @@ -0,0 +1,10 @@ +package utils + +import ( + "math/rand" + "time" +) + +func RandomSleep(maxTimeMs int) { + time.Sleep(time.Millisecond * time.Duration(rand.Intn(maxTimeMs))) +}