From 996c4c7f90c7604865667b2f5f9735c3fc151089 Mon Sep 17 00:00:00 2001 From: Gustavo Stingelin Cardoso Filho Date: Fri, 10 Jul 2026 14:20:14 -0700 Subject: [PATCH 1/4] build: add sqlc generation checks In this commit, we add sqlc to the repository tool image and wire deterministic generation into the Makefile and CI. This gives each schema PR a mechanical check that the generated Go query surface matches the SQL checked into the tree. Extracted-from: e0666e0e500b4e278ee5f8c1529d6df4e35afbfc Extracted-from: b6fbbb603fb430843c16e96f12b3911fa94a3535 Extracted-from: 66cc4be3dcec0d37514a5f4f753a3732f20388e9 Extracted-from: bd468f6cdd5bbcbb85aa96e818d67be23ff6fa28 --- .github/workflows/main.yml | 3 + Makefile | 19 +++++ sqlc.yaml | 26 ++++++ tools/Dockerfile | 1 + tools/go.mod | 55 +++++++++++-- tools/go.sum | 162 ++++++++++++++++++++++++++++++++----- 6 files changed, 238 insertions(+), 28 deletions(-) create mode 100644 sqlc.yaml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7591492088..a2f50c0df8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -63,6 +63,9 @@ jobs: - name: Check RPC format run: make rpc-check + - name: Check generated SQL code + run: make sqlc-check + - name: compile code run: go install -v ./... diff --git a/Makefile b/Makefile index def43ca079..5a66b9b2f9 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ GOINSTALL := GO111MODULE=on go install -v GOFILES = $(shell find . -type f -name '*.go' -not -name "*.pb.go") +SQL_DIR := wallet/internal/sql + RM := rm -f CP := cp MAKE := make @@ -159,6 +161,21 @@ tidy-module: tidy-module-check: tidy-module if test -n "$$(git status --porcelain)"; then echo "modules not updated, please run `make tidy-module` again!"; git status; exit 1; fi +#? sqlc: Generate type-safe Go code for the SQL backends +sqlc: docker-tools + @$(call print, "Generating SQL models and queries.") + $(DOCKER_TOOLS) sqlc generate -f sqlc.yaml + +#? sqlc-check: Verify generated SQL code is up to date +sqlc-check: sqlc + @$(call print, "Verifying generated SQL code.") + @if test -n "$$(git status --porcelain --untracked-files=all -- $(SQL_DIR))"; then \ + echo "Generated SQL code is not up-to-date. Please run 'make sqlc' and commit the changes."; \ + git status --short -- $(SQL_DIR); \ + git diff -- $(SQL_DIR); \ + exit 1; \ + fi + .PHONY: all \ default \ build \ @@ -172,6 +189,8 @@ tidy-module-check: tidy-module fmt-check \ tidy-module \ tidy-module-check \ + sqlc \ + sqlc-check \ rpc-format \ lint \ lint-config-check \ diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000000..221770a34d --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,26 @@ +# This file configures sqlc generation for the SQLite and PostgreSQL backends. +version: "2" +sql: + - engine: "postgresql" + schema: "wallet/internal/sql/pg/migrations" + queries: "wallet/internal/sql/pg/queries" + gen: + go: + out: "wallet/internal/sql/pg/sqlc" + package: "sqlc" + sql_package: "database/sql" + emit_interface: true + emit_exported_queries: true + emit_prepared_queries: true + + - engine: "sqlite" + schema: "wallet/internal/sql/sqlite/migrations" + queries: "wallet/internal/sql/sqlite/queries" + gen: + go: + out: "wallet/internal/sql/sqlite/sqlc" + package: "sqlc" + sql_package: "database/sql" + emit_interface: true + emit_exported_queries: true + emit_prepared_queries: true diff --git a/tools/Dockerfile b/tools/Dockerfile index bde0dbf984..29c4bf69dd 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -11,6 +11,7 @@ COPY tools/go.mod tools/go.sum ./ RUN go install -trimpath \ github.com/golangci/golangci-lint/v2/cmd/golangci-lint \ github.com/rinchsan/gosimports/cmd/gosimports \ + github.com/sqlc-dev/sqlc/cmd/sqlc \ && rm -rf /go/pkg/mod \ && rm -rf /root/.cache/go-build \ && rm -rf /go/src \ diff --git a/tools/go.mod b/tools/go.mod index 306d9a6b4b..a9d6fde41a 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -5,14 +5,17 @@ go 1.25.11 tool ( github.com/golangci/golangci-lint/v2/cmd/golangci-lint github.com/rinchsan/gosimports/cmd/gosimports + github.com/sqlc-dev/sqlc/cmd/sqlc ) require ( 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect + cel.dev/expr v0.25.1 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect dev.gaijin.team/go/golib v0.6.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/4meepo/tagalign v1.4.3 // indirect github.com/Abirdcfly/dupword v0.1.6 // indirect github.com/AlwxSin/noinlineerr v1.0.5 // indirect @@ -30,6 +33,7 @@ require ( github.com/alfatraining/structtag v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/alingse/nilnesserr v0.2.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/ashanbrown/forbidigo/v2 v2.1.0 // indirect github.com/ashanbrown/makezero/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -52,12 +56,14 @@ require ( github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/ckaznocha/intrange v0.3.1 // indirect + github.com/cubicdaiya/gonp v1.0.4 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.7 // indirect github.com/dave/dst v0.27.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect @@ -66,6 +72,7 @@ require ( github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.15 // indirect github.com/go-critic/go-critic v0.13.0 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -77,7 +84,7 @@ require ( github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect @@ -89,7 +96,9 @@ require ( github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect + github.com/google/cel-go v0.26.1 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.5.0 // indirect @@ -101,8 +110,13 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.5 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jgautheron/goconst v1.8.2 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/jjti/go-spancheck v0.6.5 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect @@ -135,11 +149,17 @@ require ( github.com/moricho/tparallel v0.3.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/nakabonne/nestif v0.3.1 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.20.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect + github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect + github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect + github.com/pingcap/log v1.1.0 // indirect + github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.8.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect @@ -152,8 +172,10 @@ require ( github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.2.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rinchsan/gosimports v0.3.8 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/riza-io/grpc-go v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.4.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect @@ -172,12 +194,15 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.7 // indirect github.com/spf13/viper v1.12.0 // indirect + github.com/sqlc-dev/sqlc v1.30.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tetafro/godot v1.5.1 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect github.com/timonwong/loggercheck v0.11.0 // indirect github.com/tomarrell/wrapcheck/v2 v2.11.0 // indirect @@ -186,6 +211,8 @@ require ( github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.0 // indirect github.com/uudashr/iface v1.4.1 // indirect + github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect + github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect github.com/xen0n/gosmopolitan v1.3.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yagipy/maintidx v1.0.0 // indirect @@ -196,20 +223,32 @@ require ( go-simpler.org/sloglint v0.11.1 // indirect go.augendre.info/arangolint v0.2.0 // indirect go.augendre.info/fatcontext v0.8.1 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - go.uber.org/multierr v1.10.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - golang.org/x/tools v0.36.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/tools v0.44.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.6.1 // indirect + modernc.org/libc v1.66.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.2 // indirect mvdan.cc/gofumpt v0.8.0 // indirect mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect ) diff --git a/tools/go.sum b/tools/go.sum index 58b944c06d..ca012f1219 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -2,6 +2,8 @@ 4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -41,6 +43,8 @@ dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88 dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo= dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8= github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c= github.com/Abirdcfly/dupword v0.1.6 h1:qeL6u0442RPRe3mcaLcbaCi2/Y/hOcdtw6DE9odjz9c= @@ -86,12 +90,15 @@ github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQ github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/alingse/nilnesserr v0.2.0 h1:raLem5KG7EFVb4UIDAXgrv3N2JIaffeKNtcEXkEWd/w= github.com/alingse/nilnesserr v0.2.0/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/ashanbrown/forbidigo/v2 v2.1.0 h1:NAxZrWqNUQiDz19FKScQ/xvwzmij6BiOw3S0+QUQ+Hs= github.com/ashanbrown/forbidigo/v2 v2.1.0/go.mod h1:0zZfdNAuZIL7rSComLGthgc/9/n2FqspBOH90xlCHdA= github.com/ashanbrown/makezero/v2 v2.0.1 h1:r8GtKetWOgoJ4sLyUx97UTwyt2dO7WkGFHizn/Lo8TY= github.com/ashanbrown/makezero/v2 v2.0.1/go.mod h1:kKU4IMxmYW1M4fiEHMb2vc5SFoPzXvgbMR9gIp5pjSw= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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= @@ -141,6 +148,8 @@ github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nW 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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= +github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/daixiang0/gci v0.13.7 h1:+0bG5eK9vlI08J+J/NWGbWPTNiXPG4WhNLJOkSxWITQ= @@ -156,6 +165,8 @@ github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42 github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +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= @@ -189,8 +200,12 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/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-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -249,8 +264,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw 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.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0= github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ= github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= @@ -275,6 +290,8 @@ github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqt github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e/go.mod h1:h+wZwLjUTJnm/P2rwlbJdRPZXOzaT36/FwnPnY2inzc= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= +github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -301,6 +318,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= @@ -336,10 +355,20 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= +github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +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/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4= github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8= github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -433,6 +462,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +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/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= @@ -454,6 +485,17 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= +github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= +github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0orE1ndHkXvX6Qdq2cZn8gAPir8ymKZk= +github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4= +github.com/pingcap/log v1.1.0 h1:ELiPxACz7vdo1qAvvaWJg1NrYFoY6gqAh/+Uo6aXdD8= +github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= +github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 h1:W3rpAI3bubR6VWOcwxDIG0Gz9G5rl5b3SL116T0vBt0= +github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE= 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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -497,11 +539,15 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +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/rinchsan/gosimports v0.3.8 h1:X4Pb9yFf6teHvogorT04yK/0W2Df7eHO79biCcYrA4c= github.com/rinchsan/gosimports v0.3.8/go.mod h1:t0567k69sUHjLvJMPDsV31THZC+8UIbY1oL7NW+0I2c= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= +github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -549,10 +595,14 @@ github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/sqlc-dev/sqlc v1.30.0 h1:H4HrNwPc0hntxGWzAbhlfplPRN4bQpXFx+CaEMcKz6c= +github.com/sqlc-dev/sqlc v1.30.0/go.mod h1:QnEN+npugyhUg1A+1kkYM3jc2OMOFsNlZ1eh8mdhad0= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -560,6 +610,7 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -571,6 +622,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.5.1 h1:PZnjCol4+FqaEzvZg5+O8IY2P3hfY9JzRBNPv1pEDS4= github.com/tetafro/godot v1.5.1/go.mod h1:cCdPtEndkmqqrhiCfkmxDodMQJ/f3L1BCNskCUZdTwk= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk= github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M= @@ -587,6 +640,10 @@ github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYR github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU= github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= github.com/xen0n/gosmopolitan v1.3.0 h1:zAZI1zefvo7gcpbCOrPSHJZJYA9ZgLfJqtKzZ5pHqQM= github.com/xen0n/gosmopolitan v1.3.0/go.mod h1:rckfr5T6o4lBtM1ga7mLGKZmLxswUoH1zxHgNXOsEt4= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= @@ -621,12 +678,33 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 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.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -638,6 +716,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -648,8 +728,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b h1:KdrhdYPDUvJTvrDK9gdjfFd6JTk8vA1WJoldYSi0kHo= @@ -681,8 +761,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= 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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -721,8 +801,8 @@ 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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -744,8 +824,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -797,8 +877,8 @@ 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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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= @@ -815,8 +895,8 @@ 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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -835,6 +915,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -874,8 +956,8 @@ 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.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -884,6 +966,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -935,6 +1019,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 h1:admdQBe8jR3VWhBsUrAOaF2Qw6K/+p5pSm1GN8+6Fw4= +google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800/go.mod h1:FPk7EXUKMtImne7AmknoYjT4QXqKIzzRbeQIXzLk6fQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -947,6 +1035,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= 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= @@ -959,8 +1049,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -970,14 +1061,19 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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.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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -989,6 +1085,32 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= +modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= +modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= +modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek= +modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= +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= mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k= mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg= mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8= From 9471482ea26fb76d5e86dc08932804828ed0586e Mon Sep 17 00:00:00 2001 From: Mohamed Awnallah Date: Fri, 10 Jul 2026 14:20:36 -0700 Subject: [PATCH 2/4] wallet/sql: add block schema foundation In this commit, we add the first SQLite + PostgreSQL schema slice for the port-first wallet. The block table uses the chain height as its natural key, keeps the backend-specific byte types explicit, and generates the same query surface for both backends. This lands only SQL assets and generated accessors. It does not route the wallet through a new store or change any public wallet API. Extracted-from: f6e0258e080337e7d598b2f1a328c9a3b8b3a9c5 Extracted-from: 3d67ecd5aebc1cbe825bce72ae869530b6692e71 Extracted-from: e773de5b082d11d5720b09502b891c184794a7df Extracted-from: 69e459bc66c5daf00f19eedcf500a694a65d27e1 Extracted-from: 594bb0d4ac0518b0d02be2899e87ae4bf89a1b34 Co-authored-by: Gustavo Stingelin Co-authored-by: yyforyongyu --- wallet/internal/sql/pg/doc.go | 2 + .../sql/pg/migrations/000001_blocks.down.sql | 2 + .../sql/pg/migrations/000001_blocks.up.sql | 11 ++ wallet/internal/sql/pg/queries/blocks.sql | 28 +++++ wallet/internal/sql/pg/sqlc/blocks.sql.go | 94 ++++++++++++++ wallet/internal/sql/pg/sqlc/db.go | 118 ++++++++++++++++++ wallet/internal/sql/pg/sqlc/models.go | 11 ++ wallet/internal/sql/pg/sqlc/querier.go | 18 +++ wallet/internal/sql/sqlite/doc.go | 2 + .../sqlite/migrations/000001_blocks.down.sql | 2 + .../sqlite/migrations/000001_blocks.up.sql | 11 ++ wallet/internal/sql/sqlite/queries/blocks.sql | 26 ++++ wallet/internal/sql/sqlite/sqlc/blocks.sql.go | 92 ++++++++++++++ wallet/internal/sql/sqlite/sqlc/db.go | 118 ++++++++++++++++++ wallet/internal/sql/sqlite/sqlc/models.go | 11 ++ wallet/internal/sql/sqlite/sqlc/querier.go | 18 +++ 16 files changed, 564 insertions(+) create mode 100644 wallet/internal/sql/pg/doc.go create mode 100644 wallet/internal/sql/pg/migrations/000001_blocks.down.sql create mode 100644 wallet/internal/sql/pg/migrations/000001_blocks.up.sql create mode 100644 wallet/internal/sql/pg/queries/blocks.sql create mode 100644 wallet/internal/sql/pg/sqlc/blocks.sql.go create mode 100644 wallet/internal/sql/pg/sqlc/db.go create mode 100644 wallet/internal/sql/pg/sqlc/models.go create mode 100644 wallet/internal/sql/pg/sqlc/querier.go create mode 100644 wallet/internal/sql/sqlite/doc.go create mode 100644 wallet/internal/sql/sqlite/migrations/000001_blocks.down.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000001_blocks.up.sql create mode 100644 wallet/internal/sql/sqlite/queries/blocks.sql create mode 100644 wallet/internal/sql/sqlite/sqlc/blocks.sql.go create mode 100644 wallet/internal/sql/sqlite/sqlc/db.go create mode 100644 wallet/internal/sql/sqlite/sqlc/models.go create mode 100644 wallet/internal/sql/sqlite/sqlc/querier.go diff --git a/wallet/internal/sql/pg/doc.go b/wallet/internal/sql/pg/doc.go new file mode 100644 index 0000000000..150fc0d2a1 --- /dev/null +++ b/wallet/internal/sql/pg/doc.go @@ -0,0 +1,2 @@ +// Package pg contains PostgreSQL schema assets and generated queries. +package pg diff --git a/wallet/internal/sql/pg/migrations/000001_blocks.down.sql b/wallet/internal/sql/pg/migrations/000001_blocks.down.sql new file mode 100644 index 0000000000..5b8a4868ac --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000001_blocks.down.sql @@ -0,0 +1,2 @@ +-- The rollback removes the blocks table if it exists. +DROP TABLE IF EXISTS blocks; diff --git a/wallet/internal/sql/pg/migrations/000001_blocks.up.sql b/wallet/internal/sql/pg/migrations/000001_blocks.up.sql new file mode 100644 index 0000000000..2a81436e9c --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000001_blocks.up.sql @@ -0,0 +1,11 @@ +-- The blocks table tracks blocks that contain wallet transactions. +CREATE TABLE blocks ( + -- The block height is the natural key for a main-chain position. + block_height INTEGER PRIMARY KEY CHECK (block_height >= 0), + + -- The header hash identifies the block at this height. + header_hash BYTEA NOT NULL UNIQUE CHECK (length(header_hash) = 32), + + -- The block timestamp is stored as Unix seconds. + block_timestamp BIGINT NOT NULL CHECK (block_timestamp >= 0) +); diff --git a/wallet/internal/sql/pg/queries/blocks.sql b/wallet/internal/sql/pg/queries/blocks.sql new file mode 100644 index 0000000000..7b06a12905 --- /dev/null +++ b/wallet/internal/sql/pg/queries/blocks.sql @@ -0,0 +1,28 @@ +-- name: GetBlockByHeight :one +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE block_height = $1; + +-- name: GetBlocksInRange :many +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE + block_height BETWEEN + sqlc.arg('start_height')::INTEGER + AND sqlc.arg('end_height')::INTEGER +ORDER BY block_height; + +-- name: InsertBlock :exec +INSERT INTO blocks (block_height, header_hash, block_timestamp) +VALUES ($1, $2, $3) +ON CONFLICT (block_height) DO NOTHING; + +-- name: DeleteBlock :exec +DELETE FROM blocks +WHERE block_height = $1; diff --git a/wallet/internal/sql/pg/sqlc/blocks.sql.go b/wallet/internal/sql/pg/sqlc/blocks.sql.go new file mode 100644 index 0000000000..7c3e819490 --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/blocks.sql.go @@ -0,0 +1,94 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: blocks.sql + +package sqlc + +import ( + "context" +) + +const DeleteBlock = `-- name: DeleteBlock :exec +DELETE FROM blocks +WHERE block_height = $1 +` + +func (q *Queries) DeleteBlock(ctx context.Context, blockHeight int32) error { + _, err := q.exec(ctx, q.deleteBlockStmt, DeleteBlock, blockHeight) + return err +} + +const GetBlockByHeight = `-- name: GetBlockByHeight :one +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE block_height = $1 +` + +func (q *Queries) GetBlockByHeight(ctx context.Context, blockHeight int32) (Block, error) { + row := q.queryRow(ctx, q.getBlockByHeightStmt, GetBlockByHeight, blockHeight) + var i Block + err := row.Scan(&i.BlockHeight, &i.HeaderHash, &i.BlockTimestamp) + return i, err +} + +const GetBlocksInRange = `-- name: GetBlocksInRange :many +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE + block_height BETWEEN + $1::INTEGER + AND $2::INTEGER +ORDER BY block_height +` + +type GetBlocksInRangeParams struct { + StartHeight int32 + EndHeight int32 +} + +func (q *Queries) GetBlocksInRange(ctx context.Context, arg GetBlocksInRangeParams) ([]Block, error) { + rows, err := q.query(ctx, q.getBlocksInRangeStmt, GetBlocksInRange, arg.StartHeight, arg.EndHeight) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Block + for rows.Next() { + var i Block + if err := rows.Scan(&i.BlockHeight, &i.HeaderHash, &i.BlockTimestamp); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const InsertBlock = `-- name: InsertBlock :exec +INSERT INTO blocks (block_height, header_hash, block_timestamp) +VALUES ($1, $2, $3) +ON CONFLICT (block_height) DO NOTHING +` + +type InsertBlockParams struct { + BlockHeight int32 + HeaderHash []byte + BlockTimestamp int64 +} + +func (q *Queries) InsertBlock(ctx context.Context, arg InsertBlockParams) error { + _, err := q.exec(ctx, q.insertBlockStmt, InsertBlock, arg.BlockHeight, arg.HeaderHash, arg.BlockTimestamp) + return err +} diff --git a/wallet/internal/sql/pg/sqlc/db.go b/wallet/internal/sql/pg/sqlc/db.go new file mode 100644 index 0000000000..168d8395d4 --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/db.go @@ -0,0 +1,118 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package sqlc + +import ( + "context" + "database/sql" + "fmt" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +func Prepare(ctx context.Context, db DBTX) (*Queries, error) { + q := Queries{db: db} + var err error + if q.deleteBlockStmt, err = db.PrepareContext(ctx, DeleteBlock); err != nil { + return nil, fmt.Errorf("error preparing query DeleteBlock: %w", err) + } + if q.getBlockByHeightStmt, err = db.PrepareContext(ctx, GetBlockByHeight); err != nil { + return nil, fmt.Errorf("error preparing query GetBlockByHeight: %w", err) + } + if q.getBlocksInRangeStmt, err = db.PrepareContext(ctx, GetBlocksInRange); err != nil { + return nil, fmt.Errorf("error preparing query GetBlocksInRange: %w", err) + } + if q.insertBlockStmt, err = db.PrepareContext(ctx, InsertBlock); err != nil { + return nil, fmt.Errorf("error preparing query InsertBlock: %w", err) + } + return &q, nil +} + +func (q *Queries) Close() error { + var err error + if q.deleteBlockStmt != nil { + if cerr := q.deleteBlockStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteBlockStmt: %w", cerr) + } + } + if q.getBlockByHeightStmt != nil { + if cerr := q.getBlockByHeightStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getBlockByHeightStmt: %w", cerr) + } + } + if q.getBlocksInRangeStmt != nil { + if cerr := q.getBlocksInRangeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getBlocksInRangeStmt: %w", cerr) + } + } + if q.insertBlockStmt != nil { + if cerr := q.insertBlockStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing insertBlockStmt: %w", cerr) + } + } + return err +} + +func (q *Queries) exec(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (sql.Result, error) { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).ExecContext(ctx, args...) + case stmt != nil: + return stmt.ExecContext(ctx, args...) + default: + return q.db.ExecContext(ctx, query, args...) + } +} + +func (q *Queries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Rows, error) { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).QueryContext(ctx, args...) + case stmt != nil: + return stmt.QueryContext(ctx, args...) + default: + return q.db.QueryContext(ctx, query, args...) + } +} + +func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) *sql.Row { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...) + case stmt != nil: + return stmt.QueryRowContext(ctx, args...) + default: + return q.db.QueryRowContext(ctx, query, args...) + } +} + +type Queries struct { + db DBTX + tx *sql.Tx + deleteBlockStmt *sql.Stmt + getBlockByHeightStmt *sql.Stmt + getBlocksInRangeStmt *sql.Stmt + insertBlockStmt *sql.Stmt +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + tx: tx, + deleteBlockStmt: q.deleteBlockStmt, + getBlockByHeightStmt: q.getBlockByHeightStmt, + getBlocksInRangeStmt: q.getBlocksInRangeStmt, + insertBlockStmt: q.insertBlockStmt, + } +} diff --git a/wallet/internal/sql/pg/sqlc/models.go b/wallet/internal/sql/pg/sqlc/models.go new file mode 100644 index 0000000000..9b53f59718 --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/models.go @@ -0,0 +1,11 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package sqlc + +type Block struct { + BlockHeight int32 + HeaderHash []byte + BlockTimestamp int64 +} diff --git a/wallet/internal/sql/pg/sqlc/querier.go b/wallet/internal/sql/pg/sqlc/querier.go new file mode 100644 index 0000000000..8ba410fea5 --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/querier.go @@ -0,0 +1,18 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package sqlc + +import ( + "context" +) + +type Querier interface { + DeleteBlock(ctx context.Context, blockHeight int32) error + GetBlockByHeight(ctx context.Context, blockHeight int32) (Block, error) + GetBlocksInRange(ctx context.Context, arg GetBlocksInRangeParams) ([]Block, error) + InsertBlock(ctx context.Context, arg InsertBlockParams) error +} + +var _ Querier = (*Queries)(nil) diff --git a/wallet/internal/sql/sqlite/doc.go b/wallet/internal/sql/sqlite/doc.go new file mode 100644 index 0000000000..b0cfff74c4 --- /dev/null +++ b/wallet/internal/sql/sqlite/doc.go @@ -0,0 +1,2 @@ +// Package sqlite contains SQLite schema assets and generated queries. +package sqlite diff --git a/wallet/internal/sql/sqlite/migrations/000001_blocks.down.sql b/wallet/internal/sql/sqlite/migrations/000001_blocks.down.sql new file mode 100644 index 0000000000..5b8a4868ac --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000001_blocks.down.sql @@ -0,0 +1,2 @@ +-- The rollback removes the blocks table if it exists. +DROP TABLE IF EXISTS blocks; diff --git a/wallet/internal/sql/sqlite/migrations/000001_blocks.up.sql b/wallet/internal/sql/sqlite/migrations/000001_blocks.up.sql new file mode 100644 index 0000000000..7e90178fca --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000001_blocks.up.sql @@ -0,0 +1,11 @@ +-- The blocks table tracks blocks that contain wallet transactions. +CREATE TABLE blocks ( + -- The block height is the natural key for a main-chain position. + block_height INTEGER PRIMARY KEY CHECK (block_height >= 0), + + -- The header hash identifies the block at this height. + header_hash BLOB NOT NULL UNIQUE CHECK (length(header_hash) = 32), + + -- The block timestamp is stored as Unix seconds. + block_timestamp INTEGER NOT NULL CHECK (block_timestamp >= 0) +); diff --git a/wallet/internal/sql/sqlite/queries/blocks.sql b/wallet/internal/sql/sqlite/queries/blocks.sql new file mode 100644 index 0000000000..29fe7828c2 --- /dev/null +++ b/wallet/internal/sql/sqlite/queries/blocks.sql @@ -0,0 +1,26 @@ +-- name: GetBlockByHeight :one +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE block_height = ?; + +-- name: GetBlocksInRange :many +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE + block_height >= cast(sqlc.arg('start_height') AS INTEGER) + AND block_height <= cast(sqlc.arg('end_height') AS INTEGER) +ORDER BY block_height; + +-- name: InsertBlock :exec +INSERT OR IGNORE INTO blocks (block_height, header_hash, block_timestamp) +VALUES (?, ?, ?); + +-- name: DeleteBlock :exec +DELETE FROM blocks +WHERE block_height = ?; diff --git a/wallet/internal/sql/sqlite/sqlc/blocks.sql.go b/wallet/internal/sql/sqlite/sqlc/blocks.sql.go new file mode 100644 index 0000000000..e5b8eeaa41 --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/blocks.sql.go @@ -0,0 +1,92 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: blocks.sql + +package sqlc + +import ( + "context" +) + +const DeleteBlock = `-- name: DeleteBlock :exec +DELETE FROM blocks +WHERE block_height = ? +` + +func (q *Queries) DeleteBlock(ctx context.Context, blockHeight int64) error { + _, err := q.exec(ctx, q.deleteBlockStmt, DeleteBlock, blockHeight) + return err +} + +const GetBlockByHeight = `-- name: GetBlockByHeight :one +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE block_height = ? +` + +func (q *Queries) GetBlockByHeight(ctx context.Context, blockHeight int64) (Block, error) { + row := q.queryRow(ctx, q.getBlockByHeightStmt, GetBlockByHeight, blockHeight) + var i Block + err := row.Scan(&i.BlockHeight, &i.HeaderHash, &i.BlockTimestamp) + return i, err +} + +const GetBlocksInRange = `-- name: GetBlocksInRange :many +SELECT + block_height, + header_hash, + block_timestamp +FROM blocks +WHERE + block_height >= cast(?1 AS INTEGER) + AND block_height <= cast(?2 AS INTEGER) +ORDER BY block_height +` + +type GetBlocksInRangeParams struct { + StartHeight int64 + EndHeight int64 +} + +func (q *Queries) GetBlocksInRange(ctx context.Context, arg GetBlocksInRangeParams) ([]Block, error) { + rows, err := q.query(ctx, q.getBlocksInRangeStmt, GetBlocksInRange, arg.StartHeight, arg.EndHeight) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Block + for rows.Next() { + var i Block + if err := rows.Scan(&i.BlockHeight, &i.HeaderHash, &i.BlockTimestamp); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const InsertBlock = `-- name: InsertBlock :exec +INSERT OR IGNORE INTO blocks (block_height, header_hash, block_timestamp) +VALUES (?, ?, ?) +` + +type InsertBlockParams struct { + BlockHeight int64 + HeaderHash []byte + BlockTimestamp int64 +} + +func (q *Queries) InsertBlock(ctx context.Context, arg InsertBlockParams) error { + _, err := q.exec(ctx, q.insertBlockStmt, InsertBlock, arg.BlockHeight, arg.HeaderHash, arg.BlockTimestamp) + return err +} diff --git a/wallet/internal/sql/sqlite/sqlc/db.go b/wallet/internal/sql/sqlite/sqlc/db.go new file mode 100644 index 0000000000..168d8395d4 --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/db.go @@ -0,0 +1,118 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package sqlc + +import ( + "context" + "database/sql" + "fmt" +) + +type DBTX interface { + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +func Prepare(ctx context.Context, db DBTX) (*Queries, error) { + q := Queries{db: db} + var err error + if q.deleteBlockStmt, err = db.PrepareContext(ctx, DeleteBlock); err != nil { + return nil, fmt.Errorf("error preparing query DeleteBlock: %w", err) + } + if q.getBlockByHeightStmt, err = db.PrepareContext(ctx, GetBlockByHeight); err != nil { + return nil, fmt.Errorf("error preparing query GetBlockByHeight: %w", err) + } + if q.getBlocksInRangeStmt, err = db.PrepareContext(ctx, GetBlocksInRange); err != nil { + return nil, fmt.Errorf("error preparing query GetBlocksInRange: %w", err) + } + if q.insertBlockStmt, err = db.PrepareContext(ctx, InsertBlock); err != nil { + return nil, fmt.Errorf("error preparing query InsertBlock: %w", err) + } + return &q, nil +} + +func (q *Queries) Close() error { + var err error + if q.deleteBlockStmt != nil { + if cerr := q.deleteBlockStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteBlockStmt: %w", cerr) + } + } + if q.getBlockByHeightStmt != nil { + if cerr := q.getBlockByHeightStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getBlockByHeightStmt: %w", cerr) + } + } + if q.getBlocksInRangeStmt != nil { + if cerr := q.getBlocksInRangeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getBlocksInRangeStmt: %w", cerr) + } + } + if q.insertBlockStmt != nil { + if cerr := q.insertBlockStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing insertBlockStmt: %w", cerr) + } + } + return err +} + +func (q *Queries) exec(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (sql.Result, error) { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).ExecContext(ctx, args...) + case stmt != nil: + return stmt.ExecContext(ctx, args...) + default: + return q.db.ExecContext(ctx, query, args...) + } +} + +func (q *Queries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Rows, error) { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).QueryContext(ctx, args...) + case stmt != nil: + return stmt.QueryContext(ctx, args...) + default: + return q.db.QueryContext(ctx, query, args...) + } +} + +func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) *sql.Row { + switch { + case stmt != nil && q.tx != nil: + return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...) + case stmt != nil: + return stmt.QueryRowContext(ctx, args...) + default: + return q.db.QueryRowContext(ctx, query, args...) + } +} + +type Queries struct { + db DBTX + tx *sql.Tx + deleteBlockStmt *sql.Stmt + getBlockByHeightStmt *sql.Stmt + getBlocksInRangeStmt *sql.Stmt + insertBlockStmt *sql.Stmt +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + tx: tx, + deleteBlockStmt: q.deleteBlockStmt, + getBlockByHeightStmt: q.getBlockByHeightStmt, + getBlocksInRangeStmt: q.getBlocksInRangeStmt, + insertBlockStmt: q.insertBlockStmt, + } +} diff --git a/wallet/internal/sql/sqlite/sqlc/models.go b/wallet/internal/sql/sqlite/sqlc/models.go new file mode 100644 index 0000000000..d99a13623f --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/models.go @@ -0,0 +1,11 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package sqlc + +type Block struct { + BlockHeight int64 + HeaderHash []byte + BlockTimestamp int64 +} diff --git a/wallet/internal/sql/sqlite/sqlc/querier.go b/wallet/internal/sql/sqlite/sqlc/querier.go new file mode 100644 index 0000000000..815602423a --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/querier.go @@ -0,0 +1,18 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package sqlc + +import ( + "context" +) + +type Querier interface { + DeleteBlock(ctx context.Context, blockHeight int64) error + GetBlockByHeight(ctx context.Context, blockHeight int64) (Block, error) + GetBlocksInRange(ctx context.Context, arg GetBlocksInRangeParams) ([]Block, error) + InsertBlock(ctx context.Context, arg InsertBlockParams) error +} + +var _ Querier = (*Queries)(nil) From bfad4c47da0628d09cde864d02d00d61944ce857 Mon Sep 17 00:00:00 2001 From: Gustavo Stingelin Date: Fri, 10 Jul 2026 14:26:57 -0700 Subject: [PATCH 3/4] wallet/sql: add database migration foundation In this commit, we add bounded SQLite and PostgreSQL connection pools and an embedded migration runner that can move the schema from empty to head, back to empty, then forward again. We use the released lnd/sqldb v1.0.13 module for shared SQLite defaults and make its transaction, error, and query utilities available to the later store adapter. The wallet schema and rollback-capable migration runner stay local because the concrete lnd stores own lnd-specific schema and migration ordering. Extracted-from: de762f088e085f72b4268abce51ad6e26bda3dc2 Extracted-from: 2477f8031acaf0a376728c8a7b6ec41f466d0456 Extracted-from: 12bedf64aebf1d3abadb3ded6fd14ef4732da7e5 Extracted-from: 9bf5e56eb45f4eaacce0eaa5b4c57d5d4b6471bd Extracted-from: aa1725a8755db62a8746e46ee8894ab5df3d828a Extracted-from: d7a5127cc08edb68455f1e7ece45564df3fee487 Extracted-from: 7074419bfe3f7dcd393e302d6201177d3e35df49 Extracted-from: 2be43f4108cc143abb83dd8e35f8e5d4d0667fa3 Co-authored-by: Viktor Torstensson Co-authored-by: yyforyongyu --- docs/developer/sql_port_foundation.md | 65 ++++ go.mod | 107 ++++++- go.sum | 294 +++++++++++++++--- wallet/internal/sql/pg/config.go | 117 +++++++ wallet/internal/sql/pg/config_test.go | 93 ++++++ wallet/internal/sql/pg/db.go | 42 +++ wallet/internal/sql/pg/migrations.go | 109 +++++++ wallet/internal/sql/pg/migrations_test.go | 73 +++++ wallet/internal/sql/sqlite/config.go | 79 +++++ wallet/internal/sql/sqlite/db.go | 57 ++++ wallet/internal/sql/sqlite/migrations.go | 64 ++++ wallet/internal/sql/sqlite/migrations_test.go | 45 +++ 12 files changed, 1089 insertions(+), 56 deletions(-) create mode 100644 docs/developer/sql_port_foundation.md create mode 100644 wallet/internal/sql/pg/config.go create mode 100644 wallet/internal/sql/pg/config_test.go create mode 100644 wallet/internal/sql/pg/db.go create mode 100644 wallet/internal/sql/pg/migrations.go create mode 100644 wallet/internal/sql/pg/migrations_test.go create mode 100644 wallet/internal/sql/sqlite/config.go create mode 100644 wallet/internal/sql/sqlite/db.go create mode 100644 wallet/internal/sql/sqlite/migrations.go create mode 100644 wallet/internal/sql/sqlite/migrations_test.go diff --git a/docs/developer/sql_port_foundation.md b/docs/developer/sql_port_foundation.md new file mode 100644 index 0000000000..5072e33aca --- /dev/null +++ b/docs/developer/sql_port_foundation.md @@ -0,0 +1,65 @@ +# SQL Port Foundation + +## Scope + +In this stage, we add the SQL build, connection, and migration foundation +without changing how `Wallet`, `waddrmgr`, or `wtxmgr` behave. The existing KV +path remains the reference implementation. SQLite and PostgreSQL are new +internal persistence targets behind the same wallet API. + +The first schema slice contains block metadata only. Wallet, account, address, +transaction, UTXO, and lease tables land in later commits once their migration +contract has been checked against the current KV representation. + +## `lnd/sqldb` + +This stage uses the released `github.com/lightningnetwork/lnd/sqldb` module at +`v1.0.13` as the common SQL utility layer. In particular, the SQLite connection +defaults come directly from that module. Its transaction executor, retry +policy, backend-neutral error mapping, nullable-value helpers, and pagination +helpers are available to the later store adapter without copying those +utilities into btcwallet. + +The schema and migration runners remain local to btcwallet. The concrete lnd +stores embed lnd's own schema and global migration ordering, while btcwallet +needs backend-specific triggers, functions, sequences, and table constraints. +The local wrappers also expose the down/target operations used by the rollback +gate. Sharing the utility layer without sharing the application schema keeps +the dependency boundary narrow. + +We also evaluated the nested `github.com/lightningnetwork/lnd/sqldb/v2` +development module. It has useful connection work, but it has no released v2 +tag and currently relies on a module-local `golang-migrate` replacement that +would not propagate to btcwallet. We therefore use the stable v1 module now +and leave a v2 upgrade as a normal dependency update once that line is +released. + +## Compatibility Constraints + +The wallet/address schema cannot be copied from `sql-wallet` unchanged. That +branch intentionally combines the storage port with a new encryption and +address-history model. + +The port-first schema keeps the current dual-passphrase hierarchy. It must +represent the master public/private KDF parameters, all three encrypted crypto +keys, encrypted master HD public/private keys, and encrypted public/private +material at the scope, account, and address layers. Watch-only wallets retain +the current nullability rules, including the script fallback through the public +crypto key. + +The port-first schema also persists the legacy sticky address-used bit. KV can +mark an address used without retaining enough transaction or UTXO history to +derive that fact later. Deriving used-ness from `EXISTS(utxos)` would therefore +change `LastUnusedAddress` and gap-limit behavior after migration. + +These are storage compatibility requirements, not endorsements of the legacy +model forever. The single-passphrase/key-vault and history-derived address +designs can return as separate refactors after the SQL port has migration and +`lnd` end-to-end coverage. + +## Verification + +Every schema slice must regenerate cleanly with `make sqlc-check`. SQLite and +PostgreSQL migrations must both pass an empty-to-head, head-to-empty, and +empty-to-head round trip. No PR in this stage may change an exported wallet API +or require an `lnd` source adaptation. diff --git a/go.mod b/go.mod index 54a3beb083..d11fcc030d 100644 --- a/go.mod +++ b/go.mod @@ -17,48 +17,123 @@ require ( github.com/btcsuite/btcwallet/walletdb v1.6.0 github.com/btcsuite/btcwallet/wtxmgr v1.6.0 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 - github.com/davecgh/go-spew v1.1.1 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 + github.com/golang-migrate/migrate/v4 v4.19.1 github.com/golang/protobuf v1.5.4 + github.com/jackc/pgx/v5 v5.10.0 github.com/jessevdk/go-flags v1.6.1 github.com/jrick/logrotate v1.1.2 github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf github.com/lightninglabs/neutrino v0.18.0 github.com/lightninglabs/neutrino/cache v1.1.4 github.com/lightningnetwork/lnd/fn/v2 v2.0.8 + github.com/lightningnetwork/lnd/sqldb v1.0.13 github.com/lightningnetwork/lnd/ticker v1.1.1 github.com/lightningnetwork/lnd/tlv v1.3.3-0.20260615022959-a067468f0f45 - github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.41.0 - golang.org/x/net v0.43.0 - golang.org/x/sync v0.16.0 - golang.org/x/term v0.34.0 - google.golang.org/grpc v1.73.0 - google.golang.org/protobuf v1.36.6 + github.com/stretchr/testify v1.11.1 + github.com/testcontainers/testcontainers-go v0.40.0 + github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 + golang.org/x/crypto v0.51.0 + golang.org/x/net v0.53.0 + golang.org/x/sync v0.20.0 + golang.org/x/term v0.43.0 + google.golang.org/grpc v1.80.0 + google.golang.org/protobuf v1.36.11 + modernc.org/sqlite v1.53.0 ) require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/aead/siphash v1.0.1 // indirect github.com/btcsuite/btcd/v2transport v1.0.1 // indirect + github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/continuity v0.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/creack/pty v1.1.24 // indirect github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/decred/dcrd/lru v1.1.3 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/cli v28.1.1+incompatible // indirect + github.com/docker/docker v28.5.1+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect + github.com/jackc/pgio v1.0.0 // indirect + 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/puddle/v2 v2.2.2 // indirect github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee // indirect github.com/kkdai/bstream v1.0.0 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/klauspost/compress v1.18.5 // indirect github.com/lightningnetwork/lnd/clock v1.0.1 // indirect github.com/lightningnetwork/lnd/queue v1.0.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/stretchr/objx v0.5.2 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/opencontainers/runc v1.1.14 // indirect + github.com/ory/dockertest/v3 v3.10.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/shirou/gopsutil/v4 v4.26.5 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.etcd.io/bbolt v1.3.11 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.73.4 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect ) // If you change this please run `make lint` to see where else it needs to be diff --git a/go.sum b/go.sum index c4715582e1..1340a1d872 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,13 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +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/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/btcsuite/btcd v0.26.0 h1:yntnSshlG3+H7dTwIOR4LTFXDPojVBsFORBNN5y5c/c= @@ -22,6 +32,8 @@ github.com/btcsuite/btcd/wire/v2 v2.0.0 h1:mYSKzZZ0a1sK+aMhXzfDSVsSzRkWkU3x2U04T github.com/btcsuite/btcd/wire/v2 v2.0.0/go.mod h1:bGxkPkk8IiDvUo1D96wE03llBIk7p2MdWYRyAQwLmqM= github.com/btcsuite/btclog v1.0.0 h1:sEkpKJMmfGiyZjADwEIgB1NSwMyfdD1FB8v6+w1T0Ns= github.com/btcsuite/btclog v1.0.0/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084 h1:y3bvkt8ki0KX35eUEU8XShRHusz1S+55QwXUTmxn888= +github.com/btcsuite/btclog/v2 v2.0.1-0.20250602222548-9967d19bb084/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE= github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0 h1:oIkGj32YK1CvWaJGlVwZA1f+y/KVHkfrd2PoST0ZpQs= github.com/btcsuite/btcwallet/wallet/txauthor v1.4.0/go.mod h1:sGrBjcqQ8UPexuRajFs72+o544CJn3Pavv/5H0VAWVk= github.com/btcsuite/btcwallet/wallet/txrules v1.3.0 h1:D5aGMwWIxdqek3xEJs4eOdMoh6iga2EI2xSlaXCdnNo= @@ -36,29 +48,104 @@ github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JG github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/lru v1.1.3 h1:w9EAbvGLyzm6jTjF83UKuqZEiUtJmvRhQDOCEIvSuE0= github.com/decred/dcrd/lru v1.1.3/go.mod h1:Tw0i0pJyiLEx/oZdHLe1Wdv/Y7EGzAX+sYftnmxBR4o= +github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= +github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= +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 v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= +github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +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/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/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-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +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/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= +github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= 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 v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.6/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/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/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.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.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +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 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-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/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-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/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-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +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 v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4= github.com/jessevdk/go-flags v1.6.1/go.mod h1:Mk8T1hIAWpOiJiHa9rJASDK2UGWji0EuPGBnNLMooyc= github.com/jrick/logrotate v1.1.2 h1:6ePk462NCX7TfKtNp5JJ7MbA2YIslkpfgP03TlTYMN0= @@ -67,10 +154,14 @@ github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee h1:FPP9HDkBbPyni github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee/go.mod h1:N6sz6HwJAenJ6d+/xmSl0ikfV05ZrVGmjt1ryy/WOtE= 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.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/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/neutrino v0.18.0 h1:UsyeU3twkCSAXxUssVg8vGXcHkWKzHZ3xQCej57t5t0= @@ -83,61 +174,184 @@ github.com/lightningnetwork/lnd/fn/v2 v2.0.8 h1:r2SLz7gZYQPVc3IZhU82M66guz3Zk2oY github.com/lightningnetwork/lnd/fn/v2 v2.0.8/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/sqldb v1.0.13 h1:CcG9mrHNW/hIuZnqgosdiNmS7QhjSyfR/XkSFJB7EC8= +github.com/lightningnetwork/lnd/sqldb v1.0.13/go.mod h1:ew3kMfknA0B4djTtrQSAkxvro+8+c++L8LuNaoT7GQA= github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= 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.3-0.20260615022959-a067468f0f45 h1:krvpxGTZJiDyvwU4Jr3GwR00lT4DKITDS95s28wQKU4= github.com/lightningnetwork/lnd/tlv v1.3.3-0.20260615022959-a067468f0f45/go.mod h1:oL5WIFd3ZoEwh3oH1xzizeUl6pq3DIhx9ljDvRdvI3Q= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= +github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= +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/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= +github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM= +github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= +github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= +github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0 h1:s2bIayFXlbDFexo96y+htn7FzuhpXLYJNnIuglNKqOk= +github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0/go.mod h1:h+u/2KoREGTnTl9UwrQ/g+XhasAT8E6dClclAADeXoQ= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +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/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -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/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -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/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +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/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 h1:3yiSh9fhy5/RhCSntf4Sy0Tnx50DmMpQ4MQdKKk4yg4= golang.org/x/exp v0.0.0-20250811191247-51f88131bc50/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= +modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= +modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= +modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= +modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= +modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= +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= diff --git a/wallet/internal/sql/pg/config.go b/wallet/internal/sql/pg/config.go new file mode 100644 index 0000000000..4ad52cd721 --- /dev/null +++ b/wallet/internal/sql/pg/config.go @@ -0,0 +1,117 @@ +package pg + +import ( + "crypto/tls" + "fmt" + "time" + + "github.com/jackc/pgx/v5" +) + +const ( + // DefaultMaxConnections bounds the PostgreSQL connection pool. + DefaultMaxConnections = 25 + + // DefaultMaxIdleConnections bounds idle PostgreSQL connections. + DefaultMaxIdleConnections = 6 + + // DefaultConnectionLifetime bounds connection reuse in the pool. + DefaultConnectionLifetime = 10 * time.Minute + + // DefaultConnectionIdleTime closes connections that remain idle. + DefaultConnectionIdleTime = 5 * time.Minute +) + +// Config holds the settings used to open a PostgreSQL database. +type Config struct { + DSN string + RequireSSL bool + MaxConnections int + MaxIdleConnections int + ConnectionLifetime time.Duration + ConnectionMaxIdleTime time.Duration +} + +// Validate checks that the PostgreSQL configuration can be opened safely. +func (c Config) Validate() error { + if c.DSN == "" { + return fmt.Errorf("database DSN is required") + } + + if _, err := c.connectionConfig(); err != nil { + return fmt.Errorf("invalid database DSN: %w", err) + } + + if c.MaxConnections < 0 { + return fmt.Errorf("maximum connections cannot be negative") + } + + if c.MaxIdleConnections < 0 { + return fmt.Errorf("maximum idle connections cannot be negative") + } + + return nil +} + +func (c Config) connectionConfig() (*pgx.ConnConfig, error) { + config, err := pgx.ParseConfig(c.DSN) + if err != nil { + return nil, err + } + if !c.RequireSSL { + return config, nil + } + + if config.TLSConfig == nil { + config.TLSConfig = requiredTLSConfig(config.Host) + } + for _, fallback := range config.Fallbacks { + if fallback.TLSConfig == nil { + fallback.TLSConfig = requiredTLSConfig(fallback.Host) + } + } + + return config, nil +} + +func requiredTLSConfig(host string) *tls.Config { + return &tls.Config{ + ServerName: host, + // RequireSSL matches PostgreSQL's sslmode=require behavior, which + // encrypts the connection without verifying the server identity. + InsecureSkipVerify: true, //nolint:gosec + MinVersion: tls.VersionTLS12, + } +} + +func (c Config) maxConnections() int { + if c.MaxConnections > 0 { + return c.MaxConnections + } + + return DefaultMaxConnections +} + +func (c Config) maxIdleConnections() int { + if c.MaxIdleConnections > 0 { + return c.MaxIdleConnections + } + + return DefaultMaxIdleConnections +} + +func (c Config) connectionLifetime() time.Duration { + if c.ConnectionLifetime > 0 { + return c.ConnectionLifetime + } + + return DefaultConnectionLifetime +} + +func (c Config) connectionMaxIdleTime() time.Duration { + if c.ConnectionMaxIdleTime > 0 { + return c.ConnectionMaxIdleTime + } + + return DefaultConnectionIdleTime +} diff --git a/wallet/internal/sql/pg/config_test.go b/wallet/internal/sql/pg/config_test.go new file mode 100644 index 0000000000..2c38e87370 --- /dev/null +++ b/wallet/internal/sql/pg/config_test.go @@ -0,0 +1,93 @@ +package pg + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestConnectionConfigRequireSSL verifies that SSL enforcement works for both +// DSN formats accepted by pgx without weakening stricter verification modes. +func TestConnectionConfigRequireSSL(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + dsn string + wantSkipVerify bool + wantServerName string + wantFallbacks int + wantFallbackVerify bool + }{ + { + name: "url disable", + dsn: "postgres://user:pass@localhost/wallet?sslmode=disable", + wantSkipVerify: true, + wantServerName: "localhost", + }, + { + name: "keyword value prefer", + dsn: "host=localhost user=user password=pass " + + "dbname=wallet sslmode=prefer", + wantSkipVerify: true, + wantServerName: "localhost", + wantFallbacks: 1, + wantFallbackVerify: true, + }, + { + name: "url verify full", + dsn: "postgres://user:pass@localhost/wallet?" + + "sslmode=verify-full", + wantServerName: "localhost", + }, + { + name: "keyword value verify ca", + dsn: "host=localhost user=user dbname=wallet " + + "sslmode=verify-ca", + wantSkipVerify: true, + wantServerName: "localhost", + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + config, err := (Config{ + DSN: testCase.dsn, + RequireSSL: true, + }).connectionConfig() + require.NoError(t, err) + require.NotNil(t, config.TLSConfig) + require.Equal( + t, testCase.wantSkipVerify, + config.TLSConfig.InsecureSkipVerify, + ) + require.Equal( + t, testCase.wantServerName, + config.TLSConfig.ServerName, + ) + require.Len(t, config.Fallbacks, testCase.wantFallbacks) + for _, fallback := range config.Fallbacks { + require.NotNil(t, fallback.TLSConfig) + require.Equal( + t, testCase.wantFallbackVerify, + fallback.TLSConfig.InsecureSkipVerify, + ) + } + }) + } +} + +// TestConnectionConfigAllowsExplicitNonSSL verifies that RequireSSL is +// opt-in. +func TestConnectionConfigAllowsExplicitNonSSL(t *testing.T) { + t.Parallel() + + config, err := (Config{ + DSN: "host=localhost dbname=wallet sslmode=disable", + }).connectionConfig() + require.NoError(t, err) + require.Nil(t, config.TLSConfig) +} diff --git a/wallet/internal/sql/pg/db.go b/wallet/internal/sql/pg/db.go new file mode 100644 index 0000000000..493ac4c2ad --- /dev/null +++ b/wallet/internal/sql/pg/db.go @@ -0,0 +1,42 @@ +package pg + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/jackc/pgx/v5/stdlib" +) + +const defaultOpenTimeout = 10 * time.Second + +// Open opens and verifies a PostgreSQL connection pool. +func Open(ctx context.Context, cfg Config) (*sql.DB, error) { + if err := cfg.Validate(); err != nil { + return nil, fmt.Errorf("invalid postgres config: %w", err) + } + + connConfig, err := cfg.connectionConfig() + if err != nil { + return nil, err + } + + db := stdlib.OpenDB(*connConfig) + + db.SetMaxOpenConns(cfg.maxConnections()) + db.SetMaxIdleConns(cfg.maxIdleConnections()) + db.SetConnMaxLifetime(cfg.connectionLifetime()) + db.SetConnMaxIdleTime(cfg.connectionMaxIdleTime()) + + openCtx, cancel := context.WithTimeout(ctx, defaultOpenTimeout) + defer cancel() + + if err := db.PingContext(openCtx); err != nil { + _ = db.Close() + + return nil, fmt.Errorf("ping postgres database: %w", err) + } + + return db, nil +} diff --git a/wallet/internal/sql/pg/migrations.go b/wallet/internal/sql/pg/migrations.go new file mode 100644 index 0000000000..97626b4a98 --- /dev/null +++ b/wallet/internal/sql/pg/migrations.go @@ -0,0 +1,109 @@ +package pg + +import ( + "context" + "database/sql" + "embed" + "errors" + "fmt" + + gomigrate "github.com/golang-migrate/migrate/v4" + migrate "github.com/golang-migrate/migrate/v4/database/pgx/v5" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/stdlib" +) + +//go:embed migrations/*.sql +var migrationFS embed.FS + +func newMigrationInstance(db *sql.DB) (*gomigrate.Migrate, error) { + migrationDB, err := cloneDatabase(db) + if err != nil { + return nil, fmt.Errorf("clone postgres database: %w", err) + } + + sourceDriver, err := iofs.New(migrationFS, "migrations") + if err != nil { + _ = migrationDB.Close() + + return nil, fmt.Errorf("create migration source: %w", err) + } + + driver, err := migrate.WithInstance(migrationDB, &migrate.Config{}) + if err != nil { + _ = sourceDriver.Close() + _ = migrationDB.Close() + + return nil, fmt.Errorf("create postgres migration driver: %w", err) + } + + m, err := gomigrate.NewWithInstance("iofs", sourceDriver, "pgx", driver) + if err != nil { + _ = sourceDriver.Close() + _ = driver.Close() + + return nil, fmt.Errorf("create postgres migration: %w", err) + } + + return m, nil +} + +func cloneDatabase(db *sql.DB) (*sql.DB, error) { + conn, err := db.Conn(context.Background()) + if err != nil { + return nil, err + } + defer conn.Close() + + var config *pgx.ConnConfig + err = conn.Raw(func(driverConn any) error { + pgxConn, ok := driverConn.(*stdlib.Conn) + if !ok { + return fmt.Errorf("unexpected postgres driver %T", driverConn) + } + + config = pgxConn.Conn().Config() + return nil + }) + if err != nil { + return nil, err + } + + return stdlib.OpenDB(*config), nil +} + +func runMigrations(db *sql.DB, operation string, + migrateFunc func(*gomigrate.Migrate) error) (err error) { + + m, err := newMigrationInstance(db) + if err != nil { + return err + } + defer func() { + sourceErr, databaseErr := m.Close() + closeErr := errors.Join(sourceErr, databaseErr) + if closeErr != nil { + err = errors.Join( + err, fmt.Errorf("close postgres migration: %w", closeErr), + ) + } + }() + + err = migrateFunc(m) + if err != nil && !errors.Is(err, gomigrate.ErrNoChange) { + return fmt.Errorf("%s postgres migrations: %w", operation, err) + } + + return nil +} + +// ApplyMigrations applies all embedded PostgreSQL migrations. +func ApplyMigrations(db *sql.DB) error { + return runMigrations(db, "apply", (*gomigrate.Migrate).Up) +} + +// RollbackMigrations rolls back all embedded PostgreSQL migrations. +func RollbackMigrations(db *sql.DB) error { + return runMigrations(db, "rollback", (*gomigrate.Migrate).Down) +} diff --git a/wallet/internal/sql/pg/migrations_test.go b/wallet/internal/sql/pg/migrations_test.go new file mode 100644 index 0000000000..8ee5ca796a --- /dev/null +++ b/wallet/internal/sql/pg/migrations_test.go @@ -0,0 +1,73 @@ +//go:build test_db_postgres + +package pg + +import ( + "context" + "database/sql" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" +) + +// TestMigrationsRoundTrip verifies that the schema can be applied, removed, +// and applied again on the same database. +func TestMigrationsRoundTrip(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + container, err := postgres.Run( + ctx, "postgres:18-alpine", + postgres.WithDatabase("btcwallet"), + postgres.WithUsername("postgres"), + postgres.WithPassword("postgres"), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2).WithStartupTimeout(2*time.Minute), + ), + ) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, container.Terminate(context.Background())) + }) + + dsn, err := container.ConnectionString(ctx, "sslmode=disable") + require.NoError(t, err) + + db, err := Open(ctx, Config{DSN: dsn}) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, db.Close()) + }) + + require.NoError(t, ApplyMigrations(db)) + require.Zero(t, db.Stats().InUse) + require.NoError(t, db.PingContext(ctx)) + requireBlocksTable(t, db, true) + + require.NoError(t, RollbackMigrations(db)) + require.Zero(t, db.Stats().InUse) + require.NoError(t, db.PingContext(ctx)) + requireBlocksTable(t, db, false) + + require.NoError(t, ApplyMigrations(db)) + requireBlocksTable(t, db, true) +} + +func requireBlocksTable(t *testing.T, db *sql.DB, expected bool) { + t.Helper() + + var exists bool + err := db.QueryRow( + "SELECT EXISTS (SELECT 1 FROM information_schema.tables " + + "WHERE table_schema = 'public' AND table_name = 'blocks')", + ).Scan(&exists) + require.NoError(t, err) + require.Equal(t, expected, exists) +} diff --git a/wallet/internal/sql/sqlite/config.go b/wallet/internal/sql/sqlite/config.go new file mode 100644 index 0000000000..ea3418638f --- /dev/null +++ b/wallet/internal/sql/sqlite/config.go @@ -0,0 +1,79 @@ +package sqlite + +import ( + "fmt" + "time" + + "github.com/lightningnetwork/lnd/sqldb" +) + +const ( + // DefaultMaxConnections limits SQLite contention to one writer and one + // reader. + DefaultMaxConnections = sqldb.DefaultSqliteMaxConns + + // DefaultBusyTimeout is the time SQLite waits for a locked database. + DefaultBusyTimeout = sqldb.DefaultSqliteBusyTimeout + + // DefaultConnectionLifetime bounds connection reuse in the pool. + DefaultConnectionLifetime = 10 * time.Minute +) + +// Config holds the settings used to open a SQLite database. +type Config struct { + DBPath string + BusyTimeout time.Duration + MaxConnections int + MaxIdleConnections int + ConnectionLifetime time.Duration + Pragmas []string +} + +// Validate checks that the SQLite configuration can be opened safely. +func (c Config) Validate() error { + if c.DBPath == "" { + return fmt.Errorf("database path is required") + } + + if c.MaxConnections < 0 { + return fmt.Errorf("maximum connections cannot be negative") + } + + if c.MaxIdleConnections < 0 { + return fmt.Errorf("maximum idle connections cannot be negative") + } + + return nil +} + +func (c Config) maxConnections() int { + if c.MaxConnections > 0 { + return c.MaxConnections + } + + return DefaultMaxConnections +} + +func (c Config) maxIdleConnections() int { + if c.MaxIdleConnections > 0 { + return c.MaxIdleConnections + } + + return c.maxConnections() +} + +func (c Config) busyTimeout() time.Duration { + if c.BusyTimeout > 0 { + return c.BusyTimeout + } + + return DefaultBusyTimeout +} + +func (c Config) connectionLifetime() time.Duration { + if c.ConnectionLifetime > 0 { + return c.ConnectionLifetime + } + + return DefaultConnectionLifetime +} diff --git a/wallet/internal/sql/sqlite/db.go b/wallet/internal/sql/sqlite/db.go new file mode 100644 index 0000000000..be61739be3 --- /dev/null +++ b/wallet/internal/sql/sqlite/db.go @@ -0,0 +1,57 @@ +package sqlite + +import ( + "context" + "database/sql" + "fmt" + "net/url" + "time" + + _ "modernc.org/sqlite" +) + +const defaultOpenTimeout = 10 * time.Second + +// Open opens and verifies a SQLite connection pool. +func Open(ctx context.Context, cfg Config) (*sql.DB, error) { + if err := cfg.Validate(); err != nil { + return nil, fmt.Errorf("invalid sqlite config: %w", err) + } + + options := url.Values{} + defaultPragmas := []string{ + "foreign_keys=on", + "journal_mode=WAL", + fmt.Sprintf("busy_timeout=%d", cfg.busyTimeout().Milliseconds()), + "synchronous=full", + "fullfsync=true", + "auto_vacuum=incremental", + } + for _, pragma := range append(defaultPragmas, cfg.Pragmas...) { + options.Add("_pragma", pragma) + } + + dsn := fmt.Sprintf( + "%s?%s&_txlock=immediate&_time_format=sqlite", cfg.DBPath, + options.Encode(), + ) + db, err := sql.Open("sqlite", dsn) + if err != nil { + return nil, fmt.Errorf("open sqlite database: %w", err) + } + + db.SetMaxOpenConns(cfg.maxConnections()) + db.SetMaxIdleConns(cfg.maxIdleConnections()) + db.SetConnMaxLifetime(cfg.connectionLifetime()) + + openCtx, cancel := context.WithTimeout(ctx, defaultOpenTimeout) + defer cancel() + + if err := db.PingContext(openCtx); err != nil { + _ = db.Close() + + return nil, fmt.Errorf("ping sqlite database: %w", err) + } + + return db, nil +} diff --git a/wallet/internal/sql/sqlite/migrations.go b/wallet/internal/sql/sqlite/migrations.go new file mode 100644 index 0000000000..7492f17621 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations.go @@ -0,0 +1,64 @@ +package sqlite + +import ( + "database/sql" + "embed" + "errors" + "fmt" + + gomigrate "github.com/golang-migrate/migrate/v4" + migrate "github.com/golang-migrate/migrate/v4/database/sqlite" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +//go:embed migrations/*.sql +var migrationFS embed.FS + +func newMigrationInstance(db *sql.DB) (*gomigrate.Migrate, error) { + sourceDriver, err := iofs.New(migrationFS, "migrations") + if err != nil { + return nil, fmt.Errorf("create migration source: %w", err) + } + + driver, err := migrate.WithInstance(db, &migrate.Config{}) + if err != nil { + return nil, fmt.Errorf("create sqlite migration driver: %w", err) + } + + m, err := gomigrate.NewWithInstance( + "iofs", sourceDriver, "sqlite", driver, + ) + if err != nil { + return nil, fmt.Errorf("create sqlite migration: %w", err) + } + + return m, nil +} + +// ApplyMigrations applies all embedded SQLite migrations. +func ApplyMigrations(db *sql.DB) error { + m, err := newMigrationInstance(db) + if err != nil { + return err + } + + if err := m.Up(); err != nil && !errors.Is(err, gomigrate.ErrNoChange) { + return fmt.Errorf("apply sqlite migrations: %w", err) + } + + return nil +} + +// RollbackMigrations rolls back all embedded SQLite migrations. +func RollbackMigrations(db *sql.DB) error { + m, err := newMigrationInstance(db) + if err != nil { + return err + } + + if err := m.Down(); err != nil && !errors.Is(err, gomigrate.ErrNoChange) { + return fmt.Errorf("rollback sqlite migrations: %w", err) + } + + return nil +} diff --git a/wallet/internal/sql/sqlite/migrations_test.go b/wallet/internal/sql/sqlite/migrations_test.go new file mode 100644 index 0000000000..414be3d956 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations_test.go @@ -0,0 +1,45 @@ +package sqlite + +import ( + "context" + "database/sql" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestMigrationsRoundTrip verifies that the schema can be applied, removed, +// and applied again on the same database. +func TestMigrationsRoundTrip(t *testing.T) { + t.Parallel() + + db, err := Open(context.Background(), Config{ + DBPath: filepath.Join(t.TempDir(), "wallet.db"), + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, db.Close()) + }) + + require.NoError(t, ApplyMigrations(db)) + requireBlocksTable(t, db, true) + + require.NoError(t, RollbackMigrations(db)) + requireBlocksTable(t, db, false) + + require.NoError(t, ApplyMigrations(db)) + requireBlocksTable(t, db, true) +} + +func requireBlocksTable(t *testing.T, db *sql.DB, expected bool) { + t.Helper() + + var count int + err := db.QueryRow( + "SELECT count(*) FROM sqlite_master " + + "WHERE type = 'table' AND name = 'blocks'", + ).Scan(&count) + require.NoError(t, err) + require.Equal(t, expected, count == 1) +} From a5a60cab185ae712f6b28a3375c8511e1a0074cf Mon Sep 17 00:00:00 2001 From: Gustavo Stingelin Date: Fri, 10 Jul 2026 15:39:54 -0700 Subject: [PATCH 4/4] wallet/sql: preserve legacy wallet schema In this commit, we add the wallet, scope, account, and address schema shared by the SQLite and PostgreSQL backends. The schema keeps the current dual-passphrase encryption hierarchy, watch-only nullability, encrypted public material, scope account counters, and SHA256 address identifiers. It also persists the sticky used bit directly, so the SQL port does not change gap-limit behavior or require the key-vault rewrite. Extracted-from: bbfe24b9d3fff620cd77c4806e58b416af401322 Extracted-from: b6b6a7e895c785e020d61d28bb5fe882e8947cc5 Extracted-from: 9a313ea6ffaa0f9c04385a2a9c5436acef83be5f Extracted-from: bb5a67bcbcdd1fbc694c4e5e6eb9b921970149e1 Extracted-from: c5d82b214fb8696bd4619bf3e0362bb5f35a0585 Extracted-from: 5a639d4dfb9350d0c8e93699fd105a83e0351c7f Extracted-from: 99e40ab81c0939faa4b2de1774130cfcb0c1bc7c Extracted-from: 132ba5b512677f5ea71f9031e078959399918549 Extracted-from: 47b750bc30e10e7da235acc57a31874a7f2fc954 Extracted-from: 69e459bc66c5daf00f19eedcf500a694a65d27e1 Co-authored-by: yyforyongyu --- wallet/internal/sql/pg/legacy_schema_test.go | 216 +++++++++++++ .../sql/pg/migrations/000002_wallets.down.sql | 2 + .../sql/pg/migrations/000002_wallets.up.sql | 38 +++ .../migrations/000003_address_types.down.sql | 1 + .../pg/migrations/000003_address_types.up.sql | 16 + .../pg/migrations/000004_key_scopes.down.sql | 1 + .../pg/migrations/000004_key_scopes.up.sql | 21 ++ .../pg/migrations/000005_accounts.down.sql | 1 + .../sql/pg/migrations/000005_accounts.up.sql | 47 +++ .../pg/migrations/000006_addresses.down.sql | 3 + .../sql/pg/migrations/000006_addresses.up.sql | 80 +++++ wallet/internal/sql/pg/migrations_test.go | 29 +- wallet/internal/sql/pg/queries/accounts.sql | 60 ++++ .../internal/sql/pg/queries/address_types.sql | 4 + wallet/internal/sql/pg/queries/addresses.sql | 70 +++++ wallet/internal/sql/pg/queries/key_scopes.sql | 37 +++ wallet/internal/sql/pg/queries/wallets.sql | 84 +++++ wallet/internal/sql/pg/sqlc/accounts.sql.go | 205 ++++++++++++ .../internal/sql/pg/sqlc/address_types.sql.go | 39 +++ wallet/internal/sql/pg/sqlc/addresses.sql.go | 216 +++++++++++++ wallet/internal/sql/pg/sqlc/db.go | 224 +++++++++++++- wallet/internal/sql/pg/sqlc/key_scopes.sql.go | 135 ++++++++ wallet/internal/sql/pg/sqlc/models.go | 79 +++++ wallet/internal/sql/pg/sqlc/querier.go | 20 ++ wallet/internal/sql/pg/sqlc/wallets.sql.go | 257 +++++++++++++++ .../internal/sql/sqlite/legacy_schema_test.go | 292 ++++++++++++++++++ .../sqlite/migrations/000002_wallets.down.sql | 2 + .../sqlite/migrations/000002_wallets.up.sql | 39 +++ .../migrations/000003_address_types.down.sql | 1 + .../migrations/000003_address_types.up.sql | 16 + .../migrations/000004_key_scopes.down.sql | 1 + .../migrations/000004_key_scopes.up.sql | 21 ++ .../migrations/000005_accounts.down.sql | 1 + .../sqlite/migrations/000005_accounts.up.sql | 47 +++ .../migrations/000006_addresses.down.sql | 2 + .../sqlite/migrations/000006_addresses.up.sql | 73 +++++ wallet/internal/sql/sqlite/migrations_test.go | 28 +- .../internal/sql/sqlite/queries/accounts.sql | 60 ++++ .../sql/sqlite/queries/address_types.sql | 4 + .../internal/sql/sqlite/queries/addresses.sql | 67 ++++ .../sql/sqlite/queries/key_scopes.sql | 37 +++ .../internal/sql/sqlite/queries/wallets.sql | 84 +++++ .../internal/sql/sqlite/sqlc/accounts.sql.go | 205 ++++++++++++ .../sql/sqlite/sqlc/address_types.sql.go | 39 +++ .../internal/sql/sqlite/sqlc/addresses.sql.go | 213 +++++++++++++ wallet/internal/sql/sqlite/sqlc/db.go | 224 +++++++++++++- .../sql/sqlite/sqlc/key_scopes.sql.go | 135 ++++++++ wallet/internal/sql/sqlite/sqlc/models.go | 79 +++++ wallet/internal/sql/sqlite/sqlc/querier.go | 20 ++ .../internal/sql/sqlite/sqlc/wallets.sql.go | 257 +++++++++++++++ 50 files changed, 3786 insertions(+), 46 deletions(-) create mode 100644 wallet/internal/sql/pg/legacy_schema_test.go create mode 100644 wallet/internal/sql/pg/migrations/000002_wallets.down.sql create mode 100644 wallet/internal/sql/pg/migrations/000002_wallets.up.sql create mode 100644 wallet/internal/sql/pg/migrations/000003_address_types.down.sql create mode 100644 wallet/internal/sql/pg/migrations/000003_address_types.up.sql create mode 100644 wallet/internal/sql/pg/migrations/000004_key_scopes.down.sql create mode 100644 wallet/internal/sql/pg/migrations/000004_key_scopes.up.sql create mode 100644 wallet/internal/sql/pg/migrations/000005_accounts.down.sql create mode 100644 wallet/internal/sql/pg/migrations/000005_accounts.up.sql create mode 100644 wallet/internal/sql/pg/migrations/000006_addresses.down.sql create mode 100644 wallet/internal/sql/pg/migrations/000006_addresses.up.sql create mode 100644 wallet/internal/sql/pg/queries/accounts.sql create mode 100644 wallet/internal/sql/pg/queries/address_types.sql create mode 100644 wallet/internal/sql/pg/queries/addresses.sql create mode 100644 wallet/internal/sql/pg/queries/key_scopes.sql create mode 100644 wallet/internal/sql/pg/queries/wallets.sql create mode 100644 wallet/internal/sql/pg/sqlc/accounts.sql.go create mode 100644 wallet/internal/sql/pg/sqlc/address_types.sql.go create mode 100644 wallet/internal/sql/pg/sqlc/addresses.sql.go create mode 100644 wallet/internal/sql/pg/sqlc/key_scopes.sql.go create mode 100644 wallet/internal/sql/pg/sqlc/wallets.sql.go create mode 100644 wallet/internal/sql/sqlite/legacy_schema_test.go create mode 100644 wallet/internal/sql/sqlite/migrations/000002_wallets.down.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000002_wallets.up.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000003_address_types.down.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000003_address_types.up.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000004_key_scopes.down.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000004_key_scopes.up.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000005_accounts.down.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000005_accounts.up.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000006_addresses.down.sql create mode 100644 wallet/internal/sql/sqlite/migrations/000006_addresses.up.sql create mode 100644 wallet/internal/sql/sqlite/queries/accounts.sql create mode 100644 wallet/internal/sql/sqlite/queries/address_types.sql create mode 100644 wallet/internal/sql/sqlite/queries/addresses.sql create mode 100644 wallet/internal/sql/sqlite/queries/key_scopes.sql create mode 100644 wallet/internal/sql/sqlite/queries/wallets.sql create mode 100644 wallet/internal/sql/sqlite/sqlc/accounts.sql.go create mode 100644 wallet/internal/sql/sqlite/sqlc/address_types.sql.go create mode 100644 wallet/internal/sql/sqlite/sqlc/addresses.sql.go create mode 100644 wallet/internal/sql/sqlite/sqlc/key_scopes.sql.go create mode 100644 wallet/internal/sql/sqlite/sqlc/wallets.sql.go diff --git a/wallet/internal/sql/pg/legacy_schema_test.go b/wallet/internal/sql/pg/legacy_schema_test.go new file mode 100644 index 0000000000..aa9bcc891d --- /dev/null +++ b/wallet/internal/sql/pg/legacy_schema_test.go @@ -0,0 +1,216 @@ +//go:build test_db_postgres + +package pg + +import ( + "context" + "crypto/sha256" + "database/sql" + "testing" + "time" + + "github.com/btcsuite/btcwallet/wallet/internal/sql/pg/sqlc" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" +) + +// TestLegacySchemaQueries verifies the legacy ciphertext and sticky address +// state through the generated PostgreSQL queries. +func TestLegacySchemaQueries(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + container, err := postgres.Run( + ctx, "postgres:18-alpine", + postgres.WithDatabase("btcwallet"), + postgres.WithUsername("postgres"), + postgres.WithPassword("postgres"), + testcontainers.WithWaitStrategy( + wait.ForLog("database system is ready to accept connections"). + WithOccurrence(2).WithStartupTimeout(2*time.Minute), + ), + ) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, container.Terminate(context.Background())) + }) + + dsn, err := container.ConnectionString(ctx, "sslmode=disable") + require.NoError(t, err) + db, err := Open(ctx, Config{DSN: dsn}) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, db.Close()) + }) + require.NoError(t, ApplyMigrations(db)) + requireNoPostgresPlaintextColumns(t, db) + + queries := sqlc.New(db) + genesisHash := pgBytesOf(0x01, 32) + birthdayHash := pgBytesOf(0x02, 32) + require.NoError(t, queries.InsertBlock(ctx, sqlc.InsertBlockParams{ + BlockHeight: 0, + HeaderHash: genesisHash, + BlockTimestamp: 1, + })) + require.NoError(t, queries.InsertBlock(ctx, sqlc.InsertBlockParams{ + BlockHeight: 100, + HeaderHash: birthdayHash, + BlockTimestamp: 2, + })) + + walletID, err := queries.CreateWallet(ctx, sqlc.CreateWalletParams{ + WalletName: "watch-only", + ManagerVersion: 8, + ManagerCreatedAt: 123, + IsWatchOnly: true, + MasterPubParams: []byte("encrypted-master-public-params"), + EncryptedCryptoPubKey: []byte("encrypted-crypto-public-key"), + }) + require.NoError(t, err) + wallet, err := queries.GetWalletByName(ctx, "watch-only") + require.NoError(t, err) + require.Equal(t, int64(123), wallet.ManagerCreatedAt) + require.Nil(t, wallet.MasterPrivParams) + require.Nil(t, wallet.EncryptedCryptoPrivKey) + require.Equal(t, []byte("encrypted-crypto-public-key"), + wallet.EncryptedCryptoPubKey) + + require.NoError(t, queries.PutWalletSyncState( + ctx, sqlc.PutWalletSyncStateParams{ + WalletID: walletID, + StartBlockHeight: 0, + SyncedBlockHeight: 100, + BirthdayTimestamp: 1234, + BirthdayBlockHeight: sql.NullInt32{Int32: 100, Valid: true}, + BirthdayBlockVerified: true, + }, + )) + syncState, err := queries.GetWalletSyncState(ctx, walletID) + require.NoError(t, err) + require.Equal(t, genesisHash, syncState.StartBlockHash) + require.Equal(t, birthdayHash, syncState.BirthdayBlockHash) + + scopeID, err := queries.CreateKeyScope(ctx, sqlc.CreateKeyScopeParams{ + WalletID: walletID, + Purpose: 84, + CoinType: 0, + ExternalAddrType: 4, + InternalAddrType: 4, + }) + require.NoError(t, err) + rows, err := queries.UpdateLastAccountNumber( + ctx, sqlc.UpdateLastAccountNumberParams{ + LastAccountNumber: sql.NullInt64{Int64: 7, Valid: true}, + ID: scopeID, + WalletID: walletID, + }, + ) + require.NoError(t, err) + require.Equal(t, int64(1), rows) + scope, err := queries.GetKeyScope(ctx, sqlc.GetKeyScopeParams{ + WalletID: walletID, + Purpose: 84, + CoinType: 0, + }) + require.NoError(t, err) + require.Equal(t, int64(7), scope.LastAccountNumber.Int64) + err = queries.CreateAccount(ctx, sqlc.CreateAccountParams{ + WalletID: walletID, + ScopeID: scopeID, + AccountNumber: 7, + AccountType: 1, + AccountName: "imported", + EncryptedPubKey: []byte("encrypted-account-public-key"), + MasterKeyFingerprint: sql.NullInt64{Int64: 42, Valid: true}, + ExternalAddrType: sql.NullInt16{Int16: 3, Valid: true}, + InternalAddrType: sql.NullInt16{Int16: 4, Valid: true}, + }) + require.NoError(t, err) + + watchOnlyScriptHash := sha256.Sum256([]byte("watch-only-script-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: watchOnlyScriptHash[:], + AccountNumber: 7, + AddressType: 2, + AddedAt: 12, + SyncStatus: 2, + EncryptedHash: []byte("encrypted-watch-only-script-hash"), + })) + + addressHash := sha256.Sum256([]byte("imported-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: addressHash[:], + AccountNumber: 7, + AddressType: 1, + AddedAt: 11, + SyncStatus: 2, + EncryptedPubKey: []byte("encrypted-imported-public-key"), + EncryptedPrivKey: []byte{}, + })) + rows, err = queries.MarkAddressUsed(ctx, sqlc.MarkAddressUsedParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: addressHash[:], + }) + require.NoError(t, err) + require.Equal(t, int64(1), rows) + address, err := queries.GetAddress(ctx, sqlc.GetAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: addressHash[:], + }) + require.NoError(t, err) + require.True(t, address.Used) + require.Equal(t, []byte("encrypted-imported-public-key"), + address.EncryptedPubKey) + + _, err = db.ExecContext(ctx, ` + UPDATE addresses SET used = FALSE + WHERE wallet_id = $1 AND scope_id = $2 AND address_hash = $3 + `, walletID, scopeID, addressHash[:]) + require.ErrorContains(t, err, "address used state cannot be cleared") +} + +func requireNoPostgresPlaintextColumns(t *testing.T, db *sql.DB) { + t.Helper() + + columns := map[string][]string{ + "wallets": {"master_hd_pub_key"}, + "key_scopes": {"coin_pub_key", "coin_priv_key"}, + "accounts": {"public_key", "private_key"}, + "addresses": {"address_id", "script_pub_key", "pub_key", "script"}, + } + for table, tableColumns := range columns { + for _, column := range tableColumns { + var exists bool + err := db.QueryRow(` + SELECT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = $1 + AND column_name = $2 + ) + `, table, column).Scan(&exists) + require.NoError(t, err) + require.False(t, exists, "%s.%s", table, column) + } + } +} + +func pgBytesOf(value byte, count int) []byte { + b := make([]byte, count) + for i := range b { + b[i] = value + } + + return b +} diff --git a/wallet/internal/sql/pg/migrations/000002_wallets.down.sql b/wallet/internal/sql/pg/migrations/000002_wallets.down.sql new file mode 100644 index 0000000000..0b73d6e376 --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000002_wallets.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS wallet_sync_states; +DROP TABLE IF EXISTS wallets; diff --git a/wallet/internal/sql/pg/migrations/000002_wallets.up.sql b/wallet/internal/sql/pg/migrations/000002_wallets.up.sql new file mode 100644 index 0000000000..096952f593 --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000002_wallets.up.sql @@ -0,0 +1,38 @@ +-- The wallets table preserves the legacy waddrmgr encryption hierarchy. +CREATE TABLE wallets ( + id BIGSERIAL PRIMARY KEY, + wallet_name TEXT NOT NULL UNIQUE, + manager_version BIGINT NOT NULL + CHECK (manager_version BETWEEN 0 AND 4294967295), + manager_created_at BIGINT NOT NULL CHECK (manager_created_at >= 0), + is_watch_only BOOLEAN NOT NULL, + master_pub_params BYTEA NOT NULL, + master_priv_params BYTEA, + encrypted_crypto_pub_key BYTEA NOT NULL, + encrypted_crypto_priv_key BYTEA, + encrypted_crypto_script_key BYTEA, + encrypted_master_hd_pub_key BYTEA, + encrypted_master_hd_priv_key BYTEA +); + +-- The wallet sync state mirrors the block stamps and birthday metadata stored +-- by the legacy address manager. +CREATE TABLE wallet_sync_states ( + wallet_id BIGINT PRIMARY KEY, + start_block_height INTEGER NOT NULL, + synced_block_height INTEGER NOT NULL, + birthday_timestamp BIGINT NOT NULL CHECK (birthday_timestamp >= 0), + birthday_block_height INTEGER, + birthday_block_verified BOOLEAN NOT NULL DEFAULT FALSE, + FOREIGN KEY (wallet_id) REFERENCES wallets (id) ON DELETE RESTRICT, + FOREIGN KEY (start_block_height) REFERENCES blocks (block_height) + ON DELETE RESTRICT, + FOREIGN KEY (synced_block_height) REFERENCES blocks (block_height) + ON DELETE RESTRICT, + FOREIGN KEY (birthday_block_height) REFERENCES blocks (block_height) + ON DELETE RESTRICT, + CHECK ( + birthday_block_verified = FALSE + OR birthday_block_height IS NOT NULL + ) +); diff --git a/wallet/internal/sql/pg/migrations/000003_address_types.down.sql b/wallet/internal/sql/pg/migrations/000003_address_types.down.sql new file mode 100644 index 0000000000..a8937217d1 --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000003_address_types.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS address_types; diff --git a/wallet/internal/sql/pg/migrations/000003_address_types.up.sql b/wallet/internal/sql/pg/migrations/000003_address_types.up.sql new file mode 100644 index 0000000000..dcfba1e88b --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000003_address_types.up.sql @@ -0,0 +1,16 @@ +-- Address types are the stable public waddrmgr.AddressType values used by key +-- scope address schemas. +CREATE TABLE address_types ( + id SMALLINT PRIMARY KEY CHECK (id BETWEEN 0 AND 7), + type_name TEXT NOT NULL UNIQUE +); + +INSERT INTO address_types (id, type_name) VALUES + (0, 'pubkey-hash'), + (1, 'script'), + (2, 'raw-pubkey'), + (3, 'nested-witness-pubkey'), + (4, 'witness-pubkey'), + (5, 'witness-script'), + (6, 'taproot-pubkey'), + (7, 'taproot-script'); diff --git a/wallet/internal/sql/pg/migrations/000004_key_scopes.down.sql b/wallet/internal/sql/pg/migrations/000004_key_scopes.down.sql new file mode 100644 index 0000000000..ae9bf8f8bc --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000004_key_scopes.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS key_scopes; diff --git a/wallet/internal/sql/pg/migrations/000004_key_scopes.up.sql b/wallet/internal/sql/pg/migrations/000004_key_scopes.up.sql new file mode 100644 index 0000000000..c4d5d494a3 --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000004_key_scopes.up.sql @@ -0,0 +1,21 @@ +-- Key scopes preserve encrypted coin keys and the address schema associated +-- with each purpose and coin tuple. +CREATE TABLE key_scopes ( + id BIGSERIAL PRIMARY KEY, + wallet_id BIGINT NOT NULL, + purpose BIGINT NOT NULL CHECK (purpose BETWEEN 0 AND 4294967295), + coin_type BIGINT NOT NULL CHECK (coin_type BETWEEN 0 AND 4294967295), + encrypted_coin_pub_key BYTEA, + encrypted_coin_priv_key BYTEA, + last_account_number BIGINT + CHECK (last_account_number BETWEEN 0 AND 4294967295), + external_addr_type SMALLINT NOT NULL, + internal_addr_type SMALLINT NOT NULL, + FOREIGN KEY (wallet_id) REFERENCES wallets (id) ON DELETE RESTRICT, + FOREIGN KEY (external_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + FOREIGN KEY (internal_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + UNIQUE (wallet_id, purpose, coin_type), + UNIQUE (id, wallet_id) +); diff --git a/wallet/internal/sql/pg/migrations/000005_accounts.down.sql b/wallet/internal/sql/pg/migrations/000005_accounts.down.sql new file mode 100644 index 0000000000..1616db4979 --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000005_accounts.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS accounts; diff --git a/wallet/internal/sql/pg/migrations/000005_accounts.up.sql b/wallet/internal/sql/pg/migrations/000005_accounts.up.sql new file mode 100644 index 0000000000..28069f0a7c --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000005_accounts.up.sql @@ -0,0 +1,47 @@ +-- Accounts preserve the two stable legacy account encodings. Every account, +-- including an imported watch-only account, retains its uint32 account number. +CREATE TABLE accounts ( + wallet_id BIGINT NOT NULL, + scope_id BIGINT NOT NULL, + account_number BIGINT NOT NULL + CHECK (account_number BETWEEN 0 AND 4294967295), + account_type SMALLINT NOT NULL CHECK (account_type IN (0, 1)), + account_name TEXT NOT NULL, + encrypted_pub_key BYTEA NOT NULL, + encrypted_priv_key BYTEA, + master_key_fingerprint BIGINT + CHECK ( + master_key_fingerprint IS NULL + OR master_key_fingerprint BETWEEN 0 AND 4294967295 + ), + next_external_index BIGINT NOT NULL DEFAULT 0 + CHECK (next_external_index BETWEEN 0 AND 4294967295), + next_internal_index BIGINT NOT NULL DEFAULT 0 + CHECK (next_internal_index BETWEEN 0 AND 4294967295), + external_addr_type SMALLINT, + internal_addr_type SMALLINT, + FOREIGN KEY (scope_id, wallet_id) REFERENCES key_scopes (id, wallet_id) + ON DELETE RESTRICT, + FOREIGN KEY (external_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + FOREIGN KEY (internal_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + PRIMARY KEY (scope_id, account_number), + UNIQUE (scope_id, account_name), + UNIQUE (scope_id, account_number, wallet_id), + CHECK ( + (external_addr_type IS NULL AND internal_addr_type IS NULL) + OR (external_addr_type IS NOT NULL AND internal_addr_type IS NOT NULL) + ), + CHECK ( + (account_type = 0 + AND master_key_fingerprint IS NULL + AND external_addr_type IS NULL + AND internal_addr_type IS NULL) + OR ( + account_type = 1 + AND encrypted_priv_key IS NULL + AND master_key_fingerprint IS NOT NULL + ) + ) +); diff --git a/wallet/internal/sql/pg/migrations/000006_addresses.down.sql b/wallet/internal/sql/pg/migrations/000006_addresses.down.sql new file mode 100644 index 0000000000..266c5a6fcc --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000006_addresses.down.sql @@ -0,0 +1,3 @@ +DROP TRIGGER IF EXISTS trg_addresses_used_is_monotonic ON addresses; +DROP FUNCTION IF EXISTS assert_address_used_is_monotonic(); +DROP TABLE IF EXISTS addresses; diff --git a/wallet/internal/sql/pg/migrations/000006_addresses.up.sql b/wallet/internal/sql/pg/migrations/000006_addresses.up.sql new file mode 100644 index 0000000000..445aaef7e9 --- /dev/null +++ b/wallet/internal/sql/pg/migrations/000006_addresses.up.sql @@ -0,0 +1,80 @@ +-- Addresses are keyed by SHA256(addressID), matching the legacy address and +-- used-address buckets without exposing the encoded address in plaintext. +CREATE TABLE addresses ( + wallet_id BIGINT NOT NULL, + scope_id BIGINT NOT NULL, + address_hash BYTEA NOT NULL CHECK (length(address_hash) = 32), + account_number BIGINT NOT NULL + CHECK (account_number BETWEEN 0 AND 4294967295), + address_type SMALLINT NOT NULL CHECK (address_type BETWEEN 0 AND 4), + added_at BIGINT NOT NULL CHECK (added_at >= 0), + sync_status SMALLINT NOT NULL CHECK (sync_status IN (0, 1, 2)), + branch BIGINT CHECK (branch BETWEEN 0 AND 4294967295), + address_index BIGINT CHECK (address_index BETWEEN 0 AND 4294967295), + encrypted_pub_key BYTEA, + encrypted_priv_key BYTEA, + encrypted_hash BYTEA, + encrypted_script BYTEA, + witness_version SMALLINT CHECK (witness_version BETWEEN 0 AND 255), + is_secret_script BOOLEAN, + used BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (wallet_id, scope_id, address_hash), + FOREIGN KEY (scope_id, account_number, wallet_id) + REFERENCES accounts (scope_id, account_number, wallet_id) + ON DELETE RESTRICT, + CHECK ( + (address_type = 0 + AND branch IS NOT NULL + AND address_index IS NOT NULL + AND encrypted_pub_key IS NULL + AND encrypted_priv_key IS NULL + AND encrypted_hash IS NULL + AND encrypted_script IS NULL + AND witness_version IS NULL + AND is_secret_script IS NULL) + OR (address_type = 1 + AND branch IS NULL + AND address_index IS NULL + AND encrypted_pub_key IS NOT NULL + AND encrypted_hash IS NULL + AND encrypted_script IS NULL + AND witness_version IS NULL + AND is_secret_script IS NULL) + OR (address_type = 2 + AND branch IS NULL + AND address_index IS NULL + AND encrypted_pub_key IS NULL + AND encrypted_priv_key IS NULL + AND encrypted_hash IS NOT NULL + AND witness_version IS NULL + AND is_secret_script IS NULL) + OR (address_type IN (3, 4) + AND branch IS NULL + AND address_index IS NULL + AND encrypted_pub_key IS NULL + AND encrypted_priv_key IS NULL + AND encrypted_hash IS NOT NULL + AND witness_version IS NOT NULL + AND is_secret_script IS NOT NULL) + ) +); + +CREATE INDEX idx_addresses_account +ON addresses (scope_id, account_number, address_hash); + +-- The used bit is sticky, as it is in the legacy used-address bucket. +CREATE FUNCTION assert_address_used_is_monotonic() RETURNS TRIGGER AS $$ +BEGIN + IF OLD.used AND NOT NEW.used THEN + RAISE EXCEPTION 'address used state cannot be cleared' + USING ERRCODE = '23514'; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trg_addresses_used_is_monotonic +BEFORE UPDATE OF used ON addresses +FOR EACH ROW +EXECUTE FUNCTION assert_address_used_is_monotonic(); diff --git a/wallet/internal/sql/pg/migrations_test.go b/wallet/internal/sql/pg/migrations_test.go index 8ee5ca796a..018d1c27ee 100644 --- a/wallet/internal/sql/pg/migrations_test.go +++ b/wallet/internal/sql/pg/migrations_test.go @@ -49,25 +49,32 @@ func TestMigrationsRoundTrip(t *testing.T) { require.NoError(t, ApplyMigrations(db)) require.Zero(t, db.Stats().InUse) require.NoError(t, db.PingContext(ctx)) - requireBlocksTable(t, db, true) + requireSchemaTables(t, db, true) require.NoError(t, RollbackMigrations(db)) require.Zero(t, db.Stats().InUse) require.NoError(t, db.PingContext(ctx)) - requireBlocksTable(t, db, false) + requireSchemaTables(t, db, false) require.NoError(t, ApplyMigrations(db)) - requireBlocksTable(t, db, true) + requireSchemaTables(t, db, true) } -func requireBlocksTable(t *testing.T, db *sql.DB, expected bool) { +func requireSchemaTables(t *testing.T, db *sql.DB, expected bool) { t.Helper() - var exists bool - err := db.QueryRow( - "SELECT EXISTS (SELECT 1 FROM information_schema.tables " + - "WHERE table_schema = 'public' AND table_name = 'blocks')", - ).Scan(&exists) - require.NoError(t, err) - require.Equal(t, expected, exists) + tables := []string{ + "blocks", "wallets", "wallet_sync_states", "address_types", + "key_scopes", "accounts", "addresses", + } + for _, table := range tables { + var exists bool + err := db.QueryRow( + "SELECT EXISTS (SELECT 1 FROM information_schema.tables "+ + "WHERE table_schema = 'public' AND table_name = $1)", + table, + ).Scan(&exists) + require.NoError(t, err) + require.Equal(t, expected, exists, table) + } } diff --git a/wallet/internal/sql/pg/queries/accounts.sql b/wallet/internal/sql/pg/queries/accounts.sql new file mode 100644 index 0000000000..e3d779ad16 --- /dev/null +++ b/wallet/internal/sql/pg/queries/accounts.sql @@ -0,0 +1,60 @@ +-- name: CreateAccount :exec +INSERT INTO accounts ( + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12); + +-- name: GetAccount :one +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = $1 AND account_number = $2; + +-- name: ListAccounts :many +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = $1 +ORDER BY account_number; + +-- name: RenameAccount :execrows +UPDATE accounts +SET account_name = $1 +WHERE scope_id = $2 AND account_number = $3; + +-- name: UpdateAccountIndexes :execrows +UPDATE accounts +SET next_external_index = $1, next_internal_index = $2 +WHERE scope_id = $3 AND account_number = $4; diff --git a/wallet/internal/sql/pg/queries/address_types.sql b/wallet/internal/sql/pg/queries/address_types.sql new file mode 100644 index 0000000000..879c9df0e2 --- /dev/null +++ b/wallet/internal/sql/pg/queries/address_types.sql @@ -0,0 +1,4 @@ +-- name: ListAddressTypes :many +SELECT id, type_name +FROM address_types +ORDER BY id; diff --git a/wallet/internal/sql/pg/queries/addresses.sql b/wallet/internal/sql/pg/queries/addresses.sql new file mode 100644 index 0000000000..dfc342d631 --- /dev/null +++ b/wallet/internal/sql/pg/queries/addresses.sql @@ -0,0 +1,70 @@ +-- name: CreateAddress :exec +INSERT INTO addresses ( + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, + $9, $10, $11, $12, $13, $14, $15, $16 +); + +-- name: GetAddress :one +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE wallet_id = $1 AND scope_id = $2 AND address_hash = $3; + +-- name: ListAccountAddresses :many +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE scope_id = $1 AND account_number = $2 +ORDER BY address_hash; + +-- name: MarkAddressUsed :execrows +UPDATE addresses +SET used = TRUE +WHERE wallet_id = $1 AND scope_id = $2 AND address_hash = $3; diff --git a/wallet/internal/sql/pg/queries/key_scopes.sql b/wallet/internal/sql/pg/queries/key_scopes.sql new file mode 100644 index 0000000000..020b9c664f --- /dev/null +++ b/wallet/internal/sql/pg/queries/key_scopes.sql @@ -0,0 +1,37 @@ +-- name: CreateKeyScope :one +INSERT INTO key_scopes ( + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + external_addr_type, + internal_addr_type +) VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id; + +-- name: GetKeyScope :one +SELECT + id, + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + last_account_number, + external_addr_type, + internal_addr_type +FROM key_scopes +WHERE wallet_id = $1 AND purpose = $2 AND coin_type = $3; + +-- name: UpdateKeyScopeKeys :execrows +UPDATE key_scopes +SET + encrypted_coin_pub_key = $1, + encrypted_coin_priv_key = $2 +WHERE id = $3 AND wallet_id = $4; + +-- name: UpdateLastAccountNumber :execrows +UPDATE key_scopes +SET last_account_number = $1 +WHERE id = $2 AND wallet_id = $3; diff --git a/wallet/internal/sql/pg/queries/wallets.sql b/wallet/internal/sql/pg/queries/wallets.sql new file mode 100644 index 0000000000..f3a487ef39 --- /dev/null +++ b/wallet/internal/sql/pg/queries/wallets.sql @@ -0,0 +1,84 @@ +-- name: CreateWallet :one +INSERT INTO wallets ( + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +RETURNING id; + +-- name: GetWalletByName :one +SELECT + id, + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +FROM wallets +WHERE wallet_name = $1; + +-- name: UpdateWalletEncryption :execrows +UPDATE wallets +SET + master_pub_params = $1, + master_priv_params = $2, + encrypted_crypto_pub_key = $3, + encrypted_crypto_priv_key = $4, + encrypted_crypto_script_key = $5, + encrypted_master_hd_pub_key = $6, + encrypted_master_hd_priv_key = $7 +WHERE id = $8; + +-- name: PutWalletSyncState :exec +INSERT INTO wallet_sync_states ( + wallet_id, + start_block_height, + synced_block_height, + birthday_timestamp, + birthday_block_height, + birthday_block_verified +) VALUES ($1, $2, $3, $4, $5, $6); + +-- name: GetWalletSyncState :one +SELECT + s.wallet_id, + s.start_block_height, + start_block.header_hash AS start_block_hash, + s.synced_block_height, + synced_block.header_hash AS synced_block_hash, + s.birthday_timestamp, + s.birthday_block_height, + birthday_block.header_hash AS birthday_block_hash, + s.birthday_block_verified +FROM wallet_sync_states AS s +INNER JOIN blocks AS start_block + ON s.start_block_height = start_block.block_height +INNER JOIN blocks AS synced_block + ON s.synced_block_height = synced_block.block_height +LEFT JOIN blocks AS birthday_block + ON s.birthday_block_height = birthday_block.block_height +WHERE s.wallet_id = $1; + +-- name: UpdateWalletSyncState :execrows +UPDATE wallet_sync_states +SET + start_block_height = $1, + synced_block_height = $2, + birthday_timestamp = $3, + birthday_block_height = $4, + birthday_block_verified = $5 +WHERE wallet_id = $6; diff --git a/wallet/internal/sql/pg/sqlc/accounts.sql.go b/wallet/internal/sql/pg/sqlc/accounts.sql.go new file mode 100644 index 0000000000..8e3387762f --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/accounts.sql.go @@ -0,0 +1,205 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: accounts.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateAccount = `-- name: CreateAccount :exec +INSERT INTO accounts ( + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) +` + +type CreateAccountParams struct { + WalletID int64 + ScopeID int64 + AccountNumber int64 + AccountType int16 + AccountName string + EncryptedPubKey []byte + EncryptedPrivKey []byte + MasterKeyFingerprint sql.NullInt64 + NextExternalIndex int64 + NextInternalIndex int64 + ExternalAddrType sql.NullInt16 + InternalAddrType sql.NullInt16 +} + +func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) error { + _, err := q.exec(ctx, q.createAccountStmt, CreateAccount, + arg.WalletID, + arg.ScopeID, + arg.AccountNumber, + arg.AccountType, + arg.AccountName, + arg.EncryptedPubKey, + arg.EncryptedPrivKey, + arg.MasterKeyFingerprint, + arg.NextExternalIndex, + arg.NextInternalIndex, + arg.ExternalAddrType, + arg.InternalAddrType, + ) + return err +} + +const GetAccount = `-- name: GetAccount :one +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = $1 AND account_number = $2 +` + +type GetAccountParams struct { + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) GetAccount(ctx context.Context, arg GetAccountParams) (Account, error) { + row := q.queryRow(ctx, q.getAccountStmt, GetAccount, arg.ScopeID, arg.AccountNumber) + var i Account + err := row.Scan( + &i.WalletID, + &i.ScopeID, + &i.AccountNumber, + &i.AccountType, + &i.AccountName, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.MasterKeyFingerprint, + &i.NextExternalIndex, + &i.NextInternalIndex, + &i.ExternalAddrType, + &i.InternalAddrType, + ) + return i, err +} + +const ListAccounts = `-- name: ListAccounts :many +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = $1 +ORDER BY account_number +` + +func (q *Queries) ListAccounts(ctx context.Context, scopeID int64) ([]Account, error) { + rows, err := q.query(ctx, q.listAccountsStmt, ListAccounts, scopeID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Account + for rows.Next() { + var i Account + if err := rows.Scan( + &i.WalletID, + &i.ScopeID, + &i.AccountNumber, + &i.AccountType, + &i.AccountName, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.MasterKeyFingerprint, + &i.NextExternalIndex, + &i.NextInternalIndex, + &i.ExternalAddrType, + &i.InternalAddrType, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const RenameAccount = `-- name: RenameAccount :execrows +UPDATE accounts +SET account_name = $1 +WHERE scope_id = $2 AND account_number = $3 +` + +type RenameAccountParams struct { + AccountName string + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) RenameAccount(ctx context.Context, arg RenameAccountParams) (int64, error) { + result, err := q.exec(ctx, q.renameAccountStmt, RenameAccount, arg.AccountName, arg.ScopeID, arg.AccountNumber) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +const UpdateAccountIndexes = `-- name: UpdateAccountIndexes :execrows +UPDATE accounts +SET next_external_index = $1, next_internal_index = $2 +WHERE scope_id = $3 AND account_number = $4 +` + +type UpdateAccountIndexesParams struct { + NextExternalIndex int64 + NextInternalIndex int64 + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) UpdateAccountIndexes(ctx context.Context, arg UpdateAccountIndexesParams) (int64, error) { + result, err := q.exec(ctx, q.updateAccountIndexesStmt, UpdateAccountIndexes, + arg.NextExternalIndex, + arg.NextInternalIndex, + arg.ScopeID, + arg.AccountNumber, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/wallet/internal/sql/pg/sqlc/address_types.sql.go b/wallet/internal/sql/pg/sqlc/address_types.sql.go new file mode 100644 index 0000000000..54b21a9a9f --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/address_types.sql.go @@ -0,0 +1,39 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: address_types.sql + +package sqlc + +import ( + "context" +) + +const ListAddressTypes = `-- name: ListAddressTypes :many +SELECT id, type_name +FROM address_types +ORDER BY id +` + +func (q *Queries) ListAddressTypes(ctx context.Context) ([]AddressType, error) { + rows, err := q.query(ctx, q.listAddressTypesStmt, ListAddressTypes) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AddressType + for rows.Next() { + var i AddressType + if err := rows.Scan(&i.ID, &i.TypeName); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/wallet/internal/sql/pg/sqlc/addresses.sql.go b/wallet/internal/sql/pg/sqlc/addresses.sql.go new file mode 100644 index 0000000000..3e0b27d49f --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/addresses.sql.go @@ -0,0 +1,216 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: addresses.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateAddress = `-- name: CreateAddress :exec +INSERT INTO addresses ( + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, + $9, $10, $11, $12, $13, $14, $15, $16 +) +` + +type CreateAddressParams struct { + WalletID int64 + ScopeID int64 + AddressHash []byte + AccountNumber int64 + AddressType int16 + AddedAt int64 + SyncStatus int16 + Branch sql.NullInt64 + AddressIndex sql.NullInt64 + EncryptedPubKey []byte + EncryptedPrivKey []byte + EncryptedHash []byte + EncryptedScript []byte + WitnessVersion sql.NullInt16 + IsSecretScript sql.NullBool + Used bool +} + +func (q *Queries) CreateAddress(ctx context.Context, arg CreateAddressParams) error { + _, err := q.exec(ctx, q.createAddressStmt, CreateAddress, + arg.WalletID, + arg.ScopeID, + arg.AddressHash, + arg.AccountNumber, + arg.AddressType, + arg.AddedAt, + arg.SyncStatus, + arg.Branch, + arg.AddressIndex, + arg.EncryptedPubKey, + arg.EncryptedPrivKey, + arg.EncryptedHash, + arg.EncryptedScript, + arg.WitnessVersion, + arg.IsSecretScript, + arg.Used, + ) + return err +} + +const GetAddress = `-- name: GetAddress :one +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE wallet_id = $1 AND scope_id = $2 AND address_hash = $3 +` + +type GetAddressParams struct { + WalletID int64 + ScopeID int64 + AddressHash []byte +} + +func (q *Queries) GetAddress(ctx context.Context, arg GetAddressParams) (Address, error) { + row := q.queryRow(ctx, q.getAddressStmt, GetAddress, arg.WalletID, arg.ScopeID, arg.AddressHash) + var i Address + err := row.Scan( + &i.WalletID, + &i.ScopeID, + &i.AddressHash, + &i.AccountNumber, + &i.AddressType, + &i.AddedAt, + &i.SyncStatus, + &i.Branch, + &i.AddressIndex, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.EncryptedHash, + &i.EncryptedScript, + &i.WitnessVersion, + &i.IsSecretScript, + &i.Used, + ) + return i, err +} + +const ListAccountAddresses = `-- name: ListAccountAddresses :many +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE scope_id = $1 AND account_number = $2 +ORDER BY address_hash +` + +type ListAccountAddressesParams struct { + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) ListAccountAddresses(ctx context.Context, arg ListAccountAddressesParams) ([]Address, error) { + rows, err := q.query(ctx, q.listAccountAddressesStmt, ListAccountAddresses, arg.ScopeID, arg.AccountNumber) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Address + for rows.Next() { + var i Address + if err := rows.Scan( + &i.WalletID, + &i.ScopeID, + &i.AddressHash, + &i.AccountNumber, + &i.AddressType, + &i.AddedAt, + &i.SyncStatus, + &i.Branch, + &i.AddressIndex, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.EncryptedHash, + &i.EncryptedScript, + &i.WitnessVersion, + &i.IsSecretScript, + &i.Used, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const MarkAddressUsed = `-- name: MarkAddressUsed :execrows +UPDATE addresses +SET used = TRUE +WHERE wallet_id = $1 AND scope_id = $2 AND address_hash = $3 +` + +type MarkAddressUsedParams struct { + WalletID int64 + ScopeID int64 + AddressHash []byte +} + +func (q *Queries) MarkAddressUsed(ctx context.Context, arg MarkAddressUsedParams) (int64, error) { + result, err := q.exec(ctx, q.markAddressUsedStmt, MarkAddressUsed, arg.WalletID, arg.ScopeID, arg.AddressHash) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/wallet/internal/sql/pg/sqlc/db.go b/wallet/internal/sql/pg/sqlc/db.go index 168d8395d4..3357da35b6 100644 --- a/wallet/internal/sql/pg/sqlc/db.go +++ b/wallet/internal/sql/pg/sqlc/db.go @@ -24,28 +24,118 @@ func New(db DBTX) *Queries { func Prepare(ctx context.Context, db DBTX) (*Queries, error) { q := Queries{db: db} var err error + if q.createAccountStmt, err = db.PrepareContext(ctx, CreateAccount); err != nil { + return nil, fmt.Errorf("error preparing query CreateAccount: %w", err) + } + if q.createAddressStmt, err = db.PrepareContext(ctx, CreateAddress); err != nil { + return nil, fmt.Errorf("error preparing query CreateAddress: %w", err) + } + if q.createKeyScopeStmt, err = db.PrepareContext(ctx, CreateKeyScope); err != nil { + return nil, fmt.Errorf("error preparing query CreateKeyScope: %w", err) + } + if q.createWalletStmt, err = db.PrepareContext(ctx, CreateWallet); err != nil { + return nil, fmt.Errorf("error preparing query CreateWallet: %w", err) + } if q.deleteBlockStmt, err = db.PrepareContext(ctx, DeleteBlock); err != nil { return nil, fmt.Errorf("error preparing query DeleteBlock: %w", err) } + if q.getAccountStmt, err = db.PrepareContext(ctx, GetAccount); err != nil { + return nil, fmt.Errorf("error preparing query GetAccount: %w", err) + } + if q.getAddressStmt, err = db.PrepareContext(ctx, GetAddress); err != nil { + return nil, fmt.Errorf("error preparing query GetAddress: %w", err) + } if q.getBlockByHeightStmt, err = db.PrepareContext(ctx, GetBlockByHeight); err != nil { return nil, fmt.Errorf("error preparing query GetBlockByHeight: %w", err) } if q.getBlocksInRangeStmt, err = db.PrepareContext(ctx, GetBlocksInRange); err != nil { return nil, fmt.Errorf("error preparing query GetBlocksInRange: %w", err) } + if q.getKeyScopeStmt, err = db.PrepareContext(ctx, GetKeyScope); err != nil { + return nil, fmt.Errorf("error preparing query GetKeyScope: %w", err) + } + if q.getWalletByNameStmt, err = db.PrepareContext(ctx, GetWalletByName); err != nil { + return nil, fmt.Errorf("error preparing query GetWalletByName: %w", err) + } + if q.getWalletSyncStateStmt, err = db.PrepareContext(ctx, GetWalletSyncState); err != nil { + return nil, fmt.Errorf("error preparing query GetWalletSyncState: %w", err) + } if q.insertBlockStmt, err = db.PrepareContext(ctx, InsertBlock); err != nil { return nil, fmt.Errorf("error preparing query InsertBlock: %w", err) } + if q.listAccountAddressesStmt, err = db.PrepareContext(ctx, ListAccountAddresses); err != nil { + return nil, fmt.Errorf("error preparing query ListAccountAddresses: %w", err) + } + if q.listAccountsStmt, err = db.PrepareContext(ctx, ListAccounts); err != nil { + return nil, fmt.Errorf("error preparing query ListAccounts: %w", err) + } + if q.listAddressTypesStmt, err = db.PrepareContext(ctx, ListAddressTypes); err != nil { + return nil, fmt.Errorf("error preparing query ListAddressTypes: %w", err) + } + if q.markAddressUsedStmt, err = db.PrepareContext(ctx, MarkAddressUsed); err != nil { + return nil, fmt.Errorf("error preparing query MarkAddressUsed: %w", err) + } + if q.putWalletSyncStateStmt, err = db.PrepareContext(ctx, PutWalletSyncState); err != nil { + return nil, fmt.Errorf("error preparing query PutWalletSyncState: %w", err) + } + if q.renameAccountStmt, err = db.PrepareContext(ctx, RenameAccount); err != nil { + return nil, fmt.Errorf("error preparing query RenameAccount: %w", err) + } + if q.updateAccountIndexesStmt, err = db.PrepareContext(ctx, UpdateAccountIndexes); err != nil { + return nil, fmt.Errorf("error preparing query UpdateAccountIndexes: %w", err) + } + if q.updateKeyScopeKeysStmt, err = db.PrepareContext(ctx, UpdateKeyScopeKeys); err != nil { + return nil, fmt.Errorf("error preparing query UpdateKeyScopeKeys: %w", err) + } + if q.updateLastAccountNumberStmt, err = db.PrepareContext(ctx, UpdateLastAccountNumber); err != nil { + return nil, fmt.Errorf("error preparing query UpdateLastAccountNumber: %w", err) + } + if q.updateWalletEncryptionStmt, err = db.PrepareContext(ctx, UpdateWalletEncryption); err != nil { + return nil, fmt.Errorf("error preparing query UpdateWalletEncryption: %w", err) + } + if q.updateWalletSyncStateStmt, err = db.PrepareContext(ctx, UpdateWalletSyncState); err != nil { + return nil, fmt.Errorf("error preparing query UpdateWalletSyncState: %w", err) + } return &q, nil } func (q *Queries) Close() error { var err error + if q.createAccountStmt != nil { + if cerr := q.createAccountStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createAccountStmt: %w", cerr) + } + } + if q.createAddressStmt != nil { + if cerr := q.createAddressStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createAddressStmt: %w", cerr) + } + } + if q.createKeyScopeStmt != nil { + if cerr := q.createKeyScopeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createKeyScopeStmt: %w", cerr) + } + } + if q.createWalletStmt != nil { + if cerr := q.createWalletStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createWalletStmt: %w", cerr) + } + } if q.deleteBlockStmt != nil { if cerr := q.deleteBlockStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deleteBlockStmt: %w", cerr) } } + if q.getAccountStmt != nil { + if cerr := q.getAccountStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getAccountStmt: %w", cerr) + } + } + if q.getAddressStmt != nil { + if cerr := q.getAddressStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getAddressStmt: %w", cerr) + } + } if q.getBlockByHeightStmt != nil { if cerr := q.getBlockByHeightStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getBlockByHeightStmt: %w", cerr) @@ -56,11 +146,81 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getBlocksInRangeStmt: %w", cerr) } } + if q.getKeyScopeStmt != nil { + if cerr := q.getKeyScopeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getKeyScopeStmt: %w", cerr) + } + } + if q.getWalletByNameStmt != nil { + if cerr := q.getWalletByNameStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getWalletByNameStmt: %w", cerr) + } + } + if q.getWalletSyncStateStmt != nil { + if cerr := q.getWalletSyncStateStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getWalletSyncStateStmt: %w", cerr) + } + } if q.insertBlockStmt != nil { if cerr := q.insertBlockStmt.Close(); cerr != nil { err = fmt.Errorf("error closing insertBlockStmt: %w", cerr) } } + if q.listAccountAddressesStmt != nil { + if cerr := q.listAccountAddressesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listAccountAddressesStmt: %w", cerr) + } + } + if q.listAccountsStmt != nil { + if cerr := q.listAccountsStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listAccountsStmt: %w", cerr) + } + } + if q.listAddressTypesStmt != nil { + if cerr := q.listAddressTypesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listAddressTypesStmt: %w", cerr) + } + } + if q.markAddressUsedStmt != nil { + if cerr := q.markAddressUsedStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing markAddressUsedStmt: %w", cerr) + } + } + if q.putWalletSyncStateStmt != nil { + if cerr := q.putWalletSyncStateStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing putWalletSyncStateStmt: %w", cerr) + } + } + if q.renameAccountStmt != nil { + if cerr := q.renameAccountStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing renameAccountStmt: %w", cerr) + } + } + if q.updateAccountIndexesStmt != nil { + if cerr := q.updateAccountIndexesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateAccountIndexesStmt: %w", cerr) + } + } + if q.updateKeyScopeKeysStmt != nil { + if cerr := q.updateKeyScopeKeysStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateKeyScopeKeysStmt: %w", cerr) + } + } + if q.updateLastAccountNumberStmt != nil { + if cerr := q.updateLastAccountNumberStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateLastAccountNumberStmt: %w", cerr) + } + } + if q.updateWalletEncryptionStmt != nil { + if cerr := q.updateWalletEncryptionStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateWalletEncryptionStmt: %w", cerr) + } + } + if q.updateWalletSyncStateStmt != nil { + if cerr := q.updateWalletSyncStateStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateWalletSyncStateStmt: %w", cerr) + } + } return err } @@ -98,21 +258,61 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar } type Queries struct { - db DBTX - tx *sql.Tx - deleteBlockStmt *sql.Stmt - getBlockByHeightStmt *sql.Stmt - getBlocksInRangeStmt *sql.Stmt - insertBlockStmt *sql.Stmt + db DBTX + tx *sql.Tx + createAccountStmt *sql.Stmt + createAddressStmt *sql.Stmt + createKeyScopeStmt *sql.Stmt + createWalletStmt *sql.Stmt + deleteBlockStmt *sql.Stmt + getAccountStmt *sql.Stmt + getAddressStmt *sql.Stmt + getBlockByHeightStmt *sql.Stmt + getBlocksInRangeStmt *sql.Stmt + getKeyScopeStmt *sql.Stmt + getWalletByNameStmt *sql.Stmt + getWalletSyncStateStmt *sql.Stmt + insertBlockStmt *sql.Stmt + listAccountAddressesStmt *sql.Stmt + listAccountsStmt *sql.Stmt + listAddressTypesStmt *sql.Stmt + markAddressUsedStmt *sql.Stmt + putWalletSyncStateStmt *sql.Stmt + renameAccountStmt *sql.Stmt + updateAccountIndexesStmt *sql.Stmt + updateKeyScopeKeysStmt *sql.Stmt + updateLastAccountNumberStmt *sql.Stmt + updateWalletEncryptionStmt *sql.Stmt + updateWalletSyncStateStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - deleteBlockStmt: q.deleteBlockStmt, - getBlockByHeightStmt: q.getBlockByHeightStmt, - getBlocksInRangeStmt: q.getBlocksInRangeStmt, - insertBlockStmt: q.insertBlockStmt, + db: tx, + tx: tx, + createAccountStmt: q.createAccountStmt, + createAddressStmt: q.createAddressStmt, + createKeyScopeStmt: q.createKeyScopeStmt, + createWalletStmt: q.createWalletStmt, + deleteBlockStmt: q.deleteBlockStmt, + getAccountStmt: q.getAccountStmt, + getAddressStmt: q.getAddressStmt, + getBlockByHeightStmt: q.getBlockByHeightStmt, + getBlocksInRangeStmt: q.getBlocksInRangeStmt, + getKeyScopeStmt: q.getKeyScopeStmt, + getWalletByNameStmt: q.getWalletByNameStmt, + getWalletSyncStateStmt: q.getWalletSyncStateStmt, + insertBlockStmt: q.insertBlockStmt, + listAccountAddressesStmt: q.listAccountAddressesStmt, + listAccountsStmt: q.listAccountsStmt, + listAddressTypesStmt: q.listAddressTypesStmt, + markAddressUsedStmt: q.markAddressUsedStmt, + putWalletSyncStateStmt: q.putWalletSyncStateStmt, + renameAccountStmt: q.renameAccountStmt, + updateAccountIndexesStmt: q.updateAccountIndexesStmt, + updateKeyScopeKeysStmt: q.updateKeyScopeKeysStmt, + updateLastAccountNumberStmt: q.updateLastAccountNumberStmt, + updateWalletEncryptionStmt: q.updateWalletEncryptionStmt, + updateWalletSyncStateStmt: q.updateWalletSyncStateStmt, } } diff --git a/wallet/internal/sql/pg/sqlc/key_scopes.sql.go b/wallet/internal/sql/pg/sqlc/key_scopes.sql.go new file mode 100644 index 0000000000..2aec105862 --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/key_scopes.sql.go @@ -0,0 +1,135 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: key_scopes.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateKeyScope = `-- name: CreateKeyScope :one +INSERT INTO key_scopes ( + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + external_addr_type, + internal_addr_type +) VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id +` + +type CreateKeyScopeParams struct { + WalletID int64 + Purpose int64 + CoinType int64 + EncryptedCoinPubKey []byte + EncryptedCoinPrivKey []byte + ExternalAddrType int16 + InternalAddrType int16 +} + +func (q *Queries) CreateKeyScope(ctx context.Context, arg CreateKeyScopeParams) (int64, error) { + row := q.queryRow(ctx, q.createKeyScopeStmt, CreateKeyScope, + arg.WalletID, + arg.Purpose, + arg.CoinType, + arg.EncryptedCoinPubKey, + arg.EncryptedCoinPrivKey, + arg.ExternalAddrType, + arg.InternalAddrType, + ) + var id int64 + err := row.Scan(&id) + return id, err +} + +const GetKeyScope = `-- name: GetKeyScope :one +SELECT + id, + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + last_account_number, + external_addr_type, + internal_addr_type +FROM key_scopes +WHERE wallet_id = $1 AND purpose = $2 AND coin_type = $3 +` + +type GetKeyScopeParams struct { + WalletID int64 + Purpose int64 + CoinType int64 +} + +func (q *Queries) GetKeyScope(ctx context.Context, arg GetKeyScopeParams) (KeyScope, error) { + row := q.queryRow(ctx, q.getKeyScopeStmt, GetKeyScope, arg.WalletID, arg.Purpose, arg.CoinType) + var i KeyScope + err := row.Scan( + &i.ID, + &i.WalletID, + &i.Purpose, + &i.CoinType, + &i.EncryptedCoinPubKey, + &i.EncryptedCoinPrivKey, + &i.LastAccountNumber, + &i.ExternalAddrType, + &i.InternalAddrType, + ) + return i, err +} + +const UpdateKeyScopeKeys = `-- name: UpdateKeyScopeKeys :execrows +UPDATE key_scopes +SET + encrypted_coin_pub_key = $1, + encrypted_coin_priv_key = $2 +WHERE id = $3 AND wallet_id = $4 +` + +type UpdateKeyScopeKeysParams struct { + EncryptedCoinPubKey []byte + EncryptedCoinPrivKey []byte + ID int64 + WalletID int64 +} + +func (q *Queries) UpdateKeyScopeKeys(ctx context.Context, arg UpdateKeyScopeKeysParams) (int64, error) { + result, err := q.exec(ctx, q.updateKeyScopeKeysStmt, UpdateKeyScopeKeys, + arg.EncryptedCoinPubKey, + arg.EncryptedCoinPrivKey, + arg.ID, + arg.WalletID, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +const UpdateLastAccountNumber = `-- name: UpdateLastAccountNumber :execrows +UPDATE key_scopes +SET last_account_number = $1 +WHERE id = $2 AND wallet_id = $3 +` + +type UpdateLastAccountNumberParams struct { + LastAccountNumber sql.NullInt64 + ID int64 + WalletID int64 +} + +func (q *Queries) UpdateLastAccountNumber(ctx context.Context, arg UpdateLastAccountNumberParams) (int64, error) { + result, err := q.exec(ctx, q.updateLastAccountNumberStmt, UpdateLastAccountNumber, arg.LastAccountNumber, arg.ID, arg.WalletID) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/wallet/internal/sql/pg/sqlc/models.go b/wallet/internal/sql/pg/sqlc/models.go index 9b53f59718..a0e553959a 100644 --- a/wallet/internal/sql/pg/sqlc/models.go +++ b/wallet/internal/sql/pg/sqlc/models.go @@ -4,8 +4,87 @@ package sqlc +import ( + "database/sql" +) + +type Account struct { + WalletID int64 + ScopeID int64 + AccountNumber int64 + AccountType int16 + AccountName string + EncryptedPubKey []byte + EncryptedPrivKey []byte + MasterKeyFingerprint sql.NullInt64 + NextExternalIndex int64 + NextInternalIndex int64 + ExternalAddrType sql.NullInt16 + InternalAddrType sql.NullInt16 +} + +type Address struct { + WalletID int64 + ScopeID int64 + AddressHash []byte + AccountNumber int64 + AddressType int16 + AddedAt int64 + SyncStatus int16 + Branch sql.NullInt64 + AddressIndex sql.NullInt64 + EncryptedPubKey []byte + EncryptedPrivKey []byte + EncryptedHash []byte + EncryptedScript []byte + WitnessVersion sql.NullInt16 + IsSecretScript sql.NullBool + Used bool +} + +type AddressType struct { + ID int16 + TypeName string +} + type Block struct { BlockHeight int32 HeaderHash []byte BlockTimestamp int64 } + +type KeyScope struct { + ID int64 + WalletID int64 + Purpose int64 + CoinType int64 + EncryptedCoinPubKey []byte + EncryptedCoinPrivKey []byte + LastAccountNumber sql.NullInt64 + ExternalAddrType int16 + InternalAddrType int16 +} + +type Wallet struct { + ID int64 + WalletName string + ManagerVersion int64 + ManagerCreatedAt int64 + IsWatchOnly bool + MasterPubParams []byte + MasterPrivParams []byte + EncryptedCryptoPubKey []byte + EncryptedCryptoPrivKey []byte + EncryptedCryptoScriptKey []byte + EncryptedMasterHdPubKey []byte + EncryptedMasterHdPrivKey []byte +} + +type WalletSyncState struct { + WalletID int64 + StartBlockHeight int32 + SyncedBlockHeight int32 + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt32 + BirthdayBlockVerified bool +} diff --git a/wallet/internal/sql/pg/sqlc/querier.go b/wallet/internal/sql/pg/sqlc/querier.go index 8ba410fea5..660e1042ad 100644 --- a/wallet/internal/sql/pg/sqlc/querier.go +++ b/wallet/internal/sql/pg/sqlc/querier.go @@ -9,10 +9,30 @@ import ( ) type Querier interface { + CreateAccount(ctx context.Context, arg CreateAccountParams) error + CreateAddress(ctx context.Context, arg CreateAddressParams) error + CreateKeyScope(ctx context.Context, arg CreateKeyScopeParams) (int64, error) + CreateWallet(ctx context.Context, arg CreateWalletParams) (int64, error) DeleteBlock(ctx context.Context, blockHeight int32) error + GetAccount(ctx context.Context, arg GetAccountParams) (Account, error) + GetAddress(ctx context.Context, arg GetAddressParams) (Address, error) GetBlockByHeight(ctx context.Context, blockHeight int32) (Block, error) GetBlocksInRange(ctx context.Context, arg GetBlocksInRangeParams) ([]Block, error) + GetKeyScope(ctx context.Context, arg GetKeyScopeParams) (KeyScope, error) + GetWalletByName(ctx context.Context, walletName string) (Wallet, error) + GetWalletSyncState(ctx context.Context, walletID int64) (GetWalletSyncStateRow, error) InsertBlock(ctx context.Context, arg InsertBlockParams) error + ListAccountAddresses(ctx context.Context, arg ListAccountAddressesParams) ([]Address, error) + ListAccounts(ctx context.Context, scopeID int64) ([]Account, error) + ListAddressTypes(ctx context.Context) ([]AddressType, error) + MarkAddressUsed(ctx context.Context, arg MarkAddressUsedParams) (int64, error) + PutWalletSyncState(ctx context.Context, arg PutWalletSyncStateParams) error + RenameAccount(ctx context.Context, arg RenameAccountParams) (int64, error) + UpdateAccountIndexes(ctx context.Context, arg UpdateAccountIndexesParams) (int64, error) + UpdateKeyScopeKeys(ctx context.Context, arg UpdateKeyScopeKeysParams) (int64, error) + UpdateLastAccountNumber(ctx context.Context, arg UpdateLastAccountNumberParams) (int64, error) + UpdateWalletEncryption(ctx context.Context, arg UpdateWalletEncryptionParams) (int64, error) + UpdateWalletSyncState(ctx context.Context, arg UpdateWalletSyncStateParams) (int64, error) } var _ Querier = (*Queries)(nil) diff --git a/wallet/internal/sql/pg/sqlc/wallets.sql.go b/wallet/internal/sql/pg/sqlc/wallets.sql.go new file mode 100644 index 0000000000..d7cef6f3a2 --- /dev/null +++ b/wallet/internal/sql/pg/sqlc/wallets.sql.go @@ -0,0 +1,257 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: wallets.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateWallet = `-- name: CreateWallet :one +INSERT INTO wallets ( + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) +RETURNING id +` + +type CreateWalletParams struct { + WalletName string + ManagerVersion int64 + ManagerCreatedAt int64 + IsWatchOnly bool + MasterPubParams []byte + MasterPrivParams []byte + EncryptedCryptoPubKey []byte + EncryptedCryptoPrivKey []byte + EncryptedCryptoScriptKey []byte + EncryptedMasterHdPubKey []byte + EncryptedMasterHdPrivKey []byte +} + +func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (int64, error) { + row := q.queryRow(ctx, q.createWalletStmt, CreateWallet, + arg.WalletName, + arg.ManagerVersion, + arg.ManagerCreatedAt, + arg.IsWatchOnly, + arg.MasterPubParams, + arg.MasterPrivParams, + arg.EncryptedCryptoPubKey, + arg.EncryptedCryptoPrivKey, + arg.EncryptedCryptoScriptKey, + arg.EncryptedMasterHdPubKey, + arg.EncryptedMasterHdPrivKey, + ) + var id int64 + err := row.Scan(&id) + return id, err +} + +const GetWalletByName = `-- name: GetWalletByName :one +SELECT + id, + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +FROM wallets +WHERE wallet_name = $1 +` + +func (q *Queries) GetWalletByName(ctx context.Context, walletName string) (Wallet, error) { + row := q.queryRow(ctx, q.getWalletByNameStmt, GetWalletByName, walletName) + var i Wallet + err := row.Scan( + &i.ID, + &i.WalletName, + &i.ManagerVersion, + &i.ManagerCreatedAt, + &i.IsWatchOnly, + &i.MasterPubParams, + &i.MasterPrivParams, + &i.EncryptedCryptoPubKey, + &i.EncryptedCryptoPrivKey, + &i.EncryptedCryptoScriptKey, + &i.EncryptedMasterHdPubKey, + &i.EncryptedMasterHdPrivKey, + ) + return i, err +} + +const GetWalletSyncState = `-- name: GetWalletSyncState :one +SELECT + s.wallet_id, + s.start_block_height, + start_block.header_hash AS start_block_hash, + s.synced_block_height, + synced_block.header_hash AS synced_block_hash, + s.birthday_timestamp, + s.birthday_block_height, + birthday_block.header_hash AS birthday_block_hash, + s.birthday_block_verified +FROM wallet_sync_states AS s +INNER JOIN blocks AS start_block + ON s.start_block_height = start_block.block_height +INNER JOIN blocks AS synced_block + ON s.synced_block_height = synced_block.block_height +LEFT JOIN blocks AS birthday_block + ON s.birthday_block_height = birthday_block.block_height +WHERE s.wallet_id = $1 +` + +type GetWalletSyncStateRow struct { + WalletID int64 + StartBlockHeight int32 + StartBlockHash []byte + SyncedBlockHeight int32 + SyncedBlockHash []byte + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt32 + BirthdayBlockHash []byte + BirthdayBlockVerified bool +} + +func (q *Queries) GetWalletSyncState(ctx context.Context, walletID int64) (GetWalletSyncStateRow, error) { + row := q.queryRow(ctx, q.getWalletSyncStateStmt, GetWalletSyncState, walletID) + var i GetWalletSyncStateRow + err := row.Scan( + &i.WalletID, + &i.StartBlockHeight, + &i.StartBlockHash, + &i.SyncedBlockHeight, + &i.SyncedBlockHash, + &i.BirthdayTimestamp, + &i.BirthdayBlockHeight, + &i.BirthdayBlockHash, + &i.BirthdayBlockVerified, + ) + return i, err +} + +const PutWalletSyncState = `-- name: PutWalletSyncState :exec +INSERT INTO wallet_sync_states ( + wallet_id, + start_block_height, + synced_block_height, + birthday_timestamp, + birthday_block_height, + birthday_block_verified +) VALUES ($1, $2, $3, $4, $5, $6) +` + +type PutWalletSyncStateParams struct { + WalletID int64 + StartBlockHeight int32 + SyncedBlockHeight int32 + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt32 + BirthdayBlockVerified bool +} + +func (q *Queries) PutWalletSyncState(ctx context.Context, arg PutWalletSyncStateParams) error { + _, err := q.exec(ctx, q.putWalletSyncStateStmt, PutWalletSyncState, + arg.WalletID, + arg.StartBlockHeight, + arg.SyncedBlockHeight, + arg.BirthdayTimestamp, + arg.BirthdayBlockHeight, + arg.BirthdayBlockVerified, + ) + return err +} + +const UpdateWalletEncryption = `-- name: UpdateWalletEncryption :execrows +UPDATE wallets +SET + master_pub_params = $1, + master_priv_params = $2, + encrypted_crypto_pub_key = $3, + encrypted_crypto_priv_key = $4, + encrypted_crypto_script_key = $5, + encrypted_master_hd_pub_key = $6, + encrypted_master_hd_priv_key = $7 +WHERE id = $8 +` + +type UpdateWalletEncryptionParams struct { + MasterPubParams []byte + MasterPrivParams []byte + EncryptedCryptoPubKey []byte + EncryptedCryptoPrivKey []byte + EncryptedCryptoScriptKey []byte + EncryptedMasterHdPubKey []byte + EncryptedMasterHdPrivKey []byte + ID int64 +} + +func (q *Queries) UpdateWalletEncryption(ctx context.Context, arg UpdateWalletEncryptionParams) (int64, error) { + result, err := q.exec(ctx, q.updateWalletEncryptionStmt, UpdateWalletEncryption, + arg.MasterPubParams, + arg.MasterPrivParams, + arg.EncryptedCryptoPubKey, + arg.EncryptedCryptoPrivKey, + arg.EncryptedCryptoScriptKey, + arg.EncryptedMasterHdPubKey, + arg.EncryptedMasterHdPrivKey, + arg.ID, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +const UpdateWalletSyncState = `-- name: UpdateWalletSyncState :execrows +UPDATE wallet_sync_states +SET + start_block_height = $1, + synced_block_height = $2, + birthday_timestamp = $3, + birthday_block_height = $4, + birthday_block_verified = $5 +WHERE wallet_id = $6 +` + +type UpdateWalletSyncStateParams struct { + StartBlockHeight int32 + SyncedBlockHeight int32 + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt32 + BirthdayBlockVerified bool + WalletID int64 +} + +func (q *Queries) UpdateWalletSyncState(ctx context.Context, arg UpdateWalletSyncStateParams) (int64, error) { + result, err := q.exec(ctx, q.updateWalletSyncStateStmt, UpdateWalletSyncState, + arg.StartBlockHeight, + arg.SyncedBlockHeight, + arg.BirthdayTimestamp, + arg.BirthdayBlockHeight, + arg.BirthdayBlockVerified, + arg.WalletID, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/wallet/internal/sql/sqlite/legacy_schema_test.go b/wallet/internal/sql/sqlite/legacy_schema_test.go new file mode 100644 index 0000000000..a72b8dd372 --- /dev/null +++ b/wallet/internal/sql/sqlite/legacy_schema_test.go @@ -0,0 +1,292 @@ +package sqlite + +import ( + "context" + "crypto/sha256" + "database/sql" + "path/filepath" + "testing" + + "github.com/btcsuite/btcwallet/wallet/internal/sql/sqlite/sqlc" + "github.com/stretchr/testify/require" +) + +// TestLegacySchemaQueries verifies that the SQL shape can represent the legacy +// wallet, scope, account, and address records without decrypting them. +func TestLegacySchemaQueries(t *testing.T) { + t.Parallel() + + ctx := context.Background() + db, err := Open(ctx, Config{ + DBPath: filepath.Join(t.TempDir(), "wallet.db"), + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, db.Close()) + }) + require.NoError(t, ApplyMigrations(db)) + requireNoPlaintextColumns(t, db) + + queries := sqlc.New(db) + genesisHash := bytesOf(0x01, 32) + birthdayHash := bytesOf(0x02, 32) + require.NoError(t, queries.InsertBlock(ctx, sqlc.InsertBlockParams{ + BlockHeight: 0, + HeaderHash: genesisHash, + BlockTimestamp: 1, + })) + require.NoError(t, queries.InsertBlock(ctx, sqlc.InsertBlockParams{ + BlockHeight: 100, + HeaderHash: birthdayHash, + BlockTimestamp: 2, + })) + + walletID, err := queries.CreateWallet(ctx, sqlc.CreateWalletParams{ + WalletName: "watch-only", + ManagerVersion: 8, + ManagerCreatedAt: 123, + IsWatchOnly: true, + MasterPubParams: []byte("encrypted-master-public-params"), + EncryptedCryptoPubKey: []byte("encrypted-crypto-public-key"), + }) + require.NoError(t, err) + + wallet, err := queries.GetWalletByName(ctx, "watch-only") + require.NoError(t, err) + require.Equal(t, int64(123), wallet.ManagerCreatedAt) + require.Equal(t, []byte("encrypted-master-public-params"), + wallet.MasterPubParams) + require.Nil(t, wallet.MasterPrivParams) + require.Equal(t, []byte("encrypted-crypto-public-key"), + wallet.EncryptedCryptoPubKey) + require.Nil(t, wallet.EncryptedCryptoPrivKey) + require.Nil(t, wallet.EncryptedCryptoScriptKey) + + require.NoError(t, queries.PutWalletSyncState( + ctx, sqlc.PutWalletSyncStateParams{ + WalletID: walletID, + StartBlockHeight: 0, + SyncedBlockHeight: 100, + BirthdayTimestamp: 1234, + BirthdayBlockHeight: sql.NullInt64{Int64: 100, Valid: true}, + BirthdayBlockVerified: true, + }, + )) + syncState, err := queries.GetWalletSyncState(ctx, walletID) + require.NoError(t, err) + require.Equal(t, genesisHash, syncState.StartBlockHash) + require.Equal(t, birthdayHash, syncState.SyncedBlockHash) + require.Equal(t, birthdayHash, syncState.BirthdayBlockHash) + + scopeID, err := queries.CreateKeyScope(ctx, sqlc.CreateKeyScopeParams{ + WalletID: walletID, + Purpose: 84, + CoinType: 0, + ExternalAddrType: 4, + InternalAddrType: 4, + }) + require.NoError(t, err) + scope, err := queries.GetKeyScope(ctx, sqlc.GetKeyScopeParams{ + WalletID: walletID, + Purpose: 84, + CoinType: 0, + }) + require.NoError(t, err) + require.Nil(t, scope.EncryptedCoinPubKey) + require.Nil(t, scope.EncryptedCoinPrivKey) + require.False(t, scope.LastAccountNumber.Valid) + rows, err := queries.UpdateLastAccountNumber( + ctx, sqlc.UpdateLastAccountNumberParams{ + LastAccountNumber: sql.NullInt64{Int64: 7, Valid: true}, + ID: scopeID, + WalletID: walletID, + }, + ) + require.NoError(t, err) + require.Equal(t, int64(1), rows) + scope, err = queries.GetKeyScope(ctx, sqlc.GetKeyScopeParams{ + WalletID: walletID, + Purpose: 84, + CoinType: 0, + }) + require.NoError(t, err) + require.Equal(t, int64(7), scope.LastAccountNumber.Int64) + + err = queries.CreateAccount(ctx, sqlc.CreateAccountParams{ + WalletID: walletID, + ScopeID: scopeID, + AccountNumber: 7, + AccountType: 1, + AccountName: "imported", + EncryptedPubKey: []byte("encrypted-account-public-key"), + MasterKeyFingerprint: sql.NullInt64{Int64: 42, Valid: true}, + ExternalAddrType: sql.NullInt64{Int64: 3, Valid: true}, + InternalAddrType: sql.NullInt64{Int64: 4, Valid: true}, + }) + require.NoError(t, err) + account, err := queries.GetAccount(ctx, sqlc.GetAccountParams{ + ScopeID: scopeID, + AccountNumber: 7, + }) + require.NoError(t, err) + require.Equal(t, int64(1), account.AccountType) + require.Equal(t, []byte("encrypted-account-public-key"), + account.EncryptedPubKey) + require.Nil(t, account.EncryptedPrivKey) + require.Equal(t, int64(42), account.MasterKeyFingerprint.Int64) + + createLegacyAddressRows(t, ctx, db, queries, walletID, scopeID) +} + +func requireNoPlaintextColumns(t *testing.T, db *sql.DB) { + t.Helper() + + columns := map[string][]string{ + "wallets": {"master_hd_pub_key"}, + "key_scopes": {"coin_pub_key", "coin_priv_key"}, + "accounts": {"public_key", "private_key"}, + "addresses": {"address_id", "script_pub_key", "pub_key", "script"}, + } + for table, tableColumns := range columns { + for _, column := range tableColumns { + var count int + err := db.QueryRow( + "SELECT count(*) FROM pragma_table_info(?) WHERE name = ?", + table, column, + ).Scan(&count) + require.NoError(t, err) + require.Zero(t, count, "%s.%s", table, column) + } + } +} + +func createLegacyAddressRows(t *testing.T, ctx context.Context, db *sql.DB, + queries *sqlc.Queries, walletID, scopeID int64) { + + t.Helper() + + chainHash := sha256.Sum256([]byte("chain-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: chainHash[:], + AccountNumber: 7, + AddressType: 0, + AddedAt: 10, + SyncStatus: 2, + Branch: sql.NullInt64{Int64: 0, Valid: true}, + AddressIndex: sql.NullInt64{Int64: 9, Valid: true}, + })) + + importedHash := sha256.Sum256([]byte("imported-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: importedHash[:], + AccountNumber: 7, + AddressType: 1, + AddedAt: 11, + SyncStatus: 2, + EncryptedPubKey: []byte("encrypted-imported-public-key"), + EncryptedPrivKey: []byte{}, + })) + + scriptHash := sha256.Sum256([]byte("script-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: scriptHash[:], + AccountNumber: 7, + AddressType: 2, + AddedAt: 12, + SyncStatus: 2, + EncryptedHash: []byte("encrypted-script-hash"), + EncryptedScript: []byte("encrypted-script"), + })) + + watchOnlyScriptHash := sha256.Sum256([]byte("watch-only-script-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: watchOnlyScriptHash[:], + AccountNumber: 7, + AddressType: 2, + AddedAt: 12, + SyncStatus: 2, + EncryptedHash: []byte("encrypted-watch-only-script-hash"), + })) + + witnessHash := sha256.Sum256([]byte("witness-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: witnessHash[:], + AccountNumber: 7, + AddressType: 3, + AddedAt: 13, + SyncStatus: 2, + EncryptedHash: []byte("encrypted-witness-hash"), + EncryptedScript: []byte("encrypted-witness-script"), + WitnessVersion: sql.NullInt64{Int64: 0, Valid: true}, + IsSecretScript: sql.NullBool{Bool: true, Valid: true}, + })) + + taprootHash := sha256.Sum256([]byte("taproot-address-id")) + require.NoError(t, queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: taprootHash[:], + AccountNumber: 7, + AddressType: 4, + AddedAt: 14, + SyncStatus: 2, + EncryptedHash: []byte("encrypted-taproot-hash"), + EncryptedScript: []byte("encrypted-taproot-script"), + WitnessVersion: sql.NullInt64{Int64: 1, Valid: true}, + IsSecretScript: sql.NullBool{Bool: false, Valid: true}, + })) + + rows, err := queries.MarkAddressUsed(ctx, sqlc.MarkAddressUsedParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: importedHash[:], + }) + require.NoError(t, err) + require.Equal(t, int64(1), rows) + address, err := queries.GetAddress(ctx, sqlc.GetAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: importedHash[:], + }) + require.NoError(t, err) + require.True(t, address.Used) + require.Equal(t, []byte("encrypted-imported-public-key"), + address.EncryptedPubKey) + + _, err = db.ExecContext(ctx, ` + UPDATE addresses SET used = FALSE + WHERE wallet_id = ? AND scope_id = ? AND address_hash = ? + `, walletID, scopeID, importedHash[:]) + require.ErrorContains(t, err, "address used state cannot be cleared") + + invalidHash := sha256.Sum256([]byte("invalid-chain-address-id")) + err = queries.CreateAddress(ctx, sqlc.CreateAddressParams{ + WalletID: walletID, + ScopeID: scopeID, + AddressHash: invalidHash[:], + AccountNumber: 7, + AddressType: 0, + AddedAt: 15, + SyncStatus: 2, + }) + require.Error(t, err) +} + +func bytesOf(value byte, count int) []byte { + b := make([]byte, count) + for i := range b { + b[i] = value + } + + return b +} diff --git a/wallet/internal/sql/sqlite/migrations/000002_wallets.down.sql b/wallet/internal/sql/sqlite/migrations/000002_wallets.down.sql new file mode 100644 index 0000000000..0b73d6e376 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000002_wallets.down.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS wallet_sync_states; +DROP TABLE IF EXISTS wallets; diff --git a/wallet/internal/sql/sqlite/migrations/000002_wallets.up.sql b/wallet/internal/sql/sqlite/migrations/000002_wallets.up.sql new file mode 100644 index 0000000000..f7f538f555 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000002_wallets.up.sql @@ -0,0 +1,39 @@ +-- The wallets table preserves the legacy waddrmgr encryption hierarchy. +CREATE TABLE wallets ( + id INTEGER PRIMARY KEY, + wallet_name TEXT NOT NULL UNIQUE, + manager_version INTEGER NOT NULL + CHECK (manager_version BETWEEN 0 AND 4294967295), + manager_created_at INTEGER NOT NULL CHECK (manager_created_at >= 0), + is_watch_only BOOLEAN NOT NULL CHECK (is_watch_only IN (FALSE, TRUE)), + master_pub_params BLOB NOT NULL, + master_priv_params BLOB, + encrypted_crypto_pub_key BLOB NOT NULL, + encrypted_crypto_priv_key BLOB, + encrypted_crypto_script_key BLOB, + encrypted_master_hd_pub_key BLOB, + encrypted_master_hd_priv_key BLOB +); + +-- The wallet sync state mirrors the block stamps and birthday metadata stored +-- by the legacy address manager. +CREATE TABLE wallet_sync_states ( + wallet_id INTEGER PRIMARY KEY, + start_block_height INTEGER NOT NULL, + synced_block_height INTEGER NOT NULL, + birthday_timestamp INTEGER NOT NULL CHECK (birthday_timestamp >= 0), + birthday_block_height INTEGER, + birthday_block_verified BOOLEAN NOT NULL DEFAULT FALSE + CHECK (birthday_block_verified IN (FALSE, TRUE)), + FOREIGN KEY (wallet_id) REFERENCES wallets (id) ON DELETE RESTRICT, + FOREIGN KEY (start_block_height) REFERENCES blocks (block_height) + ON DELETE RESTRICT, + FOREIGN KEY (synced_block_height) REFERENCES blocks (block_height) + ON DELETE RESTRICT, + FOREIGN KEY (birthday_block_height) REFERENCES blocks (block_height) + ON DELETE RESTRICT, + CHECK ( + birthday_block_verified = FALSE + OR birthday_block_height IS NOT NULL + ) +); diff --git a/wallet/internal/sql/sqlite/migrations/000003_address_types.down.sql b/wallet/internal/sql/sqlite/migrations/000003_address_types.down.sql new file mode 100644 index 0000000000..a8937217d1 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000003_address_types.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS address_types; diff --git a/wallet/internal/sql/sqlite/migrations/000003_address_types.up.sql b/wallet/internal/sql/sqlite/migrations/000003_address_types.up.sql new file mode 100644 index 0000000000..4a16f15e3c --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000003_address_types.up.sql @@ -0,0 +1,16 @@ +-- Address types are the stable public waddrmgr.AddressType values used by key +-- scope address schemas. +CREATE TABLE address_types ( + id INTEGER PRIMARY KEY CHECK (id BETWEEN 0 AND 7), + type_name TEXT NOT NULL UNIQUE +); + +INSERT INTO address_types (id, type_name) VALUES + (0, 'pubkey-hash'), + (1, 'script'), + (2, 'raw-pubkey'), + (3, 'nested-witness-pubkey'), + (4, 'witness-pubkey'), + (5, 'witness-script'), + (6, 'taproot-pubkey'), + (7, 'taproot-script'); diff --git a/wallet/internal/sql/sqlite/migrations/000004_key_scopes.down.sql b/wallet/internal/sql/sqlite/migrations/000004_key_scopes.down.sql new file mode 100644 index 0000000000..ae9bf8f8bc --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000004_key_scopes.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS key_scopes; diff --git a/wallet/internal/sql/sqlite/migrations/000004_key_scopes.up.sql b/wallet/internal/sql/sqlite/migrations/000004_key_scopes.up.sql new file mode 100644 index 0000000000..7f8579a9b1 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000004_key_scopes.up.sql @@ -0,0 +1,21 @@ +-- Key scopes preserve encrypted coin keys and the address schema associated +-- with each purpose and coin tuple. +CREATE TABLE key_scopes ( + id INTEGER PRIMARY KEY, + wallet_id INTEGER NOT NULL, + purpose INTEGER NOT NULL CHECK (purpose BETWEEN 0 AND 4294967295), + coin_type INTEGER NOT NULL CHECK (coin_type BETWEEN 0 AND 4294967295), + encrypted_coin_pub_key BLOB, + encrypted_coin_priv_key BLOB, + last_account_number INTEGER + CHECK (last_account_number BETWEEN 0 AND 4294967295), + external_addr_type INTEGER NOT NULL, + internal_addr_type INTEGER NOT NULL, + FOREIGN KEY (wallet_id) REFERENCES wallets (id) ON DELETE RESTRICT, + FOREIGN KEY (external_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + FOREIGN KEY (internal_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + UNIQUE (wallet_id, purpose, coin_type), + UNIQUE (id, wallet_id) +); diff --git a/wallet/internal/sql/sqlite/migrations/000005_accounts.down.sql b/wallet/internal/sql/sqlite/migrations/000005_accounts.down.sql new file mode 100644 index 0000000000..1616db4979 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000005_accounts.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS accounts; diff --git a/wallet/internal/sql/sqlite/migrations/000005_accounts.up.sql b/wallet/internal/sql/sqlite/migrations/000005_accounts.up.sql new file mode 100644 index 0000000000..b21968cc84 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000005_accounts.up.sql @@ -0,0 +1,47 @@ +-- Accounts preserve the two stable legacy account encodings. Every account, +-- including an imported watch-only account, retains its uint32 account number. +CREATE TABLE accounts ( + wallet_id INTEGER NOT NULL, + scope_id INTEGER NOT NULL, + account_number INTEGER NOT NULL + CHECK (account_number BETWEEN 0 AND 4294967295), + account_type INTEGER NOT NULL CHECK (account_type IN (0, 1)), + account_name TEXT NOT NULL, + encrypted_pub_key BLOB NOT NULL, + encrypted_priv_key BLOB, + master_key_fingerprint INTEGER + CHECK ( + master_key_fingerprint IS NULL + OR master_key_fingerprint BETWEEN 0 AND 4294967295 + ), + next_external_index INTEGER NOT NULL DEFAULT 0 + CHECK (next_external_index BETWEEN 0 AND 4294967295), + next_internal_index INTEGER NOT NULL DEFAULT 0 + CHECK (next_internal_index BETWEEN 0 AND 4294967295), + external_addr_type INTEGER, + internal_addr_type INTEGER, + FOREIGN KEY (scope_id, wallet_id) REFERENCES key_scopes (id, wallet_id) + ON DELETE RESTRICT, + FOREIGN KEY (external_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + FOREIGN KEY (internal_addr_type) REFERENCES address_types (id) + ON DELETE RESTRICT, + PRIMARY KEY (scope_id, account_number), + UNIQUE (scope_id, account_name), + UNIQUE (scope_id, account_number, wallet_id), + CHECK ( + (external_addr_type IS NULL AND internal_addr_type IS NULL) + OR (external_addr_type IS NOT NULL AND internal_addr_type IS NOT NULL) + ), + CHECK ( + (account_type = 0 + AND master_key_fingerprint IS NULL + AND external_addr_type IS NULL + AND internal_addr_type IS NULL) + OR ( + account_type = 1 + AND encrypted_priv_key IS NULL + AND master_key_fingerprint IS NOT NULL + ) + ) +); diff --git a/wallet/internal/sql/sqlite/migrations/000006_addresses.down.sql b/wallet/internal/sql/sqlite/migrations/000006_addresses.down.sql new file mode 100644 index 0000000000..9a3cd20201 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000006_addresses.down.sql @@ -0,0 +1,2 @@ +DROP TRIGGER IF EXISTS trg_addresses_used_is_monotonic; +DROP TABLE IF EXISTS addresses; diff --git a/wallet/internal/sql/sqlite/migrations/000006_addresses.up.sql b/wallet/internal/sql/sqlite/migrations/000006_addresses.up.sql new file mode 100644 index 0000000000..329cac2492 --- /dev/null +++ b/wallet/internal/sql/sqlite/migrations/000006_addresses.up.sql @@ -0,0 +1,73 @@ +-- Addresses are keyed by SHA256(addressID), matching the legacy address and +-- used-address buckets without exposing the encoded address in plaintext. +CREATE TABLE addresses ( + wallet_id INTEGER NOT NULL, + scope_id INTEGER NOT NULL, + address_hash BLOB NOT NULL CHECK (length(address_hash) = 32), + account_number INTEGER NOT NULL + CHECK (account_number BETWEEN 0 AND 4294967295), + address_type INTEGER NOT NULL CHECK (address_type BETWEEN 0 AND 4), + added_at INTEGER NOT NULL CHECK (added_at >= 0), + sync_status INTEGER NOT NULL CHECK (sync_status IN (0, 1, 2)), + branch INTEGER CHECK (branch BETWEEN 0 AND 4294967295), + address_index INTEGER CHECK (address_index BETWEEN 0 AND 4294967295), + encrypted_pub_key BLOB, + encrypted_priv_key BLOB, + encrypted_hash BLOB, + encrypted_script BLOB, + witness_version INTEGER CHECK (witness_version BETWEEN 0 AND 255), + is_secret_script BOOLEAN + CHECK (is_secret_script IS NULL OR is_secret_script IN (FALSE, TRUE)), + used BOOLEAN NOT NULL DEFAULT FALSE CHECK (used IN (FALSE, TRUE)), + PRIMARY KEY (wallet_id, scope_id, address_hash), + FOREIGN KEY (scope_id, account_number, wallet_id) + REFERENCES accounts (scope_id, account_number, wallet_id) + ON DELETE RESTRICT, + CHECK ( + (address_type = 0 + AND branch IS NOT NULL + AND address_index IS NOT NULL + AND encrypted_pub_key IS NULL + AND encrypted_priv_key IS NULL + AND encrypted_hash IS NULL + AND encrypted_script IS NULL + AND witness_version IS NULL + AND is_secret_script IS NULL) + OR (address_type = 1 + AND branch IS NULL + AND address_index IS NULL + AND encrypted_pub_key IS NOT NULL + AND encrypted_hash IS NULL + AND encrypted_script IS NULL + AND witness_version IS NULL + AND is_secret_script IS NULL) + OR (address_type = 2 + AND branch IS NULL + AND address_index IS NULL + AND encrypted_pub_key IS NULL + AND encrypted_priv_key IS NULL + AND encrypted_hash IS NOT NULL + AND witness_version IS NULL + AND is_secret_script IS NULL) + OR (address_type IN (3, 4) + AND branch IS NULL + AND address_index IS NULL + AND encrypted_pub_key IS NULL + AND encrypted_priv_key IS NULL + AND encrypted_hash IS NOT NULL + AND witness_version IS NOT NULL + AND is_secret_script IS NOT NULL) + ) +); + +CREATE INDEX idx_addresses_account +ON addresses (scope_id, account_number, address_hash); + +-- The used bit is sticky, as it is in the legacy used-address bucket. +CREATE TRIGGER trg_addresses_used_is_monotonic +BEFORE UPDATE OF used ON addresses +FOR EACH ROW +WHEN old.used = TRUE AND new.used = FALSE +BEGIN + SELECT raise(ABORT, 'address used state cannot be cleared'); +END; diff --git a/wallet/internal/sql/sqlite/migrations_test.go b/wallet/internal/sql/sqlite/migrations_test.go index 414be3d956..48b9ada1d0 100644 --- a/wallet/internal/sql/sqlite/migrations_test.go +++ b/wallet/internal/sql/sqlite/migrations_test.go @@ -23,23 +23,29 @@ func TestMigrationsRoundTrip(t *testing.T) { }) require.NoError(t, ApplyMigrations(db)) - requireBlocksTable(t, db, true) + requireSchemaTables(t, db, true) require.NoError(t, RollbackMigrations(db)) - requireBlocksTable(t, db, false) + requireSchemaTables(t, db, false) require.NoError(t, ApplyMigrations(db)) - requireBlocksTable(t, db, true) + requireSchemaTables(t, db, true) } -func requireBlocksTable(t *testing.T, db *sql.DB, expected bool) { +func requireSchemaTables(t *testing.T, db *sql.DB, expected bool) { t.Helper() - var count int - err := db.QueryRow( - "SELECT count(*) FROM sqlite_master " + - "WHERE type = 'table' AND name = 'blocks'", - ).Scan(&count) - require.NoError(t, err) - require.Equal(t, expected, count == 1) + tables := []string{ + "blocks", "wallets", "wallet_sync_states", "address_types", + "key_scopes", "accounts", "addresses", + } + for _, table := range tables { + var count int + err := db.QueryRow( + "SELECT count(*) FROM sqlite_master "+ + "WHERE type = 'table' AND name = ?", table, + ).Scan(&count) + require.NoError(t, err) + require.Equal(t, expected, count == 1, table) + } } diff --git a/wallet/internal/sql/sqlite/queries/accounts.sql b/wallet/internal/sql/sqlite/queries/accounts.sql new file mode 100644 index 0000000000..e92c6148f8 --- /dev/null +++ b/wallet/internal/sql/sqlite/queries/accounts.sql @@ -0,0 +1,60 @@ +-- name: CreateAccount :exec +INSERT INTO accounts ( + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + +-- name: GetAccount :one +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = ? AND account_number = ?; + +-- name: ListAccounts :many +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = ? +ORDER BY account_number; + +-- name: RenameAccount :execrows +UPDATE accounts +SET account_name = ? +WHERE scope_id = ? AND account_number = ?; + +-- name: UpdateAccountIndexes :execrows +UPDATE accounts +SET next_external_index = ?, next_internal_index = ? +WHERE scope_id = ? AND account_number = ?; diff --git a/wallet/internal/sql/sqlite/queries/address_types.sql b/wallet/internal/sql/sqlite/queries/address_types.sql new file mode 100644 index 0000000000..879c9df0e2 --- /dev/null +++ b/wallet/internal/sql/sqlite/queries/address_types.sql @@ -0,0 +1,4 @@ +-- name: ListAddressTypes :many +SELECT id, type_name +FROM address_types +ORDER BY id; diff --git a/wallet/internal/sql/sqlite/queries/addresses.sql b/wallet/internal/sql/sqlite/queries/addresses.sql new file mode 100644 index 0000000000..ea1ec35b7a --- /dev/null +++ b/wallet/internal/sql/sqlite/queries/addresses.sql @@ -0,0 +1,67 @@ +-- name: CreateAddress :exec +INSERT INTO addresses ( + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + +-- name: GetAddress :one +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE wallet_id = ? AND scope_id = ? AND address_hash = ?; + +-- name: ListAccountAddresses :many +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE scope_id = ? AND account_number = ? +ORDER BY address_hash; + +-- name: MarkAddressUsed :execrows +UPDATE addresses +SET used = TRUE +WHERE wallet_id = ? AND scope_id = ? AND address_hash = ?; diff --git a/wallet/internal/sql/sqlite/queries/key_scopes.sql b/wallet/internal/sql/sqlite/queries/key_scopes.sql new file mode 100644 index 0000000000..d6f0519dbb --- /dev/null +++ b/wallet/internal/sql/sqlite/queries/key_scopes.sql @@ -0,0 +1,37 @@ +-- name: CreateKeyScope :one +INSERT INTO key_scopes ( + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + external_addr_type, + internal_addr_type +) VALUES (?, ?, ?, ?, ?, ?, ?) +RETURNING id; + +-- name: GetKeyScope :one +SELECT + id, + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + last_account_number, + external_addr_type, + internal_addr_type +FROM key_scopes +WHERE wallet_id = ? AND purpose = ? AND coin_type = ?; + +-- name: UpdateKeyScopeKeys :execrows +UPDATE key_scopes +SET + encrypted_coin_pub_key = ?, + encrypted_coin_priv_key = ? +WHERE id = ? AND wallet_id = ?; + +-- name: UpdateLastAccountNumber :execrows +UPDATE key_scopes +SET last_account_number = ? +WHERE id = ? AND wallet_id = ?; diff --git a/wallet/internal/sql/sqlite/queries/wallets.sql b/wallet/internal/sql/sqlite/queries/wallets.sql new file mode 100644 index 0000000000..bffc499171 --- /dev/null +++ b/wallet/internal/sql/sqlite/queries/wallets.sql @@ -0,0 +1,84 @@ +-- name: CreateWallet :one +INSERT INTO wallets ( + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id; + +-- name: GetWalletByName :one +SELECT + id, + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +FROM wallets +WHERE wallet_name = ?; + +-- name: UpdateWalletEncryption :execrows +UPDATE wallets +SET + master_pub_params = ?, + master_priv_params = ?, + encrypted_crypto_pub_key = ?, + encrypted_crypto_priv_key = ?, + encrypted_crypto_script_key = ?, + encrypted_master_hd_pub_key = ?, + encrypted_master_hd_priv_key = ? +WHERE id = ?; + +-- name: PutWalletSyncState :exec +INSERT INTO wallet_sync_states ( + wallet_id, + start_block_height, + synced_block_height, + birthday_timestamp, + birthday_block_height, + birthday_block_verified +) VALUES (?, ?, ?, ?, ?, ?); + +-- name: GetWalletSyncState :one +SELECT + s.wallet_id, + s.start_block_height, + start_block.header_hash AS start_block_hash, + s.synced_block_height, + synced_block.header_hash AS synced_block_hash, + s.birthday_timestamp, + s.birthday_block_height, + birthday_block.header_hash AS birthday_block_hash, + s.birthday_block_verified +FROM wallet_sync_states AS s +INNER JOIN blocks AS start_block + ON s.start_block_height = start_block.block_height +INNER JOIN blocks AS synced_block + ON s.synced_block_height = synced_block.block_height +LEFT JOIN blocks AS birthday_block + ON s.birthday_block_height = birthday_block.block_height +WHERE s.wallet_id = ?; + +-- name: UpdateWalletSyncState :execrows +UPDATE wallet_sync_states +SET + start_block_height = ?, + synced_block_height = ?, + birthday_timestamp = ?, + birthday_block_height = ?, + birthday_block_verified = ? +WHERE wallet_id = ?; diff --git a/wallet/internal/sql/sqlite/sqlc/accounts.sql.go b/wallet/internal/sql/sqlite/sqlc/accounts.sql.go new file mode 100644 index 0000000000..5c892c3769 --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/accounts.sql.go @@ -0,0 +1,205 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: accounts.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateAccount = `-- name: CreateAccount :exec +INSERT INTO accounts ( + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +` + +type CreateAccountParams struct { + WalletID int64 + ScopeID int64 + AccountNumber int64 + AccountType int64 + AccountName string + EncryptedPubKey []byte + EncryptedPrivKey []byte + MasterKeyFingerprint sql.NullInt64 + NextExternalIndex int64 + NextInternalIndex int64 + ExternalAddrType sql.NullInt64 + InternalAddrType sql.NullInt64 +} + +func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) error { + _, err := q.exec(ctx, q.createAccountStmt, CreateAccount, + arg.WalletID, + arg.ScopeID, + arg.AccountNumber, + arg.AccountType, + arg.AccountName, + arg.EncryptedPubKey, + arg.EncryptedPrivKey, + arg.MasterKeyFingerprint, + arg.NextExternalIndex, + arg.NextInternalIndex, + arg.ExternalAddrType, + arg.InternalAddrType, + ) + return err +} + +const GetAccount = `-- name: GetAccount :one +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = ? AND account_number = ? +` + +type GetAccountParams struct { + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) GetAccount(ctx context.Context, arg GetAccountParams) (Account, error) { + row := q.queryRow(ctx, q.getAccountStmt, GetAccount, arg.ScopeID, arg.AccountNumber) + var i Account + err := row.Scan( + &i.WalletID, + &i.ScopeID, + &i.AccountNumber, + &i.AccountType, + &i.AccountName, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.MasterKeyFingerprint, + &i.NextExternalIndex, + &i.NextInternalIndex, + &i.ExternalAddrType, + &i.InternalAddrType, + ) + return i, err +} + +const ListAccounts = `-- name: ListAccounts :many +SELECT + wallet_id, + scope_id, + account_number, + account_type, + account_name, + encrypted_pub_key, + encrypted_priv_key, + master_key_fingerprint, + next_external_index, + next_internal_index, + external_addr_type, + internal_addr_type +FROM accounts +WHERE scope_id = ? +ORDER BY account_number +` + +func (q *Queries) ListAccounts(ctx context.Context, scopeID int64) ([]Account, error) { + rows, err := q.query(ctx, q.listAccountsStmt, ListAccounts, scopeID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Account + for rows.Next() { + var i Account + if err := rows.Scan( + &i.WalletID, + &i.ScopeID, + &i.AccountNumber, + &i.AccountType, + &i.AccountName, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.MasterKeyFingerprint, + &i.NextExternalIndex, + &i.NextInternalIndex, + &i.ExternalAddrType, + &i.InternalAddrType, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const RenameAccount = `-- name: RenameAccount :execrows +UPDATE accounts +SET account_name = ? +WHERE scope_id = ? AND account_number = ? +` + +type RenameAccountParams struct { + AccountName string + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) RenameAccount(ctx context.Context, arg RenameAccountParams) (int64, error) { + result, err := q.exec(ctx, q.renameAccountStmt, RenameAccount, arg.AccountName, arg.ScopeID, arg.AccountNumber) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +const UpdateAccountIndexes = `-- name: UpdateAccountIndexes :execrows +UPDATE accounts +SET next_external_index = ?, next_internal_index = ? +WHERE scope_id = ? AND account_number = ? +` + +type UpdateAccountIndexesParams struct { + NextExternalIndex int64 + NextInternalIndex int64 + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) UpdateAccountIndexes(ctx context.Context, arg UpdateAccountIndexesParams) (int64, error) { + result, err := q.exec(ctx, q.updateAccountIndexesStmt, UpdateAccountIndexes, + arg.NextExternalIndex, + arg.NextInternalIndex, + arg.ScopeID, + arg.AccountNumber, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/wallet/internal/sql/sqlite/sqlc/address_types.sql.go b/wallet/internal/sql/sqlite/sqlc/address_types.sql.go new file mode 100644 index 0000000000..54b21a9a9f --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/address_types.sql.go @@ -0,0 +1,39 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: address_types.sql + +package sqlc + +import ( + "context" +) + +const ListAddressTypes = `-- name: ListAddressTypes :many +SELECT id, type_name +FROM address_types +ORDER BY id +` + +func (q *Queries) ListAddressTypes(ctx context.Context) ([]AddressType, error) { + rows, err := q.query(ctx, q.listAddressTypesStmt, ListAddressTypes) + if err != nil { + return nil, err + } + defer rows.Close() + var items []AddressType + for rows.Next() { + var i AddressType + if err := rows.Scan(&i.ID, &i.TypeName); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/wallet/internal/sql/sqlite/sqlc/addresses.sql.go b/wallet/internal/sql/sqlite/sqlc/addresses.sql.go new file mode 100644 index 0000000000..e5767ed8b8 --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/addresses.sql.go @@ -0,0 +1,213 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: addresses.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateAddress = `-- name: CreateAddress :exec +INSERT INTO addresses ( + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +` + +type CreateAddressParams struct { + WalletID int64 + ScopeID int64 + AddressHash []byte + AccountNumber int64 + AddressType int64 + AddedAt int64 + SyncStatus int64 + Branch sql.NullInt64 + AddressIndex sql.NullInt64 + EncryptedPubKey []byte + EncryptedPrivKey []byte + EncryptedHash []byte + EncryptedScript []byte + WitnessVersion sql.NullInt64 + IsSecretScript sql.NullBool + Used bool +} + +func (q *Queries) CreateAddress(ctx context.Context, arg CreateAddressParams) error { + _, err := q.exec(ctx, q.createAddressStmt, CreateAddress, + arg.WalletID, + arg.ScopeID, + arg.AddressHash, + arg.AccountNumber, + arg.AddressType, + arg.AddedAt, + arg.SyncStatus, + arg.Branch, + arg.AddressIndex, + arg.EncryptedPubKey, + arg.EncryptedPrivKey, + arg.EncryptedHash, + arg.EncryptedScript, + arg.WitnessVersion, + arg.IsSecretScript, + arg.Used, + ) + return err +} + +const GetAddress = `-- name: GetAddress :one +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE wallet_id = ? AND scope_id = ? AND address_hash = ? +` + +type GetAddressParams struct { + WalletID int64 + ScopeID int64 + AddressHash []byte +} + +func (q *Queries) GetAddress(ctx context.Context, arg GetAddressParams) (Address, error) { + row := q.queryRow(ctx, q.getAddressStmt, GetAddress, arg.WalletID, arg.ScopeID, arg.AddressHash) + var i Address + err := row.Scan( + &i.WalletID, + &i.ScopeID, + &i.AddressHash, + &i.AccountNumber, + &i.AddressType, + &i.AddedAt, + &i.SyncStatus, + &i.Branch, + &i.AddressIndex, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.EncryptedHash, + &i.EncryptedScript, + &i.WitnessVersion, + &i.IsSecretScript, + &i.Used, + ) + return i, err +} + +const ListAccountAddresses = `-- name: ListAccountAddresses :many +SELECT + wallet_id, + scope_id, + address_hash, + account_number, + address_type, + added_at, + sync_status, + branch, + address_index, + encrypted_pub_key, + encrypted_priv_key, + encrypted_hash, + encrypted_script, + witness_version, + is_secret_script, + used +FROM addresses +WHERE scope_id = ? AND account_number = ? +ORDER BY address_hash +` + +type ListAccountAddressesParams struct { + ScopeID int64 + AccountNumber int64 +} + +func (q *Queries) ListAccountAddresses(ctx context.Context, arg ListAccountAddressesParams) ([]Address, error) { + rows, err := q.query(ctx, q.listAccountAddressesStmt, ListAccountAddresses, arg.ScopeID, arg.AccountNumber) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Address + for rows.Next() { + var i Address + if err := rows.Scan( + &i.WalletID, + &i.ScopeID, + &i.AddressHash, + &i.AccountNumber, + &i.AddressType, + &i.AddedAt, + &i.SyncStatus, + &i.Branch, + &i.AddressIndex, + &i.EncryptedPubKey, + &i.EncryptedPrivKey, + &i.EncryptedHash, + &i.EncryptedScript, + &i.WitnessVersion, + &i.IsSecretScript, + &i.Used, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const MarkAddressUsed = `-- name: MarkAddressUsed :execrows +UPDATE addresses +SET used = TRUE +WHERE wallet_id = ? AND scope_id = ? AND address_hash = ? +` + +type MarkAddressUsedParams struct { + WalletID int64 + ScopeID int64 + AddressHash []byte +} + +func (q *Queries) MarkAddressUsed(ctx context.Context, arg MarkAddressUsedParams) (int64, error) { + result, err := q.exec(ctx, q.markAddressUsedStmt, MarkAddressUsed, arg.WalletID, arg.ScopeID, arg.AddressHash) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/wallet/internal/sql/sqlite/sqlc/db.go b/wallet/internal/sql/sqlite/sqlc/db.go index 168d8395d4..3357da35b6 100644 --- a/wallet/internal/sql/sqlite/sqlc/db.go +++ b/wallet/internal/sql/sqlite/sqlc/db.go @@ -24,28 +24,118 @@ func New(db DBTX) *Queries { func Prepare(ctx context.Context, db DBTX) (*Queries, error) { q := Queries{db: db} var err error + if q.createAccountStmt, err = db.PrepareContext(ctx, CreateAccount); err != nil { + return nil, fmt.Errorf("error preparing query CreateAccount: %w", err) + } + if q.createAddressStmt, err = db.PrepareContext(ctx, CreateAddress); err != nil { + return nil, fmt.Errorf("error preparing query CreateAddress: %w", err) + } + if q.createKeyScopeStmt, err = db.PrepareContext(ctx, CreateKeyScope); err != nil { + return nil, fmt.Errorf("error preparing query CreateKeyScope: %w", err) + } + if q.createWalletStmt, err = db.PrepareContext(ctx, CreateWallet); err != nil { + return nil, fmt.Errorf("error preparing query CreateWallet: %w", err) + } if q.deleteBlockStmt, err = db.PrepareContext(ctx, DeleteBlock); err != nil { return nil, fmt.Errorf("error preparing query DeleteBlock: %w", err) } + if q.getAccountStmt, err = db.PrepareContext(ctx, GetAccount); err != nil { + return nil, fmt.Errorf("error preparing query GetAccount: %w", err) + } + if q.getAddressStmt, err = db.PrepareContext(ctx, GetAddress); err != nil { + return nil, fmt.Errorf("error preparing query GetAddress: %w", err) + } if q.getBlockByHeightStmt, err = db.PrepareContext(ctx, GetBlockByHeight); err != nil { return nil, fmt.Errorf("error preparing query GetBlockByHeight: %w", err) } if q.getBlocksInRangeStmt, err = db.PrepareContext(ctx, GetBlocksInRange); err != nil { return nil, fmt.Errorf("error preparing query GetBlocksInRange: %w", err) } + if q.getKeyScopeStmt, err = db.PrepareContext(ctx, GetKeyScope); err != nil { + return nil, fmt.Errorf("error preparing query GetKeyScope: %w", err) + } + if q.getWalletByNameStmt, err = db.PrepareContext(ctx, GetWalletByName); err != nil { + return nil, fmt.Errorf("error preparing query GetWalletByName: %w", err) + } + if q.getWalletSyncStateStmt, err = db.PrepareContext(ctx, GetWalletSyncState); err != nil { + return nil, fmt.Errorf("error preparing query GetWalletSyncState: %w", err) + } if q.insertBlockStmt, err = db.PrepareContext(ctx, InsertBlock); err != nil { return nil, fmt.Errorf("error preparing query InsertBlock: %w", err) } + if q.listAccountAddressesStmt, err = db.PrepareContext(ctx, ListAccountAddresses); err != nil { + return nil, fmt.Errorf("error preparing query ListAccountAddresses: %w", err) + } + if q.listAccountsStmt, err = db.PrepareContext(ctx, ListAccounts); err != nil { + return nil, fmt.Errorf("error preparing query ListAccounts: %w", err) + } + if q.listAddressTypesStmt, err = db.PrepareContext(ctx, ListAddressTypes); err != nil { + return nil, fmt.Errorf("error preparing query ListAddressTypes: %w", err) + } + if q.markAddressUsedStmt, err = db.PrepareContext(ctx, MarkAddressUsed); err != nil { + return nil, fmt.Errorf("error preparing query MarkAddressUsed: %w", err) + } + if q.putWalletSyncStateStmt, err = db.PrepareContext(ctx, PutWalletSyncState); err != nil { + return nil, fmt.Errorf("error preparing query PutWalletSyncState: %w", err) + } + if q.renameAccountStmt, err = db.PrepareContext(ctx, RenameAccount); err != nil { + return nil, fmt.Errorf("error preparing query RenameAccount: %w", err) + } + if q.updateAccountIndexesStmt, err = db.PrepareContext(ctx, UpdateAccountIndexes); err != nil { + return nil, fmt.Errorf("error preparing query UpdateAccountIndexes: %w", err) + } + if q.updateKeyScopeKeysStmt, err = db.PrepareContext(ctx, UpdateKeyScopeKeys); err != nil { + return nil, fmt.Errorf("error preparing query UpdateKeyScopeKeys: %w", err) + } + if q.updateLastAccountNumberStmt, err = db.PrepareContext(ctx, UpdateLastAccountNumber); err != nil { + return nil, fmt.Errorf("error preparing query UpdateLastAccountNumber: %w", err) + } + if q.updateWalletEncryptionStmt, err = db.PrepareContext(ctx, UpdateWalletEncryption); err != nil { + return nil, fmt.Errorf("error preparing query UpdateWalletEncryption: %w", err) + } + if q.updateWalletSyncStateStmt, err = db.PrepareContext(ctx, UpdateWalletSyncState); err != nil { + return nil, fmt.Errorf("error preparing query UpdateWalletSyncState: %w", err) + } return &q, nil } func (q *Queries) Close() error { var err error + if q.createAccountStmt != nil { + if cerr := q.createAccountStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createAccountStmt: %w", cerr) + } + } + if q.createAddressStmt != nil { + if cerr := q.createAddressStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createAddressStmt: %w", cerr) + } + } + if q.createKeyScopeStmt != nil { + if cerr := q.createKeyScopeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createKeyScopeStmt: %w", cerr) + } + } + if q.createWalletStmt != nil { + if cerr := q.createWalletStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing createWalletStmt: %w", cerr) + } + } if q.deleteBlockStmt != nil { if cerr := q.deleteBlockStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deleteBlockStmt: %w", cerr) } } + if q.getAccountStmt != nil { + if cerr := q.getAccountStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getAccountStmt: %w", cerr) + } + } + if q.getAddressStmt != nil { + if cerr := q.getAddressStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getAddressStmt: %w", cerr) + } + } if q.getBlockByHeightStmt != nil { if cerr := q.getBlockByHeightStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getBlockByHeightStmt: %w", cerr) @@ -56,11 +146,81 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getBlocksInRangeStmt: %w", cerr) } } + if q.getKeyScopeStmt != nil { + if cerr := q.getKeyScopeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getKeyScopeStmt: %w", cerr) + } + } + if q.getWalletByNameStmt != nil { + if cerr := q.getWalletByNameStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getWalletByNameStmt: %w", cerr) + } + } + if q.getWalletSyncStateStmt != nil { + if cerr := q.getWalletSyncStateStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getWalletSyncStateStmt: %w", cerr) + } + } if q.insertBlockStmt != nil { if cerr := q.insertBlockStmt.Close(); cerr != nil { err = fmt.Errorf("error closing insertBlockStmt: %w", cerr) } } + if q.listAccountAddressesStmt != nil { + if cerr := q.listAccountAddressesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listAccountAddressesStmt: %w", cerr) + } + } + if q.listAccountsStmt != nil { + if cerr := q.listAccountsStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listAccountsStmt: %w", cerr) + } + } + if q.listAddressTypesStmt != nil { + if cerr := q.listAddressTypesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listAddressTypesStmt: %w", cerr) + } + } + if q.markAddressUsedStmt != nil { + if cerr := q.markAddressUsedStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing markAddressUsedStmt: %w", cerr) + } + } + if q.putWalletSyncStateStmt != nil { + if cerr := q.putWalletSyncStateStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing putWalletSyncStateStmt: %w", cerr) + } + } + if q.renameAccountStmt != nil { + if cerr := q.renameAccountStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing renameAccountStmt: %w", cerr) + } + } + if q.updateAccountIndexesStmt != nil { + if cerr := q.updateAccountIndexesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateAccountIndexesStmt: %w", cerr) + } + } + if q.updateKeyScopeKeysStmt != nil { + if cerr := q.updateKeyScopeKeysStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateKeyScopeKeysStmt: %w", cerr) + } + } + if q.updateLastAccountNumberStmt != nil { + if cerr := q.updateLastAccountNumberStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateLastAccountNumberStmt: %w", cerr) + } + } + if q.updateWalletEncryptionStmt != nil { + if cerr := q.updateWalletEncryptionStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateWalletEncryptionStmt: %w", cerr) + } + } + if q.updateWalletSyncStateStmt != nil { + if cerr := q.updateWalletSyncStateStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateWalletSyncStateStmt: %w", cerr) + } + } return err } @@ -98,21 +258,61 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar } type Queries struct { - db DBTX - tx *sql.Tx - deleteBlockStmt *sql.Stmt - getBlockByHeightStmt *sql.Stmt - getBlocksInRangeStmt *sql.Stmt - insertBlockStmt *sql.Stmt + db DBTX + tx *sql.Tx + createAccountStmt *sql.Stmt + createAddressStmt *sql.Stmt + createKeyScopeStmt *sql.Stmt + createWalletStmt *sql.Stmt + deleteBlockStmt *sql.Stmt + getAccountStmt *sql.Stmt + getAddressStmt *sql.Stmt + getBlockByHeightStmt *sql.Stmt + getBlocksInRangeStmt *sql.Stmt + getKeyScopeStmt *sql.Stmt + getWalletByNameStmt *sql.Stmt + getWalletSyncStateStmt *sql.Stmt + insertBlockStmt *sql.Stmt + listAccountAddressesStmt *sql.Stmt + listAccountsStmt *sql.Stmt + listAddressTypesStmt *sql.Stmt + markAddressUsedStmt *sql.Stmt + putWalletSyncStateStmt *sql.Stmt + renameAccountStmt *sql.Stmt + updateAccountIndexesStmt *sql.Stmt + updateKeyScopeKeysStmt *sql.Stmt + updateLastAccountNumberStmt *sql.Stmt + updateWalletEncryptionStmt *sql.Stmt + updateWalletSyncStateStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - deleteBlockStmt: q.deleteBlockStmt, - getBlockByHeightStmt: q.getBlockByHeightStmt, - getBlocksInRangeStmt: q.getBlocksInRangeStmt, - insertBlockStmt: q.insertBlockStmt, + db: tx, + tx: tx, + createAccountStmt: q.createAccountStmt, + createAddressStmt: q.createAddressStmt, + createKeyScopeStmt: q.createKeyScopeStmt, + createWalletStmt: q.createWalletStmt, + deleteBlockStmt: q.deleteBlockStmt, + getAccountStmt: q.getAccountStmt, + getAddressStmt: q.getAddressStmt, + getBlockByHeightStmt: q.getBlockByHeightStmt, + getBlocksInRangeStmt: q.getBlocksInRangeStmt, + getKeyScopeStmt: q.getKeyScopeStmt, + getWalletByNameStmt: q.getWalletByNameStmt, + getWalletSyncStateStmt: q.getWalletSyncStateStmt, + insertBlockStmt: q.insertBlockStmt, + listAccountAddressesStmt: q.listAccountAddressesStmt, + listAccountsStmt: q.listAccountsStmt, + listAddressTypesStmt: q.listAddressTypesStmt, + markAddressUsedStmt: q.markAddressUsedStmt, + putWalletSyncStateStmt: q.putWalletSyncStateStmt, + renameAccountStmt: q.renameAccountStmt, + updateAccountIndexesStmt: q.updateAccountIndexesStmt, + updateKeyScopeKeysStmt: q.updateKeyScopeKeysStmt, + updateLastAccountNumberStmt: q.updateLastAccountNumberStmt, + updateWalletEncryptionStmt: q.updateWalletEncryptionStmt, + updateWalletSyncStateStmt: q.updateWalletSyncStateStmt, } } diff --git a/wallet/internal/sql/sqlite/sqlc/key_scopes.sql.go b/wallet/internal/sql/sqlite/sqlc/key_scopes.sql.go new file mode 100644 index 0000000000..ff19ef286b --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/key_scopes.sql.go @@ -0,0 +1,135 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: key_scopes.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateKeyScope = `-- name: CreateKeyScope :one +INSERT INTO key_scopes ( + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + external_addr_type, + internal_addr_type +) VALUES (?, ?, ?, ?, ?, ?, ?) +RETURNING id +` + +type CreateKeyScopeParams struct { + WalletID int64 + Purpose int64 + CoinType int64 + EncryptedCoinPubKey []byte + EncryptedCoinPrivKey []byte + ExternalAddrType int64 + InternalAddrType int64 +} + +func (q *Queries) CreateKeyScope(ctx context.Context, arg CreateKeyScopeParams) (int64, error) { + row := q.queryRow(ctx, q.createKeyScopeStmt, CreateKeyScope, + arg.WalletID, + arg.Purpose, + arg.CoinType, + arg.EncryptedCoinPubKey, + arg.EncryptedCoinPrivKey, + arg.ExternalAddrType, + arg.InternalAddrType, + ) + var id int64 + err := row.Scan(&id) + return id, err +} + +const GetKeyScope = `-- name: GetKeyScope :one +SELECT + id, + wallet_id, + purpose, + coin_type, + encrypted_coin_pub_key, + encrypted_coin_priv_key, + last_account_number, + external_addr_type, + internal_addr_type +FROM key_scopes +WHERE wallet_id = ? AND purpose = ? AND coin_type = ? +` + +type GetKeyScopeParams struct { + WalletID int64 + Purpose int64 + CoinType int64 +} + +func (q *Queries) GetKeyScope(ctx context.Context, arg GetKeyScopeParams) (KeyScope, error) { + row := q.queryRow(ctx, q.getKeyScopeStmt, GetKeyScope, arg.WalletID, arg.Purpose, arg.CoinType) + var i KeyScope + err := row.Scan( + &i.ID, + &i.WalletID, + &i.Purpose, + &i.CoinType, + &i.EncryptedCoinPubKey, + &i.EncryptedCoinPrivKey, + &i.LastAccountNumber, + &i.ExternalAddrType, + &i.InternalAddrType, + ) + return i, err +} + +const UpdateKeyScopeKeys = `-- name: UpdateKeyScopeKeys :execrows +UPDATE key_scopes +SET + encrypted_coin_pub_key = ?, + encrypted_coin_priv_key = ? +WHERE id = ? AND wallet_id = ? +` + +type UpdateKeyScopeKeysParams struct { + EncryptedCoinPubKey []byte + EncryptedCoinPrivKey []byte + ID int64 + WalletID int64 +} + +func (q *Queries) UpdateKeyScopeKeys(ctx context.Context, arg UpdateKeyScopeKeysParams) (int64, error) { + result, err := q.exec(ctx, q.updateKeyScopeKeysStmt, UpdateKeyScopeKeys, + arg.EncryptedCoinPubKey, + arg.EncryptedCoinPrivKey, + arg.ID, + arg.WalletID, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +const UpdateLastAccountNumber = `-- name: UpdateLastAccountNumber :execrows +UPDATE key_scopes +SET last_account_number = ? +WHERE id = ? AND wallet_id = ? +` + +type UpdateLastAccountNumberParams struct { + LastAccountNumber sql.NullInt64 + ID int64 + WalletID int64 +} + +func (q *Queries) UpdateLastAccountNumber(ctx context.Context, arg UpdateLastAccountNumberParams) (int64, error) { + result, err := q.exec(ctx, q.updateLastAccountNumberStmt, UpdateLastAccountNumber, arg.LastAccountNumber, arg.ID, arg.WalletID) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/wallet/internal/sql/sqlite/sqlc/models.go b/wallet/internal/sql/sqlite/sqlc/models.go index d99a13623f..ff251e8271 100644 --- a/wallet/internal/sql/sqlite/sqlc/models.go +++ b/wallet/internal/sql/sqlite/sqlc/models.go @@ -4,8 +4,87 @@ package sqlc +import ( + "database/sql" +) + +type Account struct { + WalletID int64 + ScopeID int64 + AccountNumber int64 + AccountType int64 + AccountName string + EncryptedPubKey []byte + EncryptedPrivKey []byte + MasterKeyFingerprint sql.NullInt64 + NextExternalIndex int64 + NextInternalIndex int64 + ExternalAddrType sql.NullInt64 + InternalAddrType sql.NullInt64 +} + +type Address struct { + WalletID int64 + ScopeID int64 + AddressHash []byte + AccountNumber int64 + AddressType int64 + AddedAt int64 + SyncStatus int64 + Branch sql.NullInt64 + AddressIndex sql.NullInt64 + EncryptedPubKey []byte + EncryptedPrivKey []byte + EncryptedHash []byte + EncryptedScript []byte + WitnessVersion sql.NullInt64 + IsSecretScript sql.NullBool + Used bool +} + +type AddressType struct { + ID int64 + TypeName string +} + type Block struct { BlockHeight int64 HeaderHash []byte BlockTimestamp int64 } + +type KeyScope struct { + ID int64 + WalletID int64 + Purpose int64 + CoinType int64 + EncryptedCoinPubKey []byte + EncryptedCoinPrivKey []byte + LastAccountNumber sql.NullInt64 + ExternalAddrType int64 + InternalAddrType int64 +} + +type Wallet struct { + ID int64 + WalletName string + ManagerVersion int64 + ManagerCreatedAt int64 + IsWatchOnly bool + MasterPubParams []byte + MasterPrivParams []byte + EncryptedCryptoPubKey []byte + EncryptedCryptoPrivKey []byte + EncryptedCryptoScriptKey []byte + EncryptedMasterHdPubKey []byte + EncryptedMasterHdPrivKey []byte +} + +type WalletSyncState struct { + WalletID int64 + StartBlockHeight int64 + SyncedBlockHeight int64 + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt64 + BirthdayBlockVerified bool +} diff --git a/wallet/internal/sql/sqlite/sqlc/querier.go b/wallet/internal/sql/sqlite/sqlc/querier.go index 815602423a..a47e92882b 100644 --- a/wallet/internal/sql/sqlite/sqlc/querier.go +++ b/wallet/internal/sql/sqlite/sqlc/querier.go @@ -9,10 +9,30 @@ import ( ) type Querier interface { + CreateAccount(ctx context.Context, arg CreateAccountParams) error + CreateAddress(ctx context.Context, arg CreateAddressParams) error + CreateKeyScope(ctx context.Context, arg CreateKeyScopeParams) (int64, error) + CreateWallet(ctx context.Context, arg CreateWalletParams) (int64, error) DeleteBlock(ctx context.Context, blockHeight int64) error + GetAccount(ctx context.Context, arg GetAccountParams) (Account, error) + GetAddress(ctx context.Context, arg GetAddressParams) (Address, error) GetBlockByHeight(ctx context.Context, blockHeight int64) (Block, error) GetBlocksInRange(ctx context.Context, arg GetBlocksInRangeParams) ([]Block, error) + GetKeyScope(ctx context.Context, arg GetKeyScopeParams) (KeyScope, error) + GetWalletByName(ctx context.Context, walletName string) (Wallet, error) + GetWalletSyncState(ctx context.Context, walletID int64) (GetWalletSyncStateRow, error) InsertBlock(ctx context.Context, arg InsertBlockParams) error + ListAccountAddresses(ctx context.Context, arg ListAccountAddressesParams) ([]Address, error) + ListAccounts(ctx context.Context, scopeID int64) ([]Account, error) + ListAddressTypes(ctx context.Context) ([]AddressType, error) + MarkAddressUsed(ctx context.Context, arg MarkAddressUsedParams) (int64, error) + PutWalletSyncState(ctx context.Context, arg PutWalletSyncStateParams) error + RenameAccount(ctx context.Context, arg RenameAccountParams) (int64, error) + UpdateAccountIndexes(ctx context.Context, arg UpdateAccountIndexesParams) (int64, error) + UpdateKeyScopeKeys(ctx context.Context, arg UpdateKeyScopeKeysParams) (int64, error) + UpdateLastAccountNumber(ctx context.Context, arg UpdateLastAccountNumberParams) (int64, error) + UpdateWalletEncryption(ctx context.Context, arg UpdateWalletEncryptionParams) (int64, error) + UpdateWalletSyncState(ctx context.Context, arg UpdateWalletSyncStateParams) (int64, error) } var _ Querier = (*Queries)(nil) diff --git a/wallet/internal/sql/sqlite/sqlc/wallets.sql.go b/wallet/internal/sql/sqlite/sqlc/wallets.sql.go new file mode 100644 index 0000000000..b99ef58f75 --- /dev/null +++ b/wallet/internal/sql/sqlite/sqlc/wallets.sql.go @@ -0,0 +1,257 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: wallets.sql + +package sqlc + +import ( + "context" + "database/sql" +) + +const CreateWallet = `-- name: CreateWallet :one +INSERT INTO wallets ( + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +RETURNING id +` + +type CreateWalletParams struct { + WalletName string + ManagerVersion int64 + ManagerCreatedAt int64 + IsWatchOnly bool + MasterPubParams []byte + MasterPrivParams []byte + EncryptedCryptoPubKey []byte + EncryptedCryptoPrivKey []byte + EncryptedCryptoScriptKey []byte + EncryptedMasterHdPubKey []byte + EncryptedMasterHdPrivKey []byte +} + +func (q *Queries) CreateWallet(ctx context.Context, arg CreateWalletParams) (int64, error) { + row := q.queryRow(ctx, q.createWalletStmt, CreateWallet, + arg.WalletName, + arg.ManagerVersion, + arg.ManagerCreatedAt, + arg.IsWatchOnly, + arg.MasterPubParams, + arg.MasterPrivParams, + arg.EncryptedCryptoPubKey, + arg.EncryptedCryptoPrivKey, + arg.EncryptedCryptoScriptKey, + arg.EncryptedMasterHdPubKey, + arg.EncryptedMasterHdPrivKey, + ) + var id int64 + err := row.Scan(&id) + return id, err +} + +const GetWalletByName = `-- name: GetWalletByName :one +SELECT + id, + wallet_name, + manager_version, + manager_created_at, + is_watch_only, + master_pub_params, + master_priv_params, + encrypted_crypto_pub_key, + encrypted_crypto_priv_key, + encrypted_crypto_script_key, + encrypted_master_hd_pub_key, + encrypted_master_hd_priv_key +FROM wallets +WHERE wallet_name = ? +` + +func (q *Queries) GetWalletByName(ctx context.Context, walletName string) (Wallet, error) { + row := q.queryRow(ctx, q.getWalletByNameStmt, GetWalletByName, walletName) + var i Wallet + err := row.Scan( + &i.ID, + &i.WalletName, + &i.ManagerVersion, + &i.ManagerCreatedAt, + &i.IsWatchOnly, + &i.MasterPubParams, + &i.MasterPrivParams, + &i.EncryptedCryptoPubKey, + &i.EncryptedCryptoPrivKey, + &i.EncryptedCryptoScriptKey, + &i.EncryptedMasterHdPubKey, + &i.EncryptedMasterHdPrivKey, + ) + return i, err +} + +const GetWalletSyncState = `-- name: GetWalletSyncState :one +SELECT + s.wallet_id, + s.start_block_height, + start_block.header_hash AS start_block_hash, + s.synced_block_height, + synced_block.header_hash AS synced_block_hash, + s.birthday_timestamp, + s.birthday_block_height, + birthday_block.header_hash AS birthday_block_hash, + s.birthday_block_verified +FROM wallet_sync_states AS s +INNER JOIN blocks AS start_block + ON s.start_block_height = start_block.block_height +INNER JOIN blocks AS synced_block + ON s.synced_block_height = synced_block.block_height +LEFT JOIN blocks AS birthday_block + ON s.birthday_block_height = birthday_block.block_height +WHERE s.wallet_id = ? +` + +type GetWalletSyncStateRow struct { + WalletID int64 + StartBlockHeight int64 + StartBlockHash []byte + SyncedBlockHeight int64 + SyncedBlockHash []byte + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt64 + BirthdayBlockHash []byte + BirthdayBlockVerified bool +} + +func (q *Queries) GetWalletSyncState(ctx context.Context, walletID int64) (GetWalletSyncStateRow, error) { + row := q.queryRow(ctx, q.getWalletSyncStateStmt, GetWalletSyncState, walletID) + var i GetWalletSyncStateRow + err := row.Scan( + &i.WalletID, + &i.StartBlockHeight, + &i.StartBlockHash, + &i.SyncedBlockHeight, + &i.SyncedBlockHash, + &i.BirthdayTimestamp, + &i.BirthdayBlockHeight, + &i.BirthdayBlockHash, + &i.BirthdayBlockVerified, + ) + return i, err +} + +const PutWalletSyncState = `-- name: PutWalletSyncState :exec +INSERT INTO wallet_sync_states ( + wallet_id, + start_block_height, + synced_block_height, + birthday_timestamp, + birthday_block_height, + birthday_block_verified +) VALUES (?, ?, ?, ?, ?, ?) +` + +type PutWalletSyncStateParams struct { + WalletID int64 + StartBlockHeight int64 + SyncedBlockHeight int64 + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt64 + BirthdayBlockVerified bool +} + +func (q *Queries) PutWalletSyncState(ctx context.Context, arg PutWalletSyncStateParams) error { + _, err := q.exec(ctx, q.putWalletSyncStateStmt, PutWalletSyncState, + arg.WalletID, + arg.StartBlockHeight, + arg.SyncedBlockHeight, + arg.BirthdayTimestamp, + arg.BirthdayBlockHeight, + arg.BirthdayBlockVerified, + ) + return err +} + +const UpdateWalletEncryption = `-- name: UpdateWalletEncryption :execrows +UPDATE wallets +SET + master_pub_params = ?, + master_priv_params = ?, + encrypted_crypto_pub_key = ?, + encrypted_crypto_priv_key = ?, + encrypted_crypto_script_key = ?, + encrypted_master_hd_pub_key = ?, + encrypted_master_hd_priv_key = ? +WHERE id = ? +` + +type UpdateWalletEncryptionParams struct { + MasterPubParams []byte + MasterPrivParams []byte + EncryptedCryptoPubKey []byte + EncryptedCryptoPrivKey []byte + EncryptedCryptoScriptKey []byte + EncryptedMasterHdPubKey []byte + EncryptedMasterHdPrivKey []byte + ID int64 +} + +func (q *Queries) UpdateWalletEncryption(ctx context.Context, arg UpdateWalletEncryptionParams) (int64, error) { + result, err := q.exec(ctx, q.updateWalletEncryptionStmt, UpdateWalletEncryption, + arg.MasterPubParams, + arg.MasterPrivParams, + arg.EncryptedCryptoPubKey, + arg.EncryptedCryptoPrivKey, + arg.EncryptedCryptoScriptKey, + arg.EncryptedMasterHdPubKey, + arg.EncryptedMasterHdPrivKey, + arg.ID, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +const UpdateWalletSyncState = `-- name: UpdateWalletSyncState :execrows +UPDATE wallet_sync_states +SET + start_block_height = ?, + synced_block_height = ?, + birthday_timestamp = ?, + birthday_block_height = ?, + birthday_block_verified = ? +WHERE wallet_id = ? +` + +type UpdateWalletSyncStateParams struct { + StartBlockHeight int64 + SyncedBlockHeight int64 + BirthdayTimestamp int64 + BirthdayBlockHeight sql.NullInt64 + BirthdayBlockVerified bool + WalletID int64 +} + +func (q *Queries) UpdateWalletSyncState(ctx context.Context, arg UpdateWalletSyncStateParams) (int64, error) { + result, err := q.exec(ctx, q.updateWalletSyncStateStmt, UpdateWalletSyncState, + arg.StartBlockHeight, + arg.SyncedBlockHeight, + arg.BirthdayTimestamp, + arg.BirthdayBlockHeight, + arg.BirthdayBlockVerified, + arg.WalletID, + ) + if err != nil { + return 0, err + } + return result.RowsAffected() +}