diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index 98f187098..80f160231 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -57,7 +57,7 @@ runs: # caches from the same environment. # - The unique part is the `hashFiles('**/go.sum')`, which calculates a # hash (a fingerprint) of the go.sum file. - key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-${{ hashFiles('go.sum') }} # The restore-keys provides a list of fallback keys. If no cache # matches the key exactly, the action will look for a cache where the @@ -80,7 +80,7 @@ runs: # Just the module download cache. path: | ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-no-build-cache-${{ hashFiles('**/go.sum') }} + key: ${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-no-build-cache-${{ hashFiles('go.sum') }} restore-keys: | ${{ runner.os }}-go-${{ inputs.go-version }}-${{ inputs.key-prefix }}-no-build-cache- diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 37bd32ace..9f8222749 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -149,8 +149,8 @@ jobs: matrix: unit_type: - unit-cover - - unit tags="test_db_sqlite" - - unit tags="test_db_postgres" + - unit tags="test_sqlite" + - unit tags="test_postgres" - unit-race steps: @@ -173,7 +173,9 @@ jobs: key-prefix: unit-test - name: Run ${{ matrix.unit_type }} - run: make ${{ matrix.unit_type }} + # Run with sudo as harness uses user 0:0 which needs extra permissions + # on the github runner in order for docker mounts to work correctly. + run: sudo env "PATH=$PATH" "GOPATH=$GOPATH" make ${{ matrix.unit_type }} - name: Clean coverage run: grep -Ev '(\.pb\.go|\.pb\.json\.go|\.pb\.gw\.go|db/sqlc/)' coverage.txt > coverage-norpc.txt diff --git a/.gitignore b/.gitignore index 5c6464497..709a74c13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /arkcli /arkd +**/test-artifacts merge-sql-schemas diff --git a/.golangci.yml b/.golangci.yml index 3a9101697..fe120c9e0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,11 +5,8 @@ run: timeout: 10m build-tags: - - dev - - kvdb_postgres - - kvdb_sqlite - - test_db_postgres - - test_db_sqlite + - test_postgres + - test_sqlite linters-settings: custom: @@ -91,6 +88,8 @@ linters-settings: replace-allow-list: # Allow forked migrate package. - github.com/golang-migrate/migrate/v4 + # Allow forked protobuf for hex display support. + - google.golang.org/protobuf linters: @@ -239,6 +238,12 @@ issues: - forbidigo - godot + # Test harness has some special rules. + - path: harness + linters: + - forbidigo + - tagliatelle + # Allow fmt.Printf() in commands. - path: cmd/.*/main\.go linters: diff --git a/Makefile b/Makefile index 799714473..852a9c06b 100644 --- a/Makefile +++ b/Makefile @@ -265,5 +265,5 @@ help: #? Show this help message @echo "Examples:" @echo " make build" @echo " make rpc" - @echo " make unit tags=\"test_db_postgres\"" + @echo " make unit tags=\"test_postgres\"" @echo " make migrate-create patchname=add_users_table" diff --git a/db/test_postgres.go b/db/test_postgres.go index f80ad737a..d6c34c8f0 100644 --- a/db/test_postgres.go +++ b/db/test_postgres.go @@ -1,4 +1,4 @@ -//go:build test_db_postgres +//go:build test_postgres package db diff --git a/db/test_sqlite.go b/db/test_sqlite.go index 58ec29335..6657f4028 100644 --- a/db/test_sqlite.go +++ b/db/test_sqlite.go @@ -1,4 +1,4 @@ -//go:build !test_db_postgres +//go:build !test_postgres package db diff --git a/go.mod b/go.mod index af3f8f61c..d8daf4a26 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,18 @@ go 1.25 replace github.com/golang-migrate/migrate/v4 => github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 require ( + github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 github.com/btcsuite/btcd/btcutil v1.1.5 - github.com/btcsuite/btclog/v2 v2.0.0 + github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b github.com/golang-migrate/migrate/v4 v4.17.0 github.com/jackc/pgconn v1.14.3 github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 github.com/jackc/pgx/v5 v5.7.6 github.com/jessevdk/go-flags v1.4.0 github.com/lib/pq v1.10.9 + github.com/lightninglabs/lndclient v0.20.0-3 + github.com/lightninglabs/taproot-assets/taprpc v1.0.9 + github.com/lightningnetwork/lnd v0.20.0-beta.rc1 github.com/lightningnetwork/lnd/fn v1.2.3 github.com/ory/dockertest/v3 v3.12.0 github.com/stretchr/testify v1.11.1 @@ -21,32 +25,62 @@ require ( golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 google.golang.org/grpc v1.64.1 google.golang.org/protobuf v1.35.1 + gopkg.in/macaroon.v2 v2.1.0 modernc.org/sqlite v1.40.0 ) require ( - dario.cat/mergo v1.0.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/aead/siphash v1.0.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect + github.com/btcsuite/btcwallet v0.16.17 // indirect + github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 // indirect + github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 // indirect + github.com/btcsuite/btcwallet/walletdb v1.5.1 // indirect + github.com/btcsuite/btcwallet/wtxmgr v1.5.6 // indirect + github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect + github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect + github.com/btcsuite/winsvc v1.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.4.5 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect - github.com/docker/cli v27.4.1+incompatible // indirect - github.com/docker/docker v27.2.0+incompatible // indirect + github.com/decred/dcrd/lru v1.1.2 // indirect + github.com/docker/cli v28.1.1+incompatible // indirect + github.com/docker/docker v28.1.1+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/fergusstrange/embedded-postgres v1.25.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.0.1 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -54,35 +88,102 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.14.4 // indirect + github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/jrick/logrotate v1.1.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/juju/loggo v1.0.0 // indirect + github.com/kkdai/bstream v1.0.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect + github.com/lightninglabs/neutrino v0.16.1 // indirect + github.com/lightninglabs/neutrino/cache v1.1.2 // indirect + github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 // indirect + github.com/lightningnetwork/lnd/clock v1.1.1 // indirect + github.com/lightningnetwork/lnd/fn/v2 v2.0.8 // indirect + github.com/lightningnetwork/lnd/healthcheck v1.2.6 // indirect + github.com/lightningnetwork/lnd/kvdb v1.4.16 // indirect + github.com/lightningnetwork/lnd/queue v1.1.1 // indirect + github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117 // indirect + github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect + github.com/lightningnetwork/lnd/tlv v1.3.2 // indirect + github.com/lightningnetwork/lnd/tor v1.1.6 // indirect + github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/miekg/dns v1.1.43 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/user v0.3.0 // indirect github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runc v1.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.11.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rogpeppe/fastuuid v1.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/soheilhy/cmux v0.1.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + go.etcd.io/bbolt v1.4.3 // indirect + go.etcd.io/etcd/api/v3 v3.5.12 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect + go.etcd.io/etcd/client/v2 v2.305.12 // indirect + go.etcd.io/etcd/client/v3 v3.5.12 // indirect + go.etcd.io/etcd/pkg/v3 v3.5.12 // indirect + go.etcd.io/etcd/raft/v3 v3.5.12 // indirect + go.etcd.io/etcd/server/v3 v3.5.12 // 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/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.17.0 // indirect golang.org/x/crypto v0.37.0 // indirect - golang.org/x/net v0.38.0 // indirect + golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect + gopkg.in/errgo.v1 v1.0.1 // indirect + gopkg.in/macaroon-bakery.v2 v2.1.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect + pgregory.net/rapid v1.2.0 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect ) + +replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display diff --git a/go.sum b/go.sum index 6e0c89694..1f4039882 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,39 @@ -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53 h1:XOZ/wRGHkKv0AqxfDks5IkzaQ1Ge6fq322ZOOG5VIkU= -github.com/btcsuite/btcd v0.24.3-0.20240921052913-67b8efd3ba53/go.mod h1:zHK7t7sw8XbsCkD64WePHE3r3k9/XoGAcf6mXV14c64= +github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 h1:8n9k3I7e8DkpdQ5YAP4j8ly/LSsbe6qX9vmVbrUGvVw= +github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= @@ -22,6 +42,8 @@ github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9Ur github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg= +github.com/btcsuite/btcd/btcutil/psbt v1.1.8/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -29,23 +51,55 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0= github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= -github.com/btcsuite/btclog/v2 v2.0.0 h1:ZfOBItEeLWfU0voi88K72j8vtxP4/dHhxRFf2bxZkVo= -github.com/btcsuite/btclog/v2 v2.0.0/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcwallet v0.16.17 h1:1N6lHznRdcjDopBvcofxaIHknArkJ/EcVKgLKfGL4Dg= +github.com/btcsuite/btcwallet v0.16.17/go.mod h1:YO+W745BAH8n/Rpgj68QsLR6eLlgM4W2do4RejT0buo= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2/go.mod h1:4v+grppsDpVn91SJv+mZT7B8hEV4nSmpREM4I8Uohws= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 h1:93o5Xz9dYepBP4RMFUc9RGIFXwqP2volSWRkYJFrNtI= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw= +github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI= +github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= +github.com/btcsuite/btcwallet/wtxmgr v1.5.6 h1:Zwvr/rrJYdOLqdBCSr4eICEstnEA+NBUvjIWLkrXaYI= +github.com/btcsuite/btcwallet/wtxmgr v1.5.6/go.mod h1:lzVbDkk/jRao2ib5kge46aLZW1yFc8RFNycdYpnsmZA= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -53,152 +107,396 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +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.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/dcrd/lru v1.1.2 h1:KdCzlkxppuoIDGEvCGah1fZRicrDH36IipvlB1ROkFY= +github.com/decred/dcrd/lru v1.1.2/go.mod h1:gEdCVgXs1/YoBvFWt7Scgknbhwik3FgVSzlnCcXL2N8= github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= -github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= -github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= +github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= +github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0= +github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw= +github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= +github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= +github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= -github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ= github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= +github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/jrick/logrotate v1.1.2 h1:6ePk462NCX7TfKtNp5JJ7MbA2YIslkpfgP03TlTYMN0= +github.com/jrick/logrotate v1.1.2/go.mod h1:f9tdWggSVK3iqavGpyvegq5IhNois7KXmasU6/N96OQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/juju/clock v1.1.1 h1:NvgHG9DQmOpBevgt6gzkyimdWBooLXDy1cQn89qJzBI= +github.com/juju/clock v1.1.1/go.mod h1:HIBvJ8kiV/n7UHwKuCkdYL4l/MDECztHR2sAvWDxxf0= +github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM= +github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8= +github.com/juju/loggo v1.0.0 h1:Y6ZMQOGR9Aj3BGkiWx7HBbIx6zNwNkxhVNOHU2i1bl0= +github.com/juju/loggo v1.0.0/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg= +github.com/juju/testing v1.0.2 h1:OR90RqCd9CJONxXamZAjLknpZdtqDyxqW8IwCbgw3i4= +github.com/juju/testing v1.0.2/go.mod h1:h3Vd2rzB57KrdsBEy6R7bmSKPzP76BnNavt7i8PerwQ= +github.com/juju/utils/v3 v3.2.0 h1:RkEgTHbrPoOqTuOSaob52B1gLkpeMLd5fZj/4kFzb1k= +github.com/juju/utils/v3 v3.2.0/go.mod h1:nAj3sHtdYfAkvnkqttTy3Xzm2HzkD9Hfgnc+upOW2Z8= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= +github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/lndclient v0.20.0-3 h1:hL37EJHYupIEQRSgogA6AFbFQ/6l+A+DuqJMGZ8vm8s= +github.com/lightninglabs/lndclient v0.20.0-3/go.mod h1:GMqsKYJ/CkKY2DgPJfcvvHf98qiwr4fpKir8oAN7TiI= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2 h1:eFjp1dIB2BhhQp/THKrjLdlYuPugO9UU4kDqu91OX/Q= github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= +github.com/lightninglabs/neutrino v0.16.1 h1:5Kz4ToxncEVkpKC6fwUjXKtFKJhuxlG3sBB3MdJTJjs= +github.com/lightninglabs/neutrino v0.16.1/go.mod h1:L+5UAccpUdyM7yDgmQySgixf7xmwBgJtOfs/IP26jCs= +github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= +github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= +github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS/00EiEg0qp0FhehxnQfk3vv8U6Xt3nN+rTY= +github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +github.com/lightninglabs/taproot-assets/taprpc v1.0.9 h1:Rr/B/6QURBwflA6LNFWuQCBOP5Xh3WbHQf/dNf8tA98= +github.com/lightninglabs/taproot-assets/taprpc v1.0.9/go.mod h1:c8gTEcKEUoUPVChgZNwqTL1hss7UWa5FDeObr8WBzQk= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9 h1:6D3LrdagJweLLdFm1JNodZsBk6iU4TTsBBFLQ4yiXfI= +github.com/lightningnetwork/lightning-onion v1.2.1-0.20240815225420-8b40adf04ab9/go.mod h1:EDqJ3MuZIbMq0QI1czTIKDJ/GS8S14RXPwapHw8cw6w= +github.com/lightningnetwork/lnd v0.20.0-beta.rc1 h1:8Rm3/pcSLQI+tpCjKfYADfMjmEVFkrtoEom470siKRA= +github.com/lightningnetwork/lnd v0.20.0-beta.rc1/go.mod h1:SgniBRmo5pE7IImxIfhUofhgdXkutcV9Znrf/rEZ7TM= +github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= +github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/fn v1.2.3 h1:Q1OrgNSgQynVheBNa16CsKVov1JI5N2AR6G07x9Mles= github.com/lightningnetwork/lnd/fn v1.2.3/go.mod h1:SyFohpVrARPKH3XVAJZlXdVe+IwMYc4OMAvrDY32kw0= +github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY+Rlj9QN5S3g= +github.com/lightningnetwork/lnd/fn/v2 v2.0.8/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= +github.com/lightningnetwork/lnd/healthcheck v1.2.6 h1:1sWhqr93GdkWy4+6U7JxBfcyZIE78MhIHTJZfPx7qqI= +github.com/lightningnetwork/lnd/healthcheck v1.2.6/go.mod h1:Mu02um4CWY/zdTOvFje7WJgJcHyX2zq/FG3MhOAiGaQ= +github.com/lightningnetwork/lnd/kvdb v1.4.16 h1:9BZgWdDfjmHRHLS97cz39bVuBAqMc4/p3HX1xtUdbDI= +github.com/lightningnetwork/lnd/kvdb v1.4.16/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= +github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= +github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= +github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117 h1:f351uGAVayRRe7NEp94kGPC6X7u0lLEvGhLkE+4V3GI= +github.com/lightningnetwork/lnd/sqldb v1.0.11-0.20250930033359-90c96c7df117/go.mod h1:oOdZ7vjmAUmI9He+aFHTunnxKVefHZAfJttZdz16hSg= +github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= +github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA= +github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0= +github.com/lightningnetwork/lnd/tlv v1.3.2/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc= +github.com/lightningnetwork/lnd/tor v1.1.6 h1:WHUumk7WgU6BUFsqHuqszI9P6nfhMeIG+rjJBlVE6OE= +github.com/lightningnetwork/lnd/tor v1.1.6/go.mod h1:qSRB8llhAK+a6kaTPWOLLXSZc6Hg8ZC0mq1sUQ/8JfI= +github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= +github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= +github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= +github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80= github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw= github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -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/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +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/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -208,106 +506,285 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v2 v2.305.12 h1:0m4ovXYo1CHaA/Mp3X/Fak5sRNIWf01wk/X1/G3sGKI= +go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= +go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.etcd.io/etcd/pkg/v3 v3.5.12 h1:OK2fZKI5hX/+BTK76gXSTyZMrbnARyX9S643GenNGb8= +go.etcd.io/etcd/pkg/v3 v3.5.12/go.mod h1:UVwg/QIMoJncyeb/YxvJBJCE/NEwtHWashqc8A1nj/M= +go.etcd.io/etcd/raft/v3 v3.5.12 h1:7r22RufdDsq2z3STjoR7Msz6fYH8tmbkdheGfwJNRmU= +go.etcd.io/etcd/raft/v3 v3.5.12/go.mod h1:ERQuZVe79PI6vcC3DlKBukDCLja/L7YMu29B74Iwj4U= +go.etcd.io/etcd/server/v3 v3.5.12 h1:EtMjsbfyfkwZuA2JlKOiBfuGkFCekv5H178qjXypbG8= +go.etcd.io/etcd/server/v3 v3.5.12/go.mod h1:axB0oCjMy+cemo5290/CutIjoxlfA6KVYKD1w0uue10= +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.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +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/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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-20210616094352-59db8d763f22/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso= +gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/macaroon-bakery.v2 v2.1.0 h1:9Jw/+9XHBSutkaeVpWhDx38IcSNLJwWUICkOK98DHls= +gopkg.in/macaroon-bakery.v2 v2.1.0/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA= +gopkg.in/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI= +gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= @@ -334,3 +811,7 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/harness/harness.go b/harness/harness.go new file mode 100644 index 000000000..2d525db5d --- /dev/null +++ b/harness/harness.go @@ -0,0 +1,1974 @@ +// Package harness provides a small, programmatic integration-test runner that +// starts a regtest bitcoind and an lnd node in Docker, boots arkd in-process, +// and offers helpers for mining and funding. It also manages per-run artifacts +// (data directories and logs), supports parallel test execution through +// dynamic ports and per-run Docker networks, and guarantees clean teardown. +package harness + +import ( + "bytes" + "context" + crand "crypto/rand" + "encoding/json" + "flag" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "path/filepath" + "runtime" + "strings" + "sync" + "syscall" + "testing" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/rpcclient" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/taproot-assets/taprpc" + lnrpc "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/macaroons" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "gopkg.in/macaroon.v2" +) + +const ( + // numInitialBlocks is the default number of blocks to pre-mine on + // startup to ensure coinbase maturity (100 blocks) plus a 6-block + // buffer for spending coinbase outputs in tests with some headroom. + numInitialBlocks = 106 + + // defaultTimeout is the default timeout for various operations + // including RPC calls, container startup, and network operations. + defaultTimeout = 30 * time.Second + + // pollInterval is the interval for polling in require.Eventually + // calls. Set to 200ms to balance responsiveness with CPU usage during + // test execution. This is used for most polling operations including + // balance checks, peer connections, and chain sync. + pollInterval = 200 * time.Millisecond + + // networkPrefix is the prefix for private Docker networks created + // for each harness instance to ensure isolation between test runs. + networkPrefix = "ark-itest-" + + // bitcoindRPCUser is the RPC username for bitcoind in regtest mode. + bitcoindRPCUser = "admin1" + + // bitcoindRPCPass is the RPC password for bitcoind in regtest mode. + bitcoindRPCPass = "123" + + // maxNetworkNameRetries is the number of times to retry creating a + // unique network name on collision before giving up. Set to 5 to + // handle rare cases of concurrent test execution creating networks + // with colliding random suffixes. + maxNetworkNameRetries = 5 + + // blockTimeCushion adds margin beyond block time to account for clock + // skew and processing delays in time-dependent operations. Set to 1 + // second to ensure reliable timing in tests that depend on block + // timestamps. + blockTimeCushion = 1000 * time.Millisecond +) + +var ( + harnessLogStdOut = flag.Bool( + "harness.logstdout", false, + "if true, harness will log to stdout in addition to file", + ) + + harnessPostgres = flag.Bool( + "harness.postgres", false, + "if true, use PostgreSQL instead of SQLite for arkd", + ) + + artifactsBaseDirFlag = flag.String( + "artifacts_base_dir", "", + "Directory where the harness stores artifacts.", + ) + + // harnessHTTPClient is a dedicated HTTP client for harness HTTP + // communication (bitcoind, electrs, etc.) with proper timeouts and + // connection limits to prevent resource exhaustion during concurrent + // test execution. + harnessHTTPClient = &http.Client{ + Timeout: defaultTimeout, + Transport: &http.Transport{ + MaxIdleConns: 10, + MaxIdleConnsPerHost: 2, + IdleConnTimeout: 30 * time.Second, + }, + } +) + +// Harness spins up an ARK test environment using bitcoind and LND in Docker +// and arkd in-process. +type Harness struct { + T *testing.T + + // opts are the options used to start the harness. + opts *Options + + // stopOnce ensures Stop is only executed once even if called multiple + // times (for example via t.Cleanup and signal handler). + stopOnce sync.Once + + // sigCh receives OS signals to trigger cleanup when tests are aborted + // with Ctrl+C or similar. + sigCh chan os.Signal + + // pool is the docker test pool to manage containers. + pool *dockertest.Pool + + // network is a private docker network for the containers. + network *dockertest.Network + + // bitcoind is the bitcoind container. + bitcoind *dockertest.Resource + + // lnd is the lnd container. + lnd *dockertest.Resource + + // tapd is the tapd container. + tapd *dockertest.Resource + + // electrs is the electrs indexer container (Esplora HTTP API). + electrs *dockertest.Resource + + // postgres is the postgres container (optional, only if + // harness.postgres flag is set). + postgres *dockertest.Resource + + // PostgresHost is the host:port for postgres connection. + PostgresHost string + + // bitcoindName is the canonical name LND uses to reach bitcoind in the + // same network. + bitcoindName string + + // artifactsDir is the base dir for all artifacts (logs, data dirs). + artifactsDir string + + // group is the optional group name for all resources (containers, + // network, dirs). + group string + + // bitcoinDataDir is the bitcoind data dir (for blocks, chainstate, + // wallet). + bitcoinDataDir string + + // lndDataDir is the lnd data dir (for tls.cert and admin.macaroon). + lndDataDir string + + // lndTLSCert is the path to the TLS cert for LND. + lndTLSCert string + + // lndMacaroon is the path to the admin macaroon for LND. + lndMacaroon string + + // tapdDataDir is the tapd data dir. + tapdDataDir string + + // tapdTLSCert is the path to the TLS cert for tapd. + tapdTLSCert string + + // tapdMacaroon is the path to the admin macaroon for tapd. + tapdMacaroon string + + // harnessLogFile is the file harness logs are written to. + harnessLogFile *os.File + + // BitcoindRPC is the host:port of bitcoind RPC (18443). + BitcoindRPC string + + // BitcoindZMQBlock is the host:port of bitcoind ZMQ for raw blocks + // (28332). + BitcoindZMQBlock string + + // BitcoindZMQTx is the host:port of bitcoind ZMQ for raw txs (28333). + BitcoindZMQTx string + + // LNDGRPCPort is the host port mapped to lnd gRPC (10009). + LNDGRPCPort string + + // LNDRestPort is the host port mapped to lnd REST (8080). + LNDRestPort string + + // TapdGRPCPort is the host port mapped to tapd gRPC (10029). + TapdGRPCPort string + + // TapdRestPort is the host port mapped to tapd REST (8089). + TapdRestPort string + + // LND is the lndclient instance connected to the running LND. + LND *lndclient.LndServices + + // extraLNDs holds additional LND instances spawned for tests. + extraLNDs map[string]*LndInstance + + // EsploraURL is the base URL of the local electrs HTTP server. + EsploraURL string +} + +// LndInstance represents a running LND node spawned by the harness. +type LndInstance struct { + Name string + Resource *dockertest.Resource + DataDir string + TLSCert string + Macaroon string + GRPCPort string + RESTPort string + Client *lndclient.LndServices + ContainerName string +} + +// Options configures how the Harness is started, isolated and how artifacts +// and logs are handled. +type Options struct { + // BitcoindImage is the docker image:tag to use for bitcoind. + BitcoindImage string + + // LNDImage is the docker image:tag to use for lnd. + LNDImage string + + // LNDBuildPath is optional: build LND image from local path instead of + // pulling tag. Leave empty to skip build and pull image instead. + LNDBuildPath string + + // TapdImage is the docker image:tag to use for tapd. + TapdImage string + + // ArtifactsBaseDir is the base directory to create store artifacts in. + ArtifactsBaseDir string + + // GroupName is an optional name to group all resources (containers, + // network, dirs) under. If empty, a random suffix is used. + GroupName string + + // HarnessLogStdOut if true, also prints harness logs to stdout in + // addition to the harness log file. + HarnessLogStdOut bool + + // ArkdLogStdOut if true, also prints arkd logs to stdout in + // addition to the arkd log file. + ArkdLogStdOut bool + + // StartTapd if true, starts a tapd instance along with the harness. + // Default is false to speed up tests that don't need tapd. + StartTapd bool + + // AlwaysKeepArtifacts if true, keeps artifacts dir even on successful + // runs. By default, artifacts are kept. + AlwaysKeepArtifacts bool +} + +// DefaultOptions returns sensible defaults for running the harness locally. +func DefaultOptions() Options { + artifactsBaseDir := "test-artifacts" + if artifactsBaseDirFlag != nil && *artifactsBaseDirFlag != "" { + artifactsBaseDir = *artifactsBaseDirFlag + } + + return Options{ + BitcoindImage: "lightninglabs/bitcoin-core:29", + LNDImage: "lightninglabs/lnd:v0.19.3-beta", + TapdImage: "lightninglabs/taproot-assets:v0.7.0-rc1", + ArtifactsBaseDir: artifactsBaseDir, + HarnessLogStdOut: *harnessLogStdOut, + AlwaysKeepArtifacts: true, + } +} + +func (o *Options) validate(t *testing.T) { + t.Helper() + + require.NotEmpty(t, o.ArtifactsBaseDir, "ArtifactsBaseDir must be "+ + "set") +} + +// NewHarness creates a new Harness instance. If opts is nil the defaults from +// DefaultOptions() are used. The returned instance is not started yet, call +// Start() to launch the environment. +func NewHarness(t *testing.T, opts *Options) *Harness { + t.Helper() + + if opts == nil { + d := DefaultOptions() + opts = &d + } + + opts.validate(t) + + return &Harness{ + T: t, + opts: opts, + extraLNDs: make(map[string]*LndInstance), + } +} + +// shortPath returns the last n path elements (e.g., "pkg/file.go"). +func shortPath(full string, n int) string { + parts := []string{} + for range n { + base := filepath.Base(full) + parts = append([]string{base}, parts...) + full = filepath.Dir(full) + if full == "." || full == "/" { + break + } + } + + if len(parts) == 0 { + return filepath.Base(full) + } + + return filepath.Join(parts...) +} + +// caller returns file:line for the frame `skip` up the stack. +func caller(skip int) (string, int) { + // runtime.Caller(0) => this function + // runtime.Caller(1) => the function that called caller() + // ... + _, file, line, ok := runtime.Caller(skip) + if !ok { + return "?:?", 0 + } + + return shortPath(file, 2), line +} + +// logWithCaller centralizes timestamp + caller resolution. baseSkip accounts +// for caller() and logWithCaller() frames. +func (h *Harness) logWithCaller(additionalSkip int, msg string) { + const baseSkip = 2 + file, line := caller(baseSkip + additionalSkip) + + const tsLayout = "2006-01-02 15:04:05.000" + ts := time.Now().Format(tsLayout) + + logLine := fmt.Sprintf("%s [%s:%d] %s\n", ts, file, line, msg) + if h.harnessLogFile != nil { + var err error + _, err = h.harnessLogFile.WriteString(logLine) + require.NoError(h.T, err, "failed to write harness log") + } + + if h.opts.HarnessLogStdOut { + // Intentionally using Print as we want to ensure that logLine + // is printed as-is without extra formatting. + fmt.Print(logLine) + } +} + +// Log centralizes harness logging by printing timestamped messages with +// caller information to both file and optionally stdout, enabling easier +// debugging of test execution flow and post-mortem analysis when tests fail. +func (h *Harness) Log(args ...any) { + h.logWithCaller(1, fmt.Sprint(args...)) +} + +// Logf centralizes harness logging by formatting and printing timestamped +// messages with caller information to both file and optionally stdout, using +// printf-style formatting for structured output during test execution. +func (h *Harness) Logf(format string, args ...any) { + h.logWithCaller(1, fmt.Sprintf(format, args...)) +} + +// BaseDir returns the base directory where all artifacts (data dirs, logs) are +// stored. +func (h *Harness) BaseDir() string { + return h.artifactsDir +} + +// Start launches bitcoind and lnd containers, initializes lnd, and boots arkd +// in-process. +func (h *Harness) Start() { + h.T.Helper() + + h.setupArtifactsAndLogging() + h.setupDockerEnvironment() + h.createDataDirectories() + h.startInfrastructure() + h.startLightningNetwork() + h.setupSignalHandlers() +} + +// setupArtifactsAndLogging creates the artifacts directory and initializes +// logging to both file and stdout. +func (h *Harness) setupArtifactsAndLogging() { + // Use a group name for all resources if provided, otherwise random. + // This allows grouping resources for easier inspection and cleanup. + if h.opts.GroupName != "" { + h.group = h.opts.GroupName + } else { + h.group = randSuffix() + } + + require.NoError(h.T, os.MkdirAll(h.opts.ArtifactsBaseDir, 0o755)) + + // Use human-readable timestamp format (YYYYMMDDhhmmss) for easier + // navigation and identification of test artifacts. + timestamp := time.Now().Format("20060102150405") + h.artifactsDir = filepath.Join( + h.opts.ArtifactsBaseDir, h.group, timestamp, + ) + require.NoError(h.T, os.MkdirAll(h.artifactsDir, 0o755)) + + // Set up harness logging with btclog to both stdout and file. + harnessLogPath := filepath.Join(h.artifactsDir, "harness.log") + var err error + h.harnessLogFile, err = os.OpenFile( + harnessLogPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644, + ) + require.NoError(h.T, err, "failed to open harness log file") + + h.Logf("Starting harness, artifacts dir: %v", h.artifactsDir) +} + +// setupDockerEnvironment initializes the Docker pool, prunes stale networks, +// and creates an isolated network for this test run. +func (h *Harness) setupDockerEnvironment() { + var err error + h.pool, err = dockertest.NewPool("") + require.NoError(h.T, err, "failed to init docker pool") + + h.pruneStaleHarnessNetworks() + + // Per-run isolation. + h.Log("Creating docker network...") + h.network, err = h.createNetworkUnique() + require.NoError(h.T, err, "failed to create network") + h.Logf("Docker network created: %s (id=%s)", h.network.Network.Name, + h.network.Network.ID) +} + +// createDataDirectories creates the necessary data directories for bitcoind, +// lnd, and tapd. +func (h *Harness) createDataDirectories() { + h.bitcoinDataDir = filepath.Join(h.artifactsDir, "bitcoind") + h.lndDataDir = filepath.Join(h.artifactsDir, "lnd") + h.tapdDataDir = filepath.Join(h.artifactsDir, "tapd") + + require.NoError(h.T, os.MkdirAll(h.bitcoinDataDir, 0o755)) + require.NoError(h.T, os.MkdirAll(h.lndDataDir, 0o755)) + require.NoError(h.T, os.MkdirAll(h.tapdDataDir, 0o755)) +} + +// startInfrastructure starts the core infrastructure containers: bitcoind, +// electrs, and optionally postgres. Bitcoind and postgres start concurrently +// for better performance. +func (h *Harness) startInfrastructure() { + // Start bitcoind and postgres concurrently (they are independent). + // This saves time since postgres startup can overlap with bitcoind + // initialization and block mining. + var wg sync.WaitGroup + if *harnessPostgres { + wg.Add(1) + go func() { + defer wg.Done() + h.Log("Starting postgres...") + h.startPostgres() + }() + } + + h.Log("Starting bitcoind...") + h.startBitcoind() + + h.Logf("Pre-mining %d regtest blocks...", numInitialBlocks) + h.Generate(numInitialBlocks) + + // Start electrs (Esplora HTTP) after pre-mining to speed up initial + // index. + h.Log("Starting electrs (Esplora HTTP)...") + h.startElectrs() + + // Wait for postgres to finish starting if it was requested. + wg.Wait() + + h.Logf("Esplora URL available at: %s", h.EsploraURL) +} + +// startLightningNetwork starts the primary LND node, initializes its wallet, +// and optionally starts tapd. +func (h *Harness) startLightningNetwork() { + h.Log("Starting lnd...") + primaryLND := h.startLND() + + h.Log("Initializing LND wallet if needed...") + h.initAndWaitLND(primaryLND) + + if h.opts.StartTapd { + h.Log("Starting tapd...") + h.startTapd() + } else { + h.Log("Skipping tapd startup (StartTapd=false)") + } +} + +// setupSignalHandlers arranges for cleanup on Ctrl+C/termination as a safety +// net in case test cleanup doesn't run. This prevents orphaned containers. +func (h *Harness) setupSignalHandlers() { + // The signal handler is safe to call concurrently with normal cleanup + // because Stop() uses sync.Once internally. + h.sigCh = make(chan os.Signal, 1) + signal.Notify( + h.sigCh, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, + syscall.SIGQUIT, + ) + + go func() { + _, ok := <-h.sigCh + if !ok { + return + } + h.Log("signal received, stopping harness...") + h.Stop() + }() +} + +// Stop tears down ark server and docker resources. +func (h *Harness) Stop() { + h.stopOnce.Do(func() { + h.disableSignalHandlers() + h.Log("Stopping harness...") + h.killContainers() + h.saveLogsOnFailure() + h.purgeDockerResources() + h.Log("Harness stopped") + h.cleanupArtifacts() + }) +} + +// disableSignalHandlers stops receiving signals to avoid re-entry and closes +// the signal channel. +func (h *Harness) disableSignalHandlers() { + if h.sigCh != nil { + signal.Stop(h.sigCh) + close(h.sigCh) + } +} + +// killContainers forcefully stops all running containers. This is faster than +// purging and allows us to save logs before removal. +func (h *Harness) killContainers() { + h.Logf("Stopping docker containers...") + + // Kill in reverse startup order: electrs, postgres, tapd, lnd(s), + // bitcoind. This ensures dependent services stop before their + // dependencies. + h.killContainer(h.electrs, "electrs") + h.killContainer(h.postgres, "postgres") + h.killContainer(h.tapd, "tapd") + h.killContainer(h.lnd, "lnd") + + // Kill any additional LND instances. + for name, inst := range h.extraLNDs { + if inst != nil && inst.Resource != nil { + h.killContainer(inst.Resource, name) + } + } + + h.killContainer(h.bitcoind, "bitcoind") +} + +// killContainer kills a single container by ID, logging any errors. +func (h *Harness) killContainer( + res *dockertest.Resource, name string) { + + if res == nil { + return + } + + err := h.pool.Client.KillContainer(docker.KillContainerOptions{ + ID: res.Container.ID, + }) + if err != nil { + h.Logf("failed to kill %s: %v", name, err) + } else { + h.Logf("%s killed", name) + } +} + +// saveLogsOnFailure saves container logs to the artifacts directory if the +// test failed. +func (h *Harness) saveLogsOnFailure() { + if h.T != nil && h.T.Failed() { + if err := h.saveLogs(); err != nil { + h.Logf("failed to save container logs: %v", err) + } + } +} + +// purgeDockerResources removes all containers and the network, cleaning up +// all Docker resources. +func (h *Harness) purgeDockerResources() { + h.Log("Purging docker containers and network...") + + // Purge in same order as kill for consistency. + h.purgeResource(h.electrs, "electrs") + h.purgeResource(h.postgres, "postgres") + h.purgeResource(h.tapd, "tapd") + h.purgeResource(h.lnd, "lnd") + h.purgeResource(h.bitcoind, "bitcoind") + + if h.network != nil { + err := h.network.Close() + if err != nil { + h.Logf("failed to close network: %v", err) + } + } +} + +// purgeResource removes a single Docker container resource. +func (h *Harness) purgeResource( + res *dockertest.Resource, name string) { + + if res == nil { + return + } + + err := h.pool.Purge(res) + if err != nil { + h.Logf("failed to purge %s: %v", name, err) + } +} + +// cleanupArtifacts closes the log file and optionally removes the artifacts +// directory if the test passed and AlwaysKeepArtifacts is false. +func (h *Harness) cleanupArtifacts() { + if h.harnessLogFile != nil { + err := h.harnessLogFile.Close() + if err != nil { + h.Logf( + "failed to close harness log file: %v", err, + ) + } + } + + // Keep artifacts by default for inspection (CI can disable with + // AlwaysKeepArtifacts=false). + if h.T != nil && !h.T.Failed() && !h.opts.AlwaysKeepArtifacts { + _ = os.RemoveAll(h.artifactsDir) + } +} + +// createNetworkUnique creates a private Docker network with a unique name, +// retrying a few times on name collisions. +func (h *Harness) createNetworkUnique() (*dockertest.Network, error) { + var lastErr error + for i := 0; i < maxNetworkNameRetries; i++ { + name := networkPrefix + randSuffix() + netw, err := h.pool.CreateNetwork(name) + if err == nil { + return netw, nil + } + lastErr = err + // Brief delay before retry to avoid tight retry loops and give + // Docker time to clean up resources on name collision. + time.Sleep(pollInterval) + } + + return nil, lastErr +} + +func (h *Harness) pruneStaleHarnessNetworks() { + // Best-effort, ignore errors. + nets, err := h.pool.Client.ListNetworks() + if err != nil { + h.Logf("[DEBUG] Failed to list networks for pruning: %v", err) + return + } + + for _, n := range nets { + if !strings.HasPrefix(n.Name, networkPrefix) { + continue + } + + if len(n.Containers) == 0 { + // Best-effort, ignore errors. + err := h.pool.Client.RemoveNetwork(n.ID) + if err != nil { + h.Logf( + "[DEBUG] Failed to remove stale "+ + "network %s: %v", + n.Name, err, + ) + } + } + } +} + +// removeContainerByName attempts to remove a container by name. This is a +// best-effort operation used to clean up leftover containers from previous +// failed runs. +func (h *Harness) removeContainerByName(name string) { + // Best-effort, ignore errors. + containers, err := h.pool.Client.ListContainers( + docker.ListContainersOptions{ + All: true, + Filters: map[string][]string{ + "name": {name}, + }, + }, + ) + if err != nil { + h.Logf("[DEBUG] Failed to list containers for cleanup: %v", err) + return + } + + for _, container := range containers { + // Kill container if it's still running. + err := h.pool.Client.KillContainer(docker.KillContainerOptions{ + ID: container.ID, + }) + if err != nil { + h.Logf("[DEBUG] Failed to kill container %s: %v", + container.ID[:12], err) + } + + // Remove the container. + err = h.pool.Client.RemoveContainer( + docker.RemoveContainerOptions{ + ID: container.ID, + Force: true, + }, + ) + if err != nil { + h.Logf("[DEBUG] Failed to remove container %s: %v", + container.ID[:12], err) + } + } +} + +// waitContainerRunning polls until the given container is running. +func (h *Harness) waitContainerRunning(res *dockertest.Resource) { + // Poll container status until running, or timeout via dockertest retry. + err := h.pool.Retry(func() error { + inspect, err := h.pool.Client.InspectContainer(res.Container.ID) + if err != nil { + return err + } + if !inspect.State.Running { + return fmt.Errorf("container not running yet") + } + + return nil + }) + + require.NoError(h.T, err, "container not running: %s", + res.Container.Name) +} + +// saveLogs persists container logs to files in the artifacts directory. +func (h *Harness) saveLogs() error { + if h.bitcoind != nil { + _ = h.writeContainerLogsToFile( + h.bitcoind, + filepath.Join(h.artifactsDir, "bitcoind.log"), + ) + } + + if h.lnd != nil { + _ = h.writeContainerLogsToFile( + h.lnd, filepath.Join(h.artifactsDir, "lnd.log"), + ) + } + + if h.tapd != nil { + _ = h.writeContainerLogsToFile( + h.tapd, filepath.Join(h.artifactsDir, "tapd.log"), + ) + } + + if h.electrs != nil { + _ = h.writeContainerLogsToFile( + h.electrs, filepath.Join(h.artifactsDir, "electrs.log"), + ) + } + + if h.postgres != nil { + _ = h.writeContainerLogsToFile( + h.postgres, filepath.Join( + h.artifactsDir, "postgres.log", + ), + ) + } + + // Save logs for any additional LND instances. + for name, inst := range h.extraLNDs { + if inst != nil && inst.Resource != nil { + _ = h.writeContainerLogsToFile( + inst.Resource, + filepath.Join(h.artifactsDir, name+".log"), + ) + } + } + + return nil +} + +// writeContainerLogsToFile writes the full logs of a container to path. +func (h *Harness) writeContainerLogsToFile(res *dockertest.Resource, + path string) error { + + var buf bytes.Buffer + err := h.pool.Client.Logs(docker.LogsOptions{ + Container: res.Container.ID, + Stdout: true, + Stderr: true, + Tail: "all", + Follow: false, + OutputStream: &buf, + ErrorStream: &buf, + }) + if err != nil { + return err + } + + return os.WriteFile(path, buf.Bytes(), 0o644) +} + +// startBitcoind launches a bitcoind container and waits until its JSON-RPC is +// responsive then ensures a wallet exists. +func (h *Harness) startBitcoind() { + // Remove any existing bitcoind container with the same name. + containerName := h.containerName("bitcoin") + h.removeContainerByName(containerName) + + cmd := []string{ + "-regtest", + "-txindex=1", + // Set wallet fallback fee to ~1 sat/vB so that even very low + // estimates stay above bitcoind's relay floor. + "-fallbackfee=0.00001", + // Match the lower fee estimates we see from electrs in tests so + // commitment packages targeting ~0.5 sat/vB still pass policy. + "-minrelaytxfee=0.00000500", + fmt.Sprintf("-rpcuser=%s", bitcoindRPCUser), + fmt.Sprintf("-rpcpassword=%s", bitcoindRPCPass), + "-rpcallowip=0.0.0.0/0", + "-rpcbind=0.0.0.0", + "-zmqpubrawblock=tcp://0.0.0.0:28332", + "-zmqpubrawtx=tcp://0.0.0.0:28333", + "-printtoconsole", + } + + // Ensure absolute host path for bind mount. + btcHostDir, err := filepath.Abs(h.bitcoinDataDir) + require.NoError(h.T, err, "failed to get absolute path "+ + "for bitcoind data dir") + + res, err := h.pool.RunWithOptions(&dockertest.RunOptions{ + Repository: imageRepo(h.opts.BitcoindImage), + Tag: imageTag(h.opts.BitcoindImage), + Cmd: cmd, + Env: []string{}, + ExposedPorts: []string{ + "18443/tcp", "28332/tcp", "28333/tcp", + }, + Name: h.containerName("bitcoin"), + Networks: []*dockertest.Network{h.network}, + Labels: map[string]string{ + "ark.harness": h.group, + "com.docker.compose.project": h.group, + }, + Mounts: []string{ + fmt.Sprintf("%s:%s", btcHostDir, + "/home/bitcoin/.bitcoin"), + }, + }, func(hc *docker.HostConfig) { + // Keep container for logs on failure; Purge() will clean up. + hc.AutoRemove = false + hc.PortBindings = map[docker.Port][]docker.PortBinding{ + "18443/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + "28332/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + "28333/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + } + }) + require.NoError(h.T, err, "failed to start bitcoind") + h.bitcoind = res + + // LND will reach bitcoind by this container name inside the network. + // Container name has leading '/', strip it. + h.bitcoindName = res.Container.Name + if len(h.bitcoindName) > 0 && h.bitcoindName[0] == '/' { + h.bitcoindName = h.bitcoindName[1:] + } + + // Log container info and resolve host ports. + h.Logf("bitcoind container id=%s name=%s", res.Container.ID, + res.Container.Name) + + // Wait until container is running before inspecting ports. + h.waitContainerRunning(res) + + rpcPort := res.GetPort("18443/tcp") + zmqBlock := res.GetPort("28332/tcp") + zmqTx := res.GetPort("28333/tcp") + h.BitcoindRPC = net.JoinHostPort("127.0.0.1", rpcPort) + h.BitcoindZMQBlock = fmt.Sprintf("tcp://127.0.0.1:%s", zmqBlock) + h.BitcoindZMQTx = fmt.Sprintf("tcp://127.0.0.1:%s", zmqTx) + + h.Logf("bitcoind RPC=%s ZMQ(block)=%s ZMQ(tx)=%s", h.BitcoindRPC, + h.BitcoindZMQBlock, h.BitcoindZMQTx) + + // Ensure JSON-RPC is responsive before proceeding. + h.Log("Waiting for bitcoind JSON-RPC to be responsive...") + h.waitForBitcoind() + + // Ensure a wallet exists. + h.Log("Ensuring bitcoind wallet exists...") + h.bitcoindEnsureWallet() +} + +// startElectrs launches an electrs container that serves an Esplora-compatible +// HTTP API used by arkd and tests. It shares the bitcoind datadir to access +// blocks and chainstate and connects to bitcoind over RPC inside the private +// network. +func (h *Harness) startElectrs() { + // Remove any existing electrs container with the same name. + containerName := h.containerName("electrs") + h.removeContainerByName(containerName) + + // Electrs flags (match Esplora-compatible mode). + cmd := []string{ + "-vvv", + "--timestamp", + "--network=regtest", + fmt.Sprintf("--cookie=%s:%s", bitcoindRPCUser, bitcoindRPCPass), + fmt.Sprintf("--daemon-rpc-addr=%s:18443", h.bitcoindName), + "--http-addr=0.0.0.0:3002", + "--electrum-rpc-addr=0.0.0.0:60401", + "--cors=*", + "--daemon-dir=/home/user/.bitcoin", + "--db-dir=/home/user/.bitcoin/db", + } + + // Mount the same host datadir used by bitcoind so electrs can read + // blocks. + btcHostDir, err := filepath.Abs(h.bitcoinDataDir) + require.NoError(h.T, err, "failed to get absolute path "+ + "for bitcoind data dir") + + res, err := h.pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "mempool/electrs", + Tag: "latest", + Cmd: cmd, + Env: []string{"RUST_BACKTRACE=1"}, + ExposedPorts: []string{"3002/tcp", "60401/tcp"}, + Name: h.containerName("electrs"), + Networks: []*dockertest.Network{h.network}, + Labels: map[string]string{ + "ark.harness": h.group, + "com.docker.compose.project": h.group, + }, + Mounts: []string{ + fmt.Sprintf("%s:%s", btcHostDir, "/home/user/.bitcoin"), + }, + }, func(hc *docker.HostConfig) { + hc.AutoRemove = false + hc.PortBindings = map[docker.Port][]docker.PortBinding{ + "3002/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + "60401/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + } + // Ensure explicit bind mount. + hc.Binds = []string{ + fmt.Sprintf("%s:%s", btcHostDir, "/home/user/.bitcoin"), + } + }) + require.NoError(h.T, err, "failed to start electrs") + h.Logf("electrs container id=%s name=%s", res.Container.ID, + res.Container.Name) + + h.electrs = res + + // Resolve host port for HTTP (Esplora API). + httpPort := res.GetPort("3002/tcp") + h.EsploraURL = fmt.Sprintf("http://127.0.0.1:%s", httpPort) + h.Logf("electrs HTTP=%s", h.EsploraURL) + + // Wait for Esplora endpoint to respond with current tip height. + require.Eventually(h.T, func() bool { + const connectTimeout = 2 * time.Second + ctx, cancel := context.WithTimeout( + context.Background(), connectTimeout, + ) + defer cancel() + + req, err := http.NewRequestWithContext( + ctx, http.MethodGet, h.EsploraURL+"/blocks/tip/height", + nil, + ) + if err != nil { + return false + } + + resp, err := harnessHTTPClient.Do(req) + if err != nil { + return false + } + defer resp.Body.Close() + + return resp.StatusCode == http.StatusOK + }, defaultTimeout, pollInterval, "electrs HTTP not ready") +} + +// startPostgres launches a postgres container for arkd to use instead of +// SQLite. +func (h *Harness) startPostgres() { + // Remove any existing postgres container with the same name (from + // previous failed runs). + containerName := h.containerName("postgres") + h.removeContainerByName(containerName) + + res, err := h.pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", + Tag: "16-alpine", + Env: []string{ + "POSTGRES_USER=ark", + "POSTGRES_PASSWORD=ark", + "POSTGRES_DB=ark", + }, + ExposedPorts: []string{"5432/tcp"}, + Name: h.containerName("postgres"), + Networks: []*dockertest.Network{h.network}, + Labels: map[string]string{ + "ark.harness": h.group, + "com.docker.compose.project": h.group, + }, + }, func(hc *docker.HostConfig) { + hc.AutoRemove = false + hc.PortBindings = map[docker.Port][]docker.PortBinding{ + "5432/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + } + }) + require.NoError(h.T, err, "failed to start postgres") + h.postgres = res + + h.Logf("postgres container id=%s name=%s", res.Container.ID, + res.Container.Name) + + // Wait until container is running. + h.waitContainerRunning(res) + + // Resolve host port. + pgPort := res.GetPort("5432/tcp") + h.PostgresHost = net.JoinHostPort("127.0.0.1", pgPort) + h.Logf("postgres listening at %s", h.PostgresHost) + + // Wait for postgres to be ready. + h.Log("Waiting for postgres to be ready...") + require.Eventually(h.T, func() bool { + conn, err := net.DialTimeout("tcp", h.PostgresHost, time.Second) + if err != nil { + return false + } + _ = conn.Close() + + return true + }, defaultTimeout, pollInterval, "postgres not ready") +} + +// Generate mines n regtest blocks to a fresh address by calling bitcoind's +// generatetoaddress RPC, returning block headers for test validation. This +// advances the chain immediately without requiring mempool transactions. +func (h *Harness) Generate(blocks int) []BlockHeader { + h.T.Helper() + + addr := h.bitcoindGetNewAddress() + headers := h.bitcoindGenerateToAddress(blocks, addr) + + h.Logf("Generated %d blocks to %s", blocks, addr) + + return headers +} + +// Block is a mined block header along with the txids it contains. +type Block struct { + // Header is the block header. + Header BlockHeader + + // TxIDs are the txids in the block. + TxIDs []string +} + +// GenerateAndWait mines 'numBlocks' blocks and waits until wall-clock >= the +// last header time. It also returns the list of txids for each mined block (in +// the same order as headers). +func (h *Harness) GenerateAndWait(numBlocks int) []Block { + headers := h.Generate(numBlocks) + blocks := make([]Block, 0, len(headers)) + + // Collect txids for each mined block. + for _, header := range headers { + txids, err := h.bitcoindGetBlockTxids(header.Hash) + require.NoError( + h.T, err, "getblock txids failed for %s", header.Hash, + ) + + blocks = append(blocks, Block{ + Header: header, + TxIDs: txids, + }) + } + + last := headers[len(headers)-1] + blockTime := time.Unix(last.Time, 0) + if wait := time.Until(blockTime); wait > 0 { + h.Logf("waiting %v until block time %v", wait, blockTime) + + // Add cushion beyond block time to ensure any time-dependent + // operations have margin for clock skew and processing delays. + time.Sleep(wait + blockTimeCushion) + } + + return blocks +} + +// BlockCount queries bitcoind's getblockcount RPC to retrieve the current +// regtest chain height, handling JSON response type variations to ensure +// robust parsing across different bitcoind versions and response formats. +func (h *Harness) BlockCount() uint32 { + res, err := h.bitcoinRPCCall("getblockcount") + require.NoError(h.T, err) + var height uint32 + // bitcoind returns a number; json.Unmarshal into uint32 via float64 + // then cast. + var raw any + require.NoError(h.T, json.Unmarshal(res, &raw)) + switch v := raw.(type) { + case float64: + height = uint32(v) + case int: + height = uint32(v) + default: + // Attempt direct decode. + require.NoError(h.T, json.Unmarshal(res, &height)) + } + + return height +} + +// Faucet funds a test address by sending the specified amount from bitcoind's +// default wallet, creating unconfirmed UTXOs for tests to spend. This mimics +// external funding without requiring manual transaction construction. +func (h *Harness) Faucet(address string, amount btcutil.Amount) string { + h.T.Helper() + + h.bitcoindEnsureWallet() + txID := h.bitcoindSendToAddress(address, amount.ToBTC()) + h.Logf("Faucet sent %v to %s (txid %s)", amount, address, txID) + + return txID +} + +// MempoolTxIDs queries bitcoind's getrawmempool RPC to retrieve all +// transaction IDs currently waiting in the mempool, enabling tests to verify +// transaction broadcast and propagation before confirmation. +func (h *Harness) MempoolTxIDs() []string { + h.T.Helper() + + res, err := h.bitcoinRPCCall("getrawmempool") + require.NoError(h.T, err) + + var txids []string + require.NoError( + h.T, json.Unmarshal(res, &txids), + "getrawmempool unmarshal failed", + ) + + return txids +} + +// WaitMempoolTxCount waits until the mempool has at least 'minTxCount' txs. +func (h *Harness) WaitMempoolTxCount(minTxCount int) []string { + h.T.Helper() + + var txIDs []string + // Poll more frequently (200ms) than default for mempool operations + // since transaction propagation is typically fast. + require.Eventually( + h.T, func() bool { + txIDs = h.MempoolTxIDs() + + return len(txIDs) >= minTxCount + }, defaultTimeout, pollInterval, + "mempool tx count %d < %d", len(txIDs), minTxCount, + ) + + return txIDs +} + +// rpcRequest is a JSON-RPC request. +type rpcRequest struct { + JSONRPC string `json:"jsonrpc"` + ID int `json:"id"` + Method string `json:"method"` + Params []interface{} `json:"params"` +} + +// rpcResponse is a JSON-RPC response. +type rpcResponse struct { + Result json.RawMessage `json:"result"` + Error *struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error"` + ID int `json:"id"` +} + +// BitcoinRPCClient returns a new bitcoind RPC client connected to the harness's +// bitcoind instance. +func (h *Harness) BitcoinRPCClient() (*rpcclient.Client, error) { + connCfg := &rpcclient.ConnConfig{ + Host: h.BitcoindRPC, + User: bitcoindRPCUser, + Pass: bitcoindRPCPass, + HTTPPostMode: true, + DisableTLS: true, + } + + return rpcclient.New(connCfg, nil) +} + +// bitcoinRPCCall makes a JSON-RPC call to bitcoind and returns the raw result +// or an error. +func (h *Harness) bitcoinRPCCall(method string, + params ...interface{}) (json.RawMessage, error) { + + ctxt, cancel := context.WithTimeout( + context.Background(), defaultTimeout, + ) + defer cancel() + + url := fmt.Sprintf("http://%s", h.BitcoindRPC) + body, err := json.Marshal(rpcRequest{ + JSONRPC: "1.0", ID: 1, Method: method, Params: params, + }) + if err != nil { + return nil, fmt.Errorf("failed to marshal rpc request: %s: %w", + method, err) + } + + req, err := http.NewRequestWithContext( + ctxt, http.MethodPost, url, bytes.NewReader(body), + ) + if err != nil { + return nil, fmt.Errorf("failed to create rpc request: %s: %w", + method, err) + } + + req.Header.Set("Content-Type", "application/json") + req.SetBasicAuth(bitcoindRPCUser, bitcoindRPCPass) + resp, err := harnessHTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("rpc request failed: %s: %w", method, + err) + } + + defer resp.Body.Close() + var rr rpcResponse + err = json.NewDecoder(resp.Body).Decode(&rr) + if err != nil { + return nil, fmt.Errorf("rpc response decode failed: %s: %w", + method, err) + } + + if rr.Error != nil { + return nil, fmt.Errorf("bitcoind error %d: %s (%s)", + rr.Error.Code, rr.Error.Message, method) + } + + return rr.Result, nil +} + +// waitForBitcoind polls bitcoind's RPC until it responds or times out. +func (h *Harness) waitForBitcoind() { + h.T.Helper() + + // Ensure node-level RPC is responsive. + require.Eventually(h.T, func() bool { + _, err := h.bitcoinRPCCall("getblockchaininfo") + return err == nil + }, defaultTimeout, time.Second, "bitcoind JSON-RPC not responsive") +} + +// bitcoindEnsureWallet makes sure a default wallet exists, creating one if +// needed. +func (h *Harness) bitcoindEnsureWallet() { + h.T.Helper() + + // Make sure a default wallet exists, create one if needed. + res, err := h.bitcoinRPCCall("listwallets") + require.NoError(h.T, err) + + var wallets []string + err = json.Unmarshal(res, &wallets) + require.NoError(h.T, err, "listwallets unmarshal failed") + + if len(wallets) == 0 { + _, err = h.bitcoinRPCCall("createwallet", "default") + require.NoError(h.T, err) + } +} + +// bitcoindGetNewAddress returns a new address from bitcoind's wallet. +func (h *Harness) bitcoindGetNewAddress() string { + h.T.Helper() + + res, err := h.bitcoinRPCCall("getnewaddress") + require.NoError(h.T, err) + + var addr string + err = json.Unmarshal(res, &addr) + require.NoError(h.T, err, "getnewaddress unmarshal failed") + + return addr +} + +// BlockHeader is a minimal representation of a block header as returned by +// bitcoind's getblockheader RPC when verbose mode is enabled. +type BlockHeader struct { + Hash string `json:"hash"` + Confirmations int64 `json:"confirmations,omitempty"` + Height int64 `json:"height"` + Version int64 `json:"version,omitempty"` + VersionHex string `json:"versionHex,omitempty"` + Merkleroot string `json:"merkleroot"` + Time int64 `json:"time"` + Mediantime int64 `json:"mediantime,omitempty"` + Nonce uint32 `json:"nonce,omitempty"` + Bits string `json:"bits,omitempty"` + Difficulty float64 `json:"difficulty,omitempty"` + Chainwork string `json:"chainwork,omitempty"` + NTx int64 `json:"nTx,omitempty"` + Previousblockhash string `json:"previousblockhash,omitempty"` + Nextblockhash string `json:"nextblockhash,omitempty"` +} + +// bitcoindGenerateToAddress mines blocks to the given address. +func (h *Harness) bitcoindGenerateToAddress(blocks int, + address string) []BlockHeader { + + h.T.Helper() + + res, err := h.bitcoinRPCCall("generatetoaddress", blocks, address) + require.NoError(h.T, err) + + var hashes []string + err = json.Unmarshal(res, &hashes) + require.NoError(h.T, err, "generatetoaddress unmarshal failed") + + headers := make([]BlockHeader, 0, len(hashes)) + for _, hash := range hashes { + // Ask for verbose header JSON (true). + hdrRes, err := h.bitcoinRPCCall("getblockheader", hash, true) + require.NoError(h.T, err, "getblockheader rpc failed") + + var hdr BlockHeader + err = json.Unmarshal(hdrRes, &hdr) + require.NoError(h.T, err, "getblockheader unmarshal failed") + + headers = append(headers, hdr) + } + + return headers +} + +// bitcoindGetBlockTxids returns the list of txids for a given block hash. +func (h *Harness) bitcoindGetBlockTxids(hash string) ([]string, error) { + // Setting verbosity=1 returns a JSON object with an array of txids as + // strings. + res, err := h.bitcoinRPCCall("getblock", hash, 1) + if err != nil { + return nil, err + } + + var payload struct { + Tx []string `json:"tx"` + } + if err := json.Unmarshal(res, &payload); err != nil { + return nil, fmt.Errorf("getblock unmarshal failed: %w", err) + } + + return payload.Tx, nil +} + +// bitcoindSendToAddress sends amount BTC to address and returns the txid. +func (h *Harness) bitcoindSendToAddress(address string, amount float64) string { + h.T.Helper() + + res, err := h.bitcoinRPCCall("sendtoaddress", address, amount) + require.NoError(h.T, err) + + var txid string + err = json.Unmarshal(res, &txid) + require.NoError(h.T, err, "sendtoaddress unmarshal failed") + + return txid +} + +// startLND launches an lnd container and waits until its gRPC is responsive +// and TLS cert and admin macaroon are available. +func (h *Harness) startLND() *LndInstance { + inst := h.startLNDInstance("lnd", h.lndDataDir) + h.lnd = inst.Resource + h.LNDGRPCPort = inst.GRPCPort + h.LNDRestPort = inst.RESTPort + h.lndTLSCert = inst.TLSCert + h.lndMacaroon = inst.Macaroon + + return inst +} + +func (h *Harness) startLNDInstance(name, dataDir string) *LndInstance { + h.T.Helper() + + require.NoError(h.T, os.MkdirAll(dataDir, 0o755)) + + res := h.startLNDContainer(lndConfig{ + name: name, + dataDir: dataDir, + bitcoindName: h.bitcoindName, + network: h.network, + group: h.group, + image: imageRepo(h.opts.LNDImage), + tag: imageTag(h.opts.LNDImage), + }) + + inst := &LndInstance{ + Name: name, + Resource: res, + DataDir: dataDir, + GRPCPort: res.GetPort("10009/tcp"), + RESTPort: res.GetPort("8080/tcp"), + } + inst.ContainerName = strings.TrimPrefix(res.Container.Name, "/") + inst.TLSCert = filepath.Join(dataDir, "tls.cert") + inst.Macaroon = filepath.Join( + dataDir, "data", "chain", "bitcoin", "regtest", + "admin.macaroon", + ) + + h.Logf("%s gRPC=127.0.0.1:%s REST=127.0.0.1:%s", name, + inst.GRPCPort, inst.RESTPort) + + require.Eventually( + h.T, func() bool { + _, err := os.Stat(inst.TLSCert) + if err != nil { + return false + } + + addr := net.JoinHostPort("127.0.0.1", inst.GRPCPort) + conn, err := net.DialTimeout("tcp", addr, time.Second) + if err != nil { + return false + } + + _ = conn.Close() + + return true + }, + defaultTimeout, time.Second, + fmt.Sprintf("%s TLS/gRPC not ready", name), + ) + + return inst +} + +// initAndWaitLND initializes the lnd wallet if needed and waits until it +// reaches SERVER_ACTIVE state. +func (h *Harness) initAndWaitLND(inst *LndInstance) { + client := h.initAndWaitLNDInstance(inst) + if inst != nil && inst.Name == "lnd" { + h.LND = client + } +} + +func (h *Harness) initAndWaitLNDInstance( + inst *LndInstance) *lndclient.LndServices { + + h.T.Helper() + + if inst == nil { + inst = &LndInstance{ + Name: "lnd", + TLSCert: h.lndTLSCert, + Macaroon: h.lndMacaroon, + GRPCPort: h.LNDGRPCPort, + } + } + + addr := net.JoinHostPort("127.0.0.1", inst.GRPCPort) + tlsCert, err := credentials.NewClientTLSFromFile(inst.TLSCert, "") + require.NoError(h.T, err, "failed to load %s TLS", inst.Name) + + h.Logf("Waiting for %s to reach SERVER_ACTIVE state...", inst.Name) + require.Eventually(h.T, func() bool { + const checkTimeout = 5 * time.Second + + ctxt, cancel := context.WithTimeout( + context.Background(), checkTimeout, + ) + defer cancel() + + conn, err := grpc.DialContext( + ctxt, addr, grpc.WithTransportCredentials(tlsCert), + grpc.WithBlock(), + ) + if err != nil { + return false + } + defer conn.Close() + + stateClient := lnrpc.NewStateClient(conn) + resp, err := stateClient.GetState( + ctxt, &lnrpc.GetStateRequest{}, + ) + if err != nil { + return false + } + + return resp.State == lnrpc.WalletState_SERVER_ACTIVE + }, defaultTimeout, time.Second, fmt.Sprintf("%s not active", inst.Name)) + + err = h.pool.Retry(func() error { + if _, err := os.Stat(inst.Macaroon); err != nil { + return err + } + + return nil + }) + require.NoError(h.T, err, "%s admin macaroon not found", inst.Name) + + lndClient, err := lndclient.NewLndServices(&lndclient.LndServicesConfig{ + LndAddress: addr, + CustomMacaroonPath: inst.Macaroon, + TLSPath: inst.TLSCert, + Network: lndclient.NetworkRegtest, + }) + require.NoError(h.T, err, "failed to create %s client", inst.Name) + + services := &lndClient.LndServices + inst.Client = services + + h.waitForLNDChainSyncInstance(inst) + + return services +} + +// StartAdditionalLND launches an extra LND instance with the given name and +// returns its handle. +func (h *Harness) StartAdditionalLND(name string) *LndInstance { + h.T.Helper() + + if name == "" { + name = fmt.Sprintf("lnd-extra-%d", len(h.extraLNDs)+1) + } + + if _, exists := h.extraLNDs[name]; exists { + h.T.Fatalf("LND instance %s already exists", name) + } + + dataDir := filepath.Join(h.artifactsDir, name) + inst := h.startLNDInstance(name, dataDir) + h.initAndWaitLNDInstance(inst) + h.extraLNDs[name] = inst + + return inst +} + +// GetAdditionalLND returns a previously started extra LND instance by name. +func (h *Harness) GetAdditionalLND(name string) *LndInstance { + h.T.Helper() + + inst, exists := h.extraLNDs[name] + if !exists { + h.T.Fatalf("LND instance %s not found", name) + } + + return inst +} + +// WaitForLNDSync waits until LND reports it is synced to chain. +func (h *Harness) WaitForLNDChainSync() { + h.T.Helper() + + if h.LND == nil { + h.T.Fatalf("primary LND client not initialized") + } + + h.waitForLNDChainSyncInstance(&LndInstance{Name: "lnd", Client: h.LND}) +} + +func (h *Harness) waitForLNDChainSyncInstance(inst *LndInstance) { + h.T.Helper() + + // Poll frequently (200ms) for chain sync since it typically completes + // quickly after wallet initialization. + require.Eventually( + h.T, func() bool { + sync, err := inst.Client.Client.GetInfo( + context.Background(), + ) + if err != nil { + return false + } + + return sync.SyncedToChain + }, + defaultTimeout, pollInterval, + fmt.Sprintf("%s not synced", inst.Name), + ) +} + +// SetupChannelBetween opens a channel from the local node towards the peer +// node with the specified capacity (in satoshis). The provided context controls +// the lifetime of the operation; if nil, context.Background is used. +func (h *Harness) SetupChannelBetween(local *LndInstance, peer *LndInstance, + capacitySat, pushAmt int64) { + + t := h.T + t.Helper() + + ctx := t.Context() + + if capacitySat == 0 { + capacitySat = 500_000 + } + + // Ensure both nodes are synced to chain before opening channels. + h.waitForLNDChainSyncInstance(local) + h.waitForLNDChainSyncInstance(peer) + + peerInfo, err := peer.Client.Client.GetInfo(ctx) + require.NoError(t, err, "getinfo failed for %s", peer.Name) + + peerKeyHex := fmt.Sprintf("%x", peerInfo.IdentityPubkey[:]) + + // Create authenticated connection to local LND. + localAddr := net.JoinHostPort("127.0.0.1", local.GRPCPort) + localConn, err := getLNDClientConn( + ctx, localAddr, local.TLSCert, local.Macaroon, + ) + require.NoError(t, err, "failed to connect to %s gRPC", local.Name) + + defer localConn.Close() + + localRPC := lnrpc.NewLightningClient(localConn) + addrResp, err := localRPC.NewAddress(ctx, &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, + }) + require.NoError(t, err, "NewAddress failed for %s", local.Name) + + h.bitcoindSendToAddress(addrResp.Address, 1.0) + h.Generate(6) + + // Wait for local LND to sync after generating blocks. + h.waitForLNDChainSyncInstance(local) + + peerHost := fmt.Sprintf("%s:9735", peer.ContainerName) + _, err = localRPC.ConnectPeer(ctx, &lnrpc.ConnectPeerRequest{ + Addr: &lnrpc.LightningAddress{ + Pubkey: peerKeyHex, + Host: peerHost, + }, + Perm: true, + }) + require.NoError(t, err, "ConnectPeer failed for %s -> %s", + local.Name, peer.Name) + + // Wait for peer connection to be fully established. Poll frequently + // (200ms) since peer connections typically establish quickly. + require.Eventually(h.T, func() bool { + resp, err := localRPC.ListPeers(ctx, &lnrpc.ListPeersRequest{}) + if err != nil { + return false + } + for _, p := range resp.Peers { + if p.PubKey == peerKeyHex { + return true + } + } + + return false + }, defaultTimeout, pollInterval, "peer not connected") + + _, err = localRPC.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ + NodePubkey: peerInfo.IdentityPubkey[:], + LocalFundingAmount: capacitySat, + PushSat: pushAmt, + }) + require.NoError(t, err, "OpenChannel failed for %s -> %s", + local.Name, peer.Name) + + h.Generate(6) + + require.Eventually(t, func() bool { + resp, err := localRPC.ListChannels( + ctx, &lnrpc.ListChannelsRequest{}, + ) + if err != nil { + return false + } + + return len(resp.Channels) > 0 + }, defaultTimeout, pollInterval) +} + +// startTapd launches a tapd container that connects to the LND instance. +func (h *Harness) startTapd() { + h.tapd = h.startTapdContainer(tapdConfig{ + name: "tapd", + tapdDataDir: h.tapdDataDir, + lndDataDir: h.lndDataDir, + lndContainer: h.lnd, + network: h.network, + group: h.group, + image: imageRepo(h.opts.TapdImage), + tag: imageTag(h.opts.TapdImage), + }) + + h.TapdGRPCPort = h.tapd.GetPort("10029/tcp") + h.TapdRestPort = h.tapd.GetPort("8089/tcp") + h.Logf("tapd gRPC=127.0.0.1:%s REST=127.0.0.1:%s", h.TapdGRPCPort, + h.TapdRestPort) + + // Set paths to tapd TLS cert and macaroon for client connections. + h.tapdTLSCert = filepath.Join(h.tapdDataDir, "tls.cert") + h.tapdMacaroon = filepath.Join( + h.tapdDataDir, "data", "regtest", "admin.macaroon", + ) + + h.Log("Waiting for tapd to be ready and synced...") + h.waitForTapdReady() + h.Log("tapd is ready and synced") +} + +// getLNDClientConn creates a gRPC connection to LND using TLS and macaroon +// authentication. +func getLNDClientConn(ctx context.Context, addr, tlsPath, + macaroonPath string) (*grpc.ClientConn, error) { + + // Load TLS credentials. + creds, err := credentials.NewClientTLSFromFile(tlsPath, "") + if err != nil { + return nil, fmt.Errorf("failed to load TLS cert: %w", err) + } + + // Load macaroon. + macBytes, err := os.ReadFile(macaroonPath) + if err != nil { + return nil, fmt.Errorf("failed to read macaroon: %w", err) + } + mac := &macaroon.Macaroon{} + if err := mac.UnmarshalBinary(macBytes); err != nil { + return nil, fmt.Errorf("failed to unmarshal macaroon: %w", err) + } + + macaroonCred, err := macaroons.NewMacaroonCredential(mac) + if err != nil { + return nil, fmt.Errorf("failed to create macaroon credential: "+ + "%w", err) + } + + // Create dial options with TLS and macaroon credentials. + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(creds), + grpc.WithPerRPCCredentials(macaroonCred), + grpc.WithBlock(), + } + + conn, err := grpc.DialContext( + ctx, addr, opts..., + ) + if err != nil { + return nil, fmt.Errorf("failed to dial LND: %w", err) + } + + return conn, nil +} + +// getTapdClientConn creates a gRPC connection to tapd using TLS and macaroon +// authentication. +func getTapdClientConn(ctx context.Context, addr, tlsPath, + macaroonPath string) (*grpc.ClientConn, error) { + + // Load TLS credentials. + creds, err := credentials.NewClientTLSFromFile(tlsPath, "") + if err != nil { + return nil, fmt.Errorf("failed to load TLS cert: %w", err) + } + + // Load macaroon. + macBytes, err := os.ReadFile(macaroonPath) + if err != nil { + return nil, fmt.Errorf("failed to read macaroon: %w", err) + } + mac := &macaroon.Macaroon{} + if err := mac.UnmarshalBinary(macBytes); err != nil { + return nil, fmt.Errorf("failed to unmarshal macaroon: %w", err) + } + + macaroonCred, err := macaroons.NewMacaroonCredential(mac) + if err != nil { + return nil, fmt.Errorf("failed to create macaroon credential: "+ + "%w", err) + } + + // Create dial options with TLS and macaroon credentials. + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(creds), + grpc.WithPerRPCCredentials(macaroonCred), + grpc.WithBlock(), + } + + conn, err := grpc.DialContext(ctx, addr, opts...) + if err != nil { + return nil, fmt.Errorf("failed to dial tapd: %w", err) + } + + return conn, nil +} + +// waitForTapdReady waits until tapd's GetInfo RPC responds and reports that +// tapd is synced to chain. +func (h *Harness) waitForTapdReady() { + h.T.Helper() + + // Wait for TLS cert and macaroon to be created by tapd. + require.Eventually(h.T, func() bool { + _, errCert := os.Stat(h.tapdTLSCert) + _, errMac := os.Stat(h.tapdMacaroon) + return errCert == nil && errMac == nil + }, defaultTimeout, time.Second, "tapd TLS cert or macaroon not found") + + // Now wait for tapd to be ready and synced. + require.Eventually(h.T, func() bool { + const checkTimeout = 5 * time.Second + ctx, cancel := context.WithTimeout( + context.Background(), checkTimeout, + ) + defer cancel() + + addr := net.JoinHostPort("127.0.0.1", h.TapdGRPCPort) + conn, err := getTapdClientConn( + ctx, addr, h.tapdTLSCert, h.tapdMacaroon, + ) + if err != nil { + return false + } + defer conn.Close() + + // Call GetInfo to check if tapd is ready and synced. + client := taprpc.NewTaprootAssetsClient(conn) + resp, err := client.GetInfo(ctx, &taprpc.GetInfoRequest{}) + if err != nil { + return false + } + + // Check if tapd is synced to chain. + return resp.SyncToChain + }, defaultTimeout, time.Second, "tapd not ready or not synced") +} + +// imageRepo extracts the repository from a Docker image reference. +func imageRepo(image string) string { + // "repo:tag" -> repo + for i := len(image) - 1; i >= 0; i-- { + if image[i] == ':' { + return image[:i] + } + } + + return image +} + +// imageTag extracts the tag from a Docker image reference ("" if none). +func imageTag(image string) string { + // "repo:tag" -> tag (empty if none) + for i := len(image) - 1; i >= 0; i-- { + if image[i] == ':' { + return image[i+1:] + } + } + + return "" +} + +// containerName returns a container name, optionally with random suffix. If the +// harness has an explicit GroupName, use it without suffix for predictable +// names. +func (h *Harness) containerName(prefix string) string { + if h.opts.GroupName != "" { + return prefix + "-" + h.group + } + + return prefix + "-" + h.group + "-" + randSuffix() +} + +// randSuffix returns a short, random suffix suitable for naming resources. +func randSuffix() string { + const alpha = "abcdefghijklmnopqrstuvwxyz0123456789" + b := make([]byte, 8) + + if _, err := crand.Read(b); err != nil { + // fallback to time-based if RNG fails + now := time.Now().UnixNano() + for i := range b { + b[i] = alpha[int(now+int64(i))%len(alpha)] + } + + return string(b) + } + + for i := range b { + b[i] = alpha[int(b[i])%len(alpha)] + } + + return string(b) +} + +// NewTapClientHarness creates a TapClientHarness connected to the harness's +// main tapd instance. This is a stub for now as TapClientHarness is not yet +// ported. +func (h *Harness) NewTapClientHarness(name string) interface{} { + // TODO: Port TapClientHarness from tap-arktree when needed. + h.T.Fatal("NewTapClientHarness not yet implemented") + + return nil +} + +// SetPostgresEnabled allows tests to programmatically enable or disable +// postgres. It returns the previous value so tests can restore it. This is +// useful for testing postgres-specific functionality without requiring command +// line flags. +func SetPostgresEnabled(enabled bool) bool { + old := *harnessPostgres + *harnessPostgres = enabled + + return old +} + +// TapdUniverseHost returns the universe host for the harness's main tapd, +// suitable for container-to-container communication. +func (h *Harness) TapdUniverseHost() string { + // Get the container name (strip leading '/' if present). + tapdName := h.tapd.Container.Name + if len(tapdName) > 0 && tapdName[0] == '/' { + tapdName = tapdName[1:] + } + + // Use container name with internal port for container-to-container + // communication. + return net.JoinHostPort(tapdName, "10029") +} diff --git a/harness/harness_test.go b/harness/harness_test.go new file mode 100644 index 000000000..36812209e --- /dev/null +++ b/harness/harness_test.go @@ -0,0 +1,470 @@ +package harness_test + +import ( + "database/sql" + "flag" + "fmt" + "net" + "net/http" + "os" + "testing" + + "github.com/btcsuite/btcd/btcutil" + _ "github.com/lib/pq" + "github.com/lightninglabs/darepo-client/harness" + "github.com/stretchr/testify/require" +) + +var ( + // testParallelism controls how many harness tests can run in parallel. + // Each harness test spawns multiple Docker containers (bitcoind, lnd, + // electrs, etc.) which can be resource-intensive. + testParallelism = flag.Int( + "test.parallelism", 4, + "maximum number of harness tests to run in parallel", + ) + + // testParallelismSem is a semaphore channel to limit parallel test + // execution based on testParallelism flag. + testParallelismSem chan struct{} +) + +// ParallelN acquires a slot from the parallelism semaphore and marks the test +// as parallel. This allows controlling how many resource-intensive harness +// tests run concurrently via the -test.parallelism flag. +func ParallelN(t *testing.T) { + t.Helper() + + t.Parallel() + + testParallelismSem <- struct{}{} + t.Cleanup(func() { + <-testParallelismSem + }) +} + +// TestMain is the entry point for the test suite and handles flag parsing. +func TestMain(m *testing.M) { + // Parse flags and initialize the parallelism semaphore. + flag.Parse() + testParallelismSem = make(chan struct{}, *testParallelism) + + // Run tests. + os.Exit(m.Run()) +} + +// TestHarnessStartup tests that we can start up the harness (which includes +// bitcoind, LND and tapd) and create a new TapdHarness for a user with a +// separate lnd and tapd instance. +func TestHarnessStartup(t *testing.T) { + ParallelN(t) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + opts.StartTapd = true + + h := harness.NewHarness(t, &opts) + + t.Cleanup(func() { + h.Stop() + }) + + h.Start() + t.Logf("LND gRPC: %s, bitcoind RPC: %s", h.LNDGRPCPort, h.BitcoindRPC) + + // Start a TapdHarness for Alice to verify tapd infrastructure works. + t.Log("Starting TapdHarness for alice...") + alice := h.NewTapdHarness("alice") + t.Cleanup(func() { + alice.Stop() + }) + + t.Logf("Alice LND gRPC: %s, Alice tapd gRPC: %s", + alice.LNDGRPCPort, alice.TapdGRPCPort) +} + +// TestHarnessMining tests block generation functionality including Generate +// and GenerateAndWait methods. +func TestHarnessMining(t *testing.T) { + ParallelN(t) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + + h := harness.NewHarness(t, &opts) + t.Cleanup(h.Stop) + h.Start() + + // Test Generate() produces the expected number of blocks. + initialHeight := h.BlockCount() + t.Logf("Initial block height: %d", initialHeight) + + const numBlocks = 10 + headers := h.Generate(numBlocks) + require.Len(t, headers, numBlocks, "Generate should return "+ + "correct number of block headers") + + newHeight := h.BlockCount() + require.Equal(t, initialHeight+uint32(numBlocks), newHeight, + "Block height should increase by number of mined blocks") + + // Test GenerateAndWait() returns block headers with txids. + blocks := h.GenerateAndWait(5) + require.Len(t, blocks, 5, "GenerateAndWait should return 5 blocks") + + for i, block := range blocks { + require.NotEmpty(t, block.Header.Hash, + "Block %d should have hash", i) + require.NotEmpty(t, block.TxIDs, + "Block %d should have at least coinbase tx", i) + t.Logf("Block %d: hash=%s, txCount=%d", + i, block.Header.Hash, len(block.TxIDs)) + } + + finalHeight := h.BlockCount() + require.Equal(t, newHeight+5, finalHeight, + "Block height should increase by 5 after GenerateAndWait") +} + +// TestHarnessFaucet tests wallet funding functionality using the Faucet method. +func TestHarnessFaucet(t *testing.T) { + ParallelN(t) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + + h := harness.NewHarness(t, &opts) + t.Cleanup(h.Stop) + h.Start() + + // Get bitcoin RPC client to generate a new address. + client, err := h.BitcoinRPCClient() + require.NoError(t, err, "Should create RPC client") + defer client.Shutdown() + + addr, err := client.GetNewAddress("") + require.NoError(t, err, "GetNewAddress should succeed") + t.Logf("Generated address: %s", addr.String()) + + // Fund the address using the faucet. + const amount = btcutil.Amount(1_000_000) + txID := h.Faucet(addr.String(), amount) + require.NotEmpty(t, txID, "Faucet should return txid") + t.Logf("Faucet txid: %s", txID) + + // Verify the transaction is in the mempool. + txIDs := h.MempoolTxIDs() + require.Contains(t, txIDs, txID, + "Faucet tx should be in mempool") + + // Mine a block to confirm the transaction. + h.Generate(1) + + // Verify the transaction is no longer in the mempool. + txIDs = h.MempoolTxIDs() + require.NotContains(t, txIDs, txID, + "Confirmed tx should not be in mempool") +} + +// TestHarnessMempool tests mempool inspection functionality including +// MempoolTxIDs and WaitMempoolTxCount. +func TestHarnessMempool(t *testing.T) { + ParallelN(t) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + + h := harness.NewHarness(t, &opts) + t.Cleanup(h.Stop) + h.Start() + + // Initially mempool should be empty or nearly empty. + initialTxIDs := h.MempoolTxIDs() + t.Logf("Initial mempool size: %d", len(initialTxIDs)) + + // Get bitcoin RPC client to generate addresses. + client, err := h.BitcoinRPCClient() + require.NoError(t, err, "Should create RPC client") + defer client.Shutdown() + + // Create some transactions by funding addresses. + const numTxs = 3 + for i := range numTxs { + addr, err := client.GetNewAddress("") + require.NoError(t, err, "GetNewAddress should succeed") + + const amount = btcutil.Amount(100_000) + txID := h.Faucet(addr.String(), amount) + t.Logf("Created tx %d: %s", i+1, txID) + } + + // Wait for all transactions to appear in mempool. + expectedCount := len(initialTxIDs) + numTxs + txIDs := h.WaitMempoolTxCount(expectedCount) + require.GreaterOrEqual( + t, len(txIDs), expectedCount, + "Mempool should contain at least %d txs", expectedCount, + ) + t.Logf("Mempool now contains %d transactions", len(txIDs)) + + // Mine blocks to clear the mempool. + h.Generate(1) + clearedTxIDs := h.MempoolTxIDs() + t.Logf("Mempool after mining: %d txs", len(clearedTxIDs)) +} + +// TestHarnessEsplora tests Esplora API integration by verifying the HTTP +// endpoint is accessible and returns valid data. +func TestHarnessEsplora(t *testing.T) { + ParallelN(t) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + + h := harness.NewHarness(t, &opts) + t.Cleanup(h.Stop) + h.Start() + + require.NotEmpty(t, h.EsploraURL, "EsploraURL should be set") + t.Logf("Esplora URL: %s", h.EsploraURL) + + // Test /blocks/tip/height endpoint. + req, err := http.NewRequestWithContext( + t.Context(), http.MethodGet, h.EsploraURL+"/blocks/tip/height", + nil, + ) + require.NoError(t, err, "Should create request") + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err, "Esplora endpoint should be accessible") + defer resp.Body.Close() + + require.Equal( + t, http.StatusOK, resp.StatusCode, + "Esplora should return 200 OK", + ) + + // Mine some blocks and verify height increases. + initialHeight := h.BlockCount() + h.Generate(5) + newHeight := h.BlockCount() + + require.Equal( + t, initialHeight+5, newHeight, "Block height should increase", + ) + t.Logf("Verified block height: %d -> %d", initialHeight, newHeight) +} + +// TestHarnessBitcoindRPCClient tests that we can create a bitcoind RPC +// client and make calls. +func TestHarnessBitcoindRPCClient(t *testing.T) { + ParallelN(t) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + + h := harness.NewHarness(t, &opts) + t.Cleanup(h.Stop) + h.Start() + + // Create RPC client. + client, err := h.BitcoinRPCClient() + require.NoError(t, err, "Should create RPC client") + defer client.Shutdown() + + // Test getblockchaininfo RPC call. + info, err := client.GetBlockChainInfo() + require.NoError(t, err, "GetBlockChainInfo should succeed") + require.Equal( + t, "regtest", info.Chain, "Should be on regtest network", + ) + t.Logf("Chain: %s, Blocks: %d", info.Chain, info.Blocks) + + // Test block generation using generatetoaddress (Generate is + // deprecated in newer bitcoind versions). + initialBlocks := info.Blocks + addr, err := client.GetNewAddress("") + require.NoError(t, err, "GetNewAddress should succeed") + + _, err = client.GenerateToAddress(3, addr, nil) + require.NoError(t, err, "GenerateToAddress should succeed") + + info, err = client.GetBlockChainInfo() + require.NoError(t, err, "GetBlockChainInfo should succeed") + require.Equal( + t, initialBlocks+3, info.Blocks, + "Block count should increase by 3", + ) +} + +// TestHarnessPostgres tests that postgres can be started and is accessible. +func TestHarnessPostgres(t *testing.T) { + ParallelN(t) + + // Enable postgres via the flag. + oldVal := harness.SetPostgresEnabled(true) + defer harness.SetPostgresEnabled(oldVal) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + + h := harness.NewHarness(t, &opts) + t.Cleanup(h.Stop) + h.Start() + + require.NotEmpty(t, h.PostgresHost, "PostgresHost should be set") + t.Logf("Postgres host: %s", h.PostgresHost) + + // Verify postgres is functional by executing a simple query. + // PostgresHost is already "host:port", so parse it. + host, port, err := net.SplitHostPort(h.PostgresHost) + require.NoError(t, err, "Should be able to parse PostgresHost") + + connStr := fmt.Sprintf( + "host=%s port=%s user=ark password=ark "+ + "dbname=ark sslmode=disable", + host, port, + ) + + db, err := sql.Open("postgres", connStr) + require.NoError(t, err, "Should be able to open postgres connection") + defer db.Close() + + // Verify connection works by running a simple query. + var result int + err = db.QueryRow("SELECT 1+1").Scan(&result) + require.NoError(t, err, "Should be able to execute query") + require.Equal(t, 2, result, "Query should return correct result") + t.Logf("Successfully executed query, result: %d", result) + + // Verify we can create a table, insert, and query data. + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS test_table ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL + ) + `) + require.NoError(t, err, "Should be able to create table") + + _, err = db.Exec( + "INSERT INTO test_table (name) VALUES ($1)", "test_value", + ) + require.NoError(t, err, "Should be able to insert data") + + var name string + err = db.QueryRow( + "SELECT name FROM test_table WHERE name = $1", "test_value", + ).Scan(&name) + require.NoError(t, err, "Should be able to query data") + require.Equal(t, "test_value", name, "Should retrieve correct data") + + t.Log("Successfully verified postgres functionality") +} + +// TestHarnessMultiNode tests multi-node Lightning Network functionality +// including starting additional LND nodes and setting up channels between them. +func TestHarnessMultiNode(t *testing.T) { + ParallelN(t) + + opts := harness.DefaultOptions() + opts.GroupName = t.Name() + + h := harness.NewHarness(t, &opts) + t.Cleanup(h.Stop) + h.Start() + + // Start two additional LND nodes (alice and bob). + t.Log("Starting additional LND nodes...") + alice := h.StartAdditionalLND("alice") + require.NotNil(t, alice, "Alice LND instance should be created") + require.Equal(t, "alice", alice.Name) + require.NotEmpty(t, alice.GRPCPort, "Alice should have gRPC port") + t.Logf("Alice LND gRPC: %s", alice.GRPCPort) + + bob := h.StartAdditionalLND("bob") + require.NotNil(t, bob, "Bob LND instance should be created") + require.Equal(t, "bob", bob.Name) + require.NotEmpty(t, bob.GRPCPort, "Bob should have gRPC port") + t.Logf("Bob LND gRPC: %s", bob.GRPCPort) + + // Verify we can retrieve the nodes by name. + retrievedAlice := h.GetAdditionalLND("alice") + require.Equal(t, alice, retrievedAlice, "Should retrieve same alice") + + retrievedBob := h.GetAdditionalLND("bob") + require.Equal(t, bob, retrievedBob, "Should retrieve same bob") + + // Get node info to verify they're operational. + aliceInfo, err := alice.Client.Client.GetInfo(t.Context()) + require.NoError(t, err, "Should get alice info") + require.NotEmpty( + t, aliceInfo.IdentityPubkey, "Alice should have pubkey", + ) + t.Logf("Alice pubkey: %x", aliceInfo.IdentityPubkey) + + bobInfo, err := bob.Client.Client.GetInfo(t.Context()) + require.NoError(t, err, "Should get bob info") + require.NotEmpty(t, bobInfo.IdentityPubkey, "Bob should have pubkey") + t.Logf("Bob pubkey: %x", bobInfo.IdentityPubkey) + + // Setup a channel from alice to bob with push amount. + t.Log("Setting up channel from alice to bob...") + const capacitySat = 500_000 + const pushAmt = 100_000 + h.SetupChannelBetween(alice, bob, capacitySat, pushAmt) + + // Verify alice has the channel. + aliceChannels, err := alice.Client.Client.ListChannels( + t.Context(), false, false, + ) + require.NoError(t, err, "Should list alice channels") + require.Len( + t, aliceChannels, 1, + "Alice should have exactly one channel", + ) + + aliceChan := aliceChannels[0] + require.Equal( + t, btcutil.Amount(capacitySat), aliceChan.Capacity, + "Channel should have correct capacity", + ) + // Alice's local balance should be approximately capacity - pushAmt, + // accounting for commitment transaction fees. + expectedAliceLocal := btcutil.Amount(capacitySat - pushAmt) + require.InDelta( + t, float64(expectedAliceLocal), float64(aliceChan.LocalBalance), + 10000.0, // Allow 10k sats for fees + "Alice local balance should be close to capacity minus "+ + "push amount", + ) + t.Logf( + "Alice channel: capacity=%d, local=%d, remote=%d", + aliceChan.Capacity, aliceChan.LocalBalance, + aliceChan.RemoteBalance, + ) + + // Verify bob has the channel with push amount. + bobChannels, err := bob.Client.Client.ListChannels( + t.Context(), false, false, + ) + require.NoError(t, err, "Should list bob channels") + require.Len(t, bobChannels, 1, "Bob should have exactly one channel") + + bobChan := bobChannels[0] + require.Equal( + t, btcutil.Amount(capacitySat), bobChan.Capacity, + "Channel should have correct capacity", + ) + // Bob's local balance should be approximately the push amount. + require.InDelta( + t, float64(pushAmt), float64(bobChan.LocalBalance), + 5000.0, // Allow 5k sats variance + "Bob local balance should be close to push amount", + ) + t.Logf( + "Bob channel: capacity=%d, local=%d, remote=%d", + bobChan.Capacity, bobChan.LocalBalance, bobChan.RemoteBalance, + ) + + t.Log("Successfully verified multi-node setup and channel creation") +} diff --git a/harness/tapd_harness.go b/harness/tapd_harness.go new file mode 100644 index 000000000..7b2b4b62c --- /dev/null +++ b/harness/tapd_harness.go @@ -0,0 +1,508 @@ +package harness + +import ( + "context" + "fmt" + "net" + "os" + "path/filepath" + "time" + + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/taproot-assets/taprpc" + lnrpc "github.com/lightningnetwork/lnd/lnrpc" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +// TapdHarness represents a paired LND and tapd instance for test clients. +type TapdHarness struct { + // Name is the identifier for this harness (e.g., "alice", "bob"). + Name string + + // h is the parent harness. + h *Harness + + // lnd is the lnd container. + lnd *dockertest.Resource + + // tapd is the tapd container. + tapd *dockertest.Resource + + // lndDataDir is the lnd data dir. + lndDataDir string + + // tapdDataDir is the tapd data dir. + tapdDataDir string + + // LNDGRPCPort is the host port mapped to lnd gRPC. + LNDGRPCPort string + + // LNDRestPort is the host port mapped to lnd REST. + LNDRestPort string + + // TapdGRPCPort is the host port mapped to tapd gRPC. + TapdGRPCPort string + + // TapdRestPort is the host port mapped to tapd REST. + TapdRestPort string + + // LNDTLSCert is the path to the LND TLS cert. + LNDTLSCert string + + // LNDMacaroon is the path to the LND admin macaroon. + LNDMacaroon string + + // TapdTLSCert is the path to the tapd TLS cert. + TapdTLSCert string + + // TapdMacaroon is the path to the tapd admin macaroon. + TapdMacaroon string + + // LND is the lndclient instance connected to the running LND. + LND *lndclient.LndServices +} + +// lndConfig holds the configuration for starting an LND container. +type lndConfig struct { + name string + dataDir string + bitcoindName string + network *dockertest.Network + group string + image string + tag string +} + +// tapdConfig holds the configuration for starting a tapd container. +type tapdConfig struct { + name string + tapdDataDir string + lndDataDir string + lndContainer *dockertest.Resource + network *dockertest.Network + group string + image string + tag string +} + +// NewTapdHarness creates a new LND + tapd harness for test clients. The LND +// instance will connect to the main harness's bitcoind, and the tapd instance +// will connect to the new LND instance. +func (h *Harness) NewTapdHarness(name string) *TapdHarness { + h.T.Helper() + + th := &TapdHarness{ + Name: name, + h: h, + } + + // Create data directories for this harness. + th.lndDataDir = filepath.Join(h.artifactsDir, name+"_lnd") + th.tapdDataDir = filepath.Join(h.artifactsDir, name+"_tapd") + require.NoError(h.T, os.MkdirAll(th.lndDataDir, 0o755)) + require.NoError(h.T, os.MkdirAll(th.tapdDataDir, 0o755)) + + h.Logf("Starting LND for %s...", name) + th.lnd = h.startLNDContainer(lndConfig{ + name: name + "-lnd", + dataDir: th.lndDataDir, + bitcoindName: h.bitcoindName, + network: h.network, + group: h.group, + image: imageRepo(h.opts.LNDImage), + tag: imageTag(h.opts.LNDImage), + }) + th.setupLNDPaths() + + h.Logf("Initializing LND wallet for %s...", name) + th.initAndWaitLND() + + h.Logf("Starting tapd for %s...", name) + th.tapd = h.startTapdContainer(tapdConfig{ + name: name + "-tapd", + tapdDataDir: th.tapdDataDir, + lndDataDir: th.lndDataDir, + lndContainer: th.lnd, + network: h.network, + group: h.group, + image: imageRepo(h.opts.TapdImage), + tag: imageTag(h.opts.TapdImage), + }) + th.setupTapdPaths() + + h.Logf("TapdHarness %s is ready", name) + + return th +} + +// startLNDContainer starts an LND container with the given configuration. +func (h *Harness) startLNDContainer(cfg lndConfig) *dockertest.Resource { + lndDirInContainer := "/data" + lndName := h.containerName(cfg.name) + + // Remove any existing LND container with the same name. + h.removeContainerByName(lndName) + + cmd := []string{ + fmt.Sprintf("--lnddir=%s", lndDirInContainer), + "--bitcoin.active", + "--bitcoin.regtest", + "--bitcoin.node=bitcoind", + fmt.Sprintf("--bitcoind.rpchost=%s:18443", cfg.bitcoindName), + fmt.Sprintf("--bitcoind.rpcuser=%s", bitcoindRPCUser), + fmt.Sprintf("--bitcoind.rpcpass=%s", bitcoindRPCPass), + fmt.Sprintf("--bitcoind.zmqpubrawblock=tcp://%s:28332", + cfg.bitcoindName), + fmt.Sprintf("--bitcoind.zmqpubrawtx=tcp://%s:28333", + cfg.bitcoindName), + "--rpclisten=0.0.0.0:10009", + "--restlisten=0.0.0.0:8080", + "--listen=0.0.0.0:9735", + fmt.Sprintf("--tlsextradomain=%s", lndName), + fmt.Sprintf("--tlsextraip=%s", lndName), + "--noseedbackup", + "--nobootstrap", + "--accept-keysend", + } + + lndHostDir, err := filepath.Abs(cfg.dataDir) + require.NoError( + h.T, err, "failed to get absolute path for lnd data dir", + ) + + res, err := h.pool.RunWithOptions(&dockertest.RunOptions{ + Repository: cfg.image, + Tag: cfg.tag, + Cmd: cmd, + ExposedPorts: []string{"10009/tcp", "8080/tcp"}, + Name: lndName, + Networks: []*dockertest.Network{cfg.network}, + User: fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), + Mounts: []string{ + fmt.Sprintf("%s:%s", lndHostDir, lndDirInContainer), + }, + Labels: map[string]string{ + "ark.harness": cfg.group, + "com.docker.compose.project": cfg.group, + }, + }, func(hc *docker.HostConfig) { + hc.AutoRemove = false + hc.PortBindings = map[docker.Port][]docker.PortBinding{ + "10009/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + "8080/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + } + hc.Binds = []string{ + fmt.Sprintf("%s:%s", lndHostDir, lndDirInContainer), + } + }) + require.NoError(h.T, err, "failed to start lnd for "+cfg.name) + + h.Logf("%s LND container id=%s name=%s", cfg.name, res.Container.ID, + res.Container.Name) + + h.waitContainerRunning(res) + + return res +} + +// startTapdContainer starts a tapd container with the given configuration. +func (h *Harness) startTapdContainer(cfg tapdConfig) *dockertest.Resource { + // Use /data/tapd as the base directory to avoid requiring root + // privileges, matching the pattern used for LND containers. + tapdDirInContainer := "/data/tapd" + + // Get LND's container name for internal network connectivity. + lndName := cfg.lndContainer.Container.Name + if len(lndName) > 0 && lndName[0] == '/' { + lndName = lndName[1:] + } + + tapdName := h.containerName(cfg.name) + + // Remove any existing tapd container with the same name. + h.removeContainerByName(tapdName) + + cmd := []string{ + fmt.Sprintf("--tapddir=%s", tapdDirInContainer), + "--network=regtest", + fmt.Sprintf("--lnd.host=%s:10009", lndName), + fmt.Sprintf("--lnd.macaroonpath=%s", + "/lnd/data/chain/bitcoin/regtest/admin.macaroon"), + fmt.Sprintf("--lnd.tlspath=%s", "/lnd/tls.cert"), + "--rpclisten=0.0.0.0:10029", + "--restlisten=0.0.0.0:8089", + fmt.Sprintf("--tlsextradomain=%s", tapdName), + fmt.Sprintf("--tlsextraip=%s", tapdName), + "--universe.syncinterval=10s", + "--allow-public-stats", + "--allow-public-uni-proof-courier", + "--universe.public-access=rw", + "--universe.sync-all-assets", + "--debuglevel=debug", + } + + tapdHostDir, err := filepath.Abs(cfg.tapdDataDir) + require.NoError( + h.T, err, "failed to get absolute path for tapd data dir", + ) + + lndHostDir, err := filepath.Abs(cfg.lndDataDir) + require.NoError( + h.T, err, "failed to get absolute path for lnd data dir", + ) + + res, err := h.pool.RunWithOptions(&dockertest.RunOptions{ + Repository: cfg.image, + Tag: cfg.tag, + Cmd: cmd, + Env: []string{"RUST_BACKTRACE=1"}, + ExposedPorts: []string{"10029/tcp", "8089/tcp"}, + Name: tapdName, + Networks: []*dockertest.Network{cfg.network}, + // Run as current user to avoid permission issues with mounted + // volumes, matching the pattern used for LND containers. + User: fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), + Mounts: []string{ + fmt.Sprintf("%s:%s", tapdHostDir, tapdDirInContainer), + fmt.Sprintf("%s:%s:ro", lndHostDir, "/lnd"), + }, + Labels: map[string]string{ + "ark.harness": cfg.group, + "com.docker.compose.project": cfg.group, + }, + }, func(hc *docker.HostConfig) { + hc.AutoRemove = false + hc.PortBindings = map[docker.Port][]docker.PortBinding{ + "10029/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + "8089/tcp": {{HostIP: "0.0.0.0", HostPort: ""}}, + } + hc.Binds = []string{ + fmt.Sprintf("%s:%s", tapdHostDir, tapdDirInContainer), + fmt.Sprintf("%s:%s:ro", lndHostDir, "/lnd"), + } + }) + require.NoError(h.T, err, "failed to start tapd for "+cfg.name) + + h.Logf("%s tapd container id=%s name=%s", cfg.name, res.Container.ID, + res.Container.Name) + + h.waitContainerRunning(res) + + return res +} + +// setupLNDPaths sets up the LND-related paths and ports for the harness. +func (th *TapdHarness) setupLNDPaths() { + th.LNDGRPCPort = th.lnd.GetPort("10009/tcp") + th.LNDRestPort = th.lnd.GetPort("8080/tcp") + th.h.Logf("%s lnd gRPC=127.0.0.1:%s REST=127.0.0.1:%s", th.Name, + th.LNDGRPCPort, th.LNDRestPort) + + th.LNDTLSCert = filepath.Join(th.lndDataDir, "tls.cert") + th.LNDMacaroon = filepath.Join( + th.lndDataDir, "data", "chain", "bitcoin", "regtest", + "admin.macaroon", + ) + + // Wait for TLS cert to be available. + require.Eventually(th.h.T, func() bool { + _, err := os.Stat(th.LNDTLSCert) + if err != nil { + return false + } + + addr := net.JoinHostPort("127.0.0.1", th.LNDGRPCPort) + conn, err := net.DialTimeout("tcp", addr, time.Second) + if err != nil { + return false + } + + _ = conn.Close() + + return true + }, defaultTimeout, time.Second, th.Name+" LND TLS/gRPC not ready") +} + +// setupTapdPaths sets up the tapd-related paths and ports for the harness. +func (th *TapdHarness) setupTapdPaths() { + th.TapdGRPCPort = th.tapd.GetPort("10029/tcp") + th.TapdRestPort = th.tapd.GetPort("8089/tcp") + th.h.Logf("%s tapd gRPC=127.0.0.1:%s REST=127.0.0.1:%s", th.Name, + th.TapdGRPCPort, th.TapdRestPort) + + th.TapdTLSCert = filepath.Join(th.tapdDataDir, "tls.cert") + th.TapdMacaroon = filepath.Join( + th.tapdDataDir, "data", "regtest", "admin.macaroon", + ) + + th.h.Logf("Waiting for %s tapd to be ready and synced...", th.Name) + th.waitForTapdReady() + th.h.Logf("%s tapd is ready and synced", th.Name) +} + +// initAndWaitLND initializes the LND wallet and waits until it's active. +func (th *TapdHarness) initAndWaitLND() { + addr := net.JoinHostPort("127.0.0.1", th.LNDGRPCPort) + tlsCert, err := credentials.NewClientTLSFromFile(th.LNDTLSCert, "") + require.NoError(th.h.T, err, "failed to load "+th.Name+" lnd TLS") + + // Wait for LND's state service to report SERVER_ACTIVE. + th.h.Logf("Waiting for %s LND to reach SERVER_ACTIVE state...", th.Name) + require.Eventually(th.h.T, func() bool { + const checkTimeout = 5 * time.Second + ctx, cancel := context.WithTimeout( + context.Background(), checkTimeout, + ) + defer cancel() + + conn, err := grpc.DialContext( + ctx, addr, grpc.WithTransportCredentials(tlsCert), + grpc.WithBlock(), + ) + if err != nil { + return false + } + defer conn.Close() + + stateClient := lnrpc.NewStateClient(conn) + resp, err := stateClient.GetState( + ctx, &lnrpc.GetStateRequest{}, + ) + if err != nil { + return false + } + + return resp.State == lnrpc.WalletState_SERVER_ACTIVE + }, defaultTimeout, time.Second, th.Name+" LND not active") + + // Wait for macaroon file. + err = th.h.pool.Retry(func() error { + if _, err := os.Stat(th.LNDMacaroon); err != nil { + return err + } + + return nil + }) + require.NoError(th.h.T, err, th.Name+" LND admin macaroon not found") + + // Create lndclient. + lndClient, err := lndclient.NewLndServices( + &lndclient.LndServicesConfig{ + LndAddress: addr, + CustomMacaroonPath: th.LNDMacaroon, + TLSPath: th.LNDTLSCert, + Network: lndclient.NetworkRegtest, + }, + ) + require.NoError(th.h.T, err, "failed to create "+th.Name+" lnd client") + th.LND = &lndClient.LndServices + + // Wait for chain sync. Poll frequently (200ms) since chain sync + // typically completes quickly after wallet initialization. + require.Eventually(th.h.T, func() bool { + sync, err := th.LND.Client.GetInfo(context.Background()) + if err != nil { + return false + } + + return sync.SyncedToChain + }, defaultTimeout, pollInterval, th.Name+" LND not synced") +} + +// waitForTapdReady waits until tapd is ready and synced to chain. +func (th *TapdHarness) waitForTapdReady() { + // Wait for TLS cert and macaroon to be created. + require.Eventually(th.h.T, func() bool { + _, errCert := os.Stat(th.TapdTLSCert) + _, errMac := os.Stat(th.TapdMacaroon) + return errCert == nil && errMac == nil + }, defaultTimeout, time.Second, + th.Name+" tapd TLS cert or macaroon not found") + + // Wait for tapd to be ready and synced. + require.Eventually(th.h.T, func() bool { + const checkTimeout = 5 * time.Second + ctx, cancel := context.WithTimeout( + context.Background(), checkTimeout, + ) + defer cancel() + + addr := net.JoinHostPort("127.0.0.1", th.TapdGRPCPort) + conn, err := getTapdClientConn( + ctx, addr, th.TapdTLSCert, th.TapdMacaroon, + ) + if err != nil { + return false + } + defer conn.Close() + + client := taprpc.NewTaprootAssetsClient(conn) + resp, err := client.GetInfo(ctx, &taprpc.GetInfoRequest{}) + if err != nil { + return false + } + + return resp.SyncToChain + }, defaultTimeout, time.Second, th.Name+" tapd not ready or not synced") +} + +// Stop tears down the LND and tapd containers for this harness. +func (th *TapdHarness) Stop() { + th.h.Logf("Stopping %s TapdHarness...", th.Name) + + if th.tapd != nil { + err := th.h.pool.Client.KillContainer( + docker.KillContainerOptions{ + ID: th.tapd.Container.ID, + }, + ) + if err != nil { + th.h.Logf("failed to kill %s tapd: %v", th.Name, err) + } else { + th.h.Logf("%s tapd killed", th.Name) + } + } + + if th.lnd != nil { + err := th.h.pool.Client.KillContainer( + docker.KillContainerOptions{ + ID: th.lnd.Container.ID, + }, + ) + if err != nil { + th.h.Logf("failed to kill %s lnd: %v", th.Name, err) + } else { + th.h.Logf("%s lnd killed", th.Name) + } + } + + if th.tapd != nil { + err := th.h.pool.Purge(th.tapd) + if err != nil { + th.h.Logf("failed to purge %s tapd: %v", th.Name, err) + } + } + + if th.lnd != nil { + err := th.h.pool.Purge(th.lnd) + if err != nil { + th.h.Logf("failed to purge %s lnd: %v", th.Name, err) + } + } + + th.h.Logf("%s TapdHarness stopped", th.Name) +} + +// NewTapClientHarness creates a TapClientHarness connected to this +// TapdHarness's tapd instance. This is a stub for now as TapClientHarness +// is not yet ported. +func (th *TapdHarness) NewTapClientHarness() interface{} { + // TODO: Port TapClientHarness from tap-arktree when needed. + th.h.T.Fatal("NewTapClientHarness not yet implemented") + + return nil +}