From b0957c1bba5b21f918c57059f53894cf4dc403a0 Mon Sep 17 00:00:00 2001 From: ziggie Date: Mon, 7 Apr 2025 16:26:12 +0200 Subject: [PATCH 1/6] logs: update logger to use btclog v2 --- cmd_init_wallet.go | 22 +++++++++++----------- cmd_store_configmap.go | 6 +++--- cmd_store_secret.go | 6 +++--- cmd_wait_ready.go | 16 ++++++++-------- go.mod | 6 +++--- go.sum | 4 ++-- k8s.go | 34 +++++++++++++++++----------------- log.go | 23 ++++++++++++----------- main.go | 39 ++++++++++++++++++++++----------------- 9 files changed, 81 insertions(+), 75 deletions(-) diff --git a/cmd_init_wallet.go b/cmd_init_wallet.go index 40f8055..1965445 100644 --- a/cmd_init_wallet.go +++ b/cmd_init_wallet.go @@ -159,7 +159,7 @@ func (x *initWalletCommand) Execute(_ []string) error { if x.InitRpc.WatchOnly { // For initializing a watch-only wallet we need the // accounts JSON file. - log("Reading accounts from file") + logger.Info("Reading accounts from file") accountsBytes, err := readFile(x.InitRpc.AccountsFile) if err != nil { return err @@ -215,7 +215,7 @@ func (x *initWalletCommand) readInput(requireSeed bool) (string, string, string, // Read all secrets from individual files. case storageFile: if requireSeed { - log("Reading seed from file") + logger.Info("Reading seed from file") seed, err = readFile(x.File.Seed) if err != nil { return "", "", "", err @@ -224,14 +224,14 @@ func (x *initWalletCommand) readInput(requireSeed bool) (string, string, string, // The seed passphrase is optional. if x.File.SeedPassphrase != "" { - log("Reading seed passphrase from file") + logger.Info("Reading seed passphrase from file") seedPassPhrase, err = readFile(x.File.SeedPassphrase) if err != nil { return "", "", "", err } } - log("Reading wallet password from file") + logger.Info("Reading wallet password from file") walletPassword, err = readFile(x.File.WalletPassword) if err != nil { return "", "", "", err @@ -248,7 +248,7 @@ func (x *initWalletCommand) readInput(requireSeed bool) (string, string, string, } if requireSeed { - log("Reading seed from k8s secret %s (namespace %s)", + logger.Infof("Reading seed from k8s secret %s (namespace %s)", x.K8s.SecretName, x.K8s.Namespace) seed, _, err = readK8s(k8sSecret) if err != nil { @@ -258,7 +258,7 @@ func (x *initWalletCommand) readInput(requireSeed bool) (string, string, string, // The seed passphrase is optional. if x.K8s.SeedPassphraseKeyName != "" { - log("Reading seed passphrase from k8s secret %s "+ + logger.Infof("Reading seed passphrase from k8s secret %s "+ "(namespace %s)", x.K8s.SecretName, x.K8s.Namespace) k8sSecret.KeyName = x.K8s.SeedPassphraseKeyName @@ -268,7 +268,7 @@ func (x *initWalletCommand) readInput(requireSeed bool) (string, string, string, } } - log("Reading wallet password from k8s secret %s (namespace %s)", + logger.Infof("Reading wallet password from k8s secret %s (namespace %s)", x.K8s.SecretName, x.K8s.Namespace) k8sSecret.KeyName = x.K8s.WalletPasswordKeyName walletPassword, _, err = readK8s(k8sSecret) @@ -329,7 +329,7 @@ func createWalletFile(cipherSeed *aezeed.CipherSeed, walletPassword, walletDir, func createWallet(walletDir string, cipherSeed *aezeed.CipherSeed, walletPassword []byte, network string) error { - log("Creating new wallet in %s", walletDir) + logger.Infof("Creating new wallet in %s", walletDir) // The network parameters are needed for some wallet internal things // like the chain genesis hash and timestamp. @@ -358,7 +358,7 @@ func createWallet(walletDir string, cipherSeed *aezeed.CipherSeed, err) } - log("Wallet created successfully in %s", walletDir) + logger.Infof("Wallet created successfully in %s", walletDir) return nil } @@ -366,7 +366,7 @@ func createWallet(walletDir string, cipherSeed *aezeed.CipherSeed, func validateWallet(walletDir string, walletPassword []byte, network string) error { - log("Validating password for wallet in %s", walletDir) + logger.Infof("Validating password for wallet in %s", walletDir) // The network parameters are needed for some wallet internal things // like the chain genesis hash and timestamp. @@ -391,7 +391,7 @@ func validateWallet(walletDir string, walletPassword []byte, err) } - log("Wallet password validated successfully") + logger.Info("Wallet password validated successfully") return nil } diff --git a/cmd_store_configmap.go b/cmd_store_configmap.go index a7ba094..8f09cc8 100644 --- a/cmd_store_configmap.go +++ b/cmd_store_configmap.go @@ -59,7 +59,7 @@ func (x *storeConfigmapCommand) Execute(args []string) error { case x.Batch: for _, file := range args { - log("Reading value/entry from file %s", file) + logger.Infof("Reading value/entry from file %s", file) content, err := readFile(file) if err != nil { return fmt.Errorf("cannot read file %s: %v", @@ -73,7 +73,7 @@ func (x *storeConfigmapCommand) Execute(args []string) error { } default: - log("Reading value/entry from stdin") + logger.Info("Reading value/entry from stdin") value, err := io.ReadAll(os.Stdin) if err != nil { return fmt.Errorf("error reading entry from stdin: %v", err) @@ -115,7 +115,7 @@ func storeConfigmapsK8s(entries []*entry, opts *targetK8sConfigmap, ObjectType: ObjectTypeConfigMap, } - log("Storing key with name %s to configmap %s in namespace %s", + logger.Infof("Storing key with name %s to configmap %s in namespace %s", entryOpts.KeyName, entryOpts.Name, entryOpts.Namespace) diff --git a/cmd_store_secret.go b/cmd_store_secret.go index 14f4e55..d20ded5 100644 --- a/cmd_store_secret.go +++ b/cmd_store_secret.go @@ -68,7 +68,7 @@ func (x *storeSecretCommand) Execute(args []string) error { case x.Batch: for _, file := range args { - log("Reading secret from file %s", file) + logger.Infof("Reading secret from file %s", file) content, err := readFile(file) if err != nil { return fmt.Errorf("cannot read file %s: %v", @@ -82,7 +82,7 @@ func (x *storeSecretCommand) Execute(args []string) error { } default: - log("Reading secret from stdin") + logger.Info("Reading secret from stdin") secret, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil && err != io.EOF { return fmt.Errorf("error reading secret from stdin: %v", @@ -126,7 +126,7 @@ func storeSecretsK8s(entries []*entry, opts *targetK8sSecret, ObjectType: ObjectTypeSecret, } - log("Storing key with name %s to secret %s in namespace %s", + logger.Infof("Storing key with name %s to secret %s in namespace %s", entryOpts.KeyName, entryOpts.Name, entryOpts.Namespace) err := saveK8s(entry.value, entryOpts, overwrite, opts.Helm) diff --git a/cmd_wait_ready.go b/cmd_wait_ready.go index 9632772..b0c6a2d 100644 --- a/cmd_wait_ready.go +++ b/cmd_wait_ready.go @@ -58,7 +58,7 @@ func (x *waitReadyCommand) Execute(_ []string) error { timeout := time.Duration(math.MaxInt64) if x.Timeout > 0 { timeout = x.Timeout - log("Will time out in %v (%s)", timeout, started.Add(timeout)) + logger.Infof("Will time out in %v (%s)", timeout, started.Add(timeout)) } return waitUntilStatus( @@ -70,17 +70,17 @@ func (x *waitReadyCommand) Execute(_ []string) error { func waitUntilStatus(rpcServer string, desiredState lnrpc.WalletState, timeout time.Duration, shutdown <-chan struct{}) error { - log("Waiting for lnd to become ready (want state %v)", desiredState) + logger.Infof("Waiting for lnd to become ready (want state %v)", desiredState) connectionRetryTicker := time.NewTicker(connectionRetryInterval) timeoutChan := time.After(timeout) connectionLoop: for { - log("Attempting to connect to RPC server %s", rpcServer) + logger.Infof("Attempting to connect to RPC server %s", rpcServer) conn, err := getStatusConnection(rpcServer) if err != nil { - log("Connection to lnd not successful: %v", err) + logger.Errorf("Connection to lnd not successful: %v", err) select { case <-connectionRetryTicker.C: @@ -93,12 +93,12 @@ connectionLoop: continue } - log("Attempting to subscribe to the wallet state") + logger.Info("Attempting to subscribe to the wallet state") statusStream, err := conn.SubscribeState( context.Background(), &lnrpc.SubscribeStateRequest{}, ) if err != nil { - log("Status subscription for lnd not successful: %v", + logger.Errorf("Status subscription for lnd not successful: %v", err) select { @@ -124,7 +124,7 @@ connectionLoop: msg, err := statusStream.Recv() if err != nil { - log("Error receiving status update: %v", err) + logger.Errorf("Error receiving status update: %v", err) select { case <-connectionRetryTicker.C: @@ -140,7 +140,7 @@ connectionLoop: continue connectionLoop } - log("Received update from lnd, wallet status is now: "+ + logger.Infof("Received update from lnd, wallet status is now: "+ "%v", msg.State) // We've arrived at the final state! diff --git a/go.mod b/go.mod index 2b66171..68f4517 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/lightninglabs/lndinit require ( github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 + github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c + github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 github.com/btcsuite/btcwallet v0.16.12 github.com/jessevdk/go-flags v1.4.0 github.com/kkdai/bstream v1.0.0 @@ -26,12 +28,10 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.5 // indirect github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect - github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect - github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 // indirect github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 // indirect github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 // indirect - github.com/btcsuite/btcwallet/walletdb v1.4.4 // indirect + github.com/btcsuite/btcwallet/walletdb v1.4.5-0.20250311184728-a7bb395324e8 // indirect github.com/btcsuite/btcwallet/wtxmgr v1.5.4 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect diff --git a/go.sum b/go.sum index 4536fd6..b4d443e 100644 --- a/go.sum +++ b/go.sum @@ -107,8 +107,8 @@ github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsm github.com/btcsuite/btcwallet/wallet/txrules v1.2.2/go.mod h1:4v+grppsDpVn91SJv+mZT7B8hEV4nSmpREM4I8Uohws= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 h1:93o5Xz9dYepBP4RMFUc9RGIFXwqP2volSWRkYJFrNtI= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw= -github.com/btcsuite/btcwallet/walletdb v1.4.4 h1:BDel6iT/ltYSIYKs0YbjwnEDi7xR3yzABIsQxN2F1L8= -github.com/btcsuite/btcwallet/walletdb v1.4.4/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= +github.com/btcsuite/btcwallet/walletdb v1.4.5-0.20250311184728-a7bb395324e8 h1:itzwCi4TYQFyyCu7qYLXxAZ0APYo1cFiEJuPX1Hv8Z4= +github.com/btcsuite/btcwallet/walletdb v1.4.5-0.20250311184728-a7bb395324e8/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= github.com/btcsuite/btcwallet/wtxmgr v1.5.4 h1:hJjHy1h/dJwSfD9uDsCwcH21D1iOrus6OrI5gR9E/O0= github.com/btcsuite/btcwallet/wtxmgr v1.5.4/go.mod h1:lAv0b1Vj9Ig5U8QFm0yiJ9WqPl8yGO/6l7JxdHY1PKE= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= diff --git a/k8s.go b/k8s.go index 1f92adc..8264e33 100644 --- a/k8s.go +++ b/k8s.go @@ -174,37 +174,37 @@ func readSecretK8s(client *kubernetes.Clientset, } func getClientK8s() (*kubernetes.Clientset, error) { - log("Creating k8s cluster config") + logger.Info("Creating k8s cluster config") config, err := rest.InClusterConfig() if err != nil { return nil, fmt.Errorf("unable to grab cluster config: %v", err) } - log("Creating k8s cluster client") + logger.Info("Creating k8s cluster client") client, err := kubernetes.NewForConfig(config) if err != nil { return nil, fmt.Errorf("error creating cluster config: %v", err) } - log("Cluster client created successfully") + logger.Info("Cluster client created successfully") return client, nil } func getSecretK8s(client *kubernetes.Clientset, namespace, name string) (*api.Secret, bool, error) { - log("Attempting to load secret %s from namespace %s", name, namespace) + logger.Infof("Attempting to load secret %s from namespace %s", name, namespace) secret, err := client.CoreV1().Secrets(namespace).Get( context.Background(), name, metav1.GetOptions{}, ) switch { case err == nil: - log("Secret %s loaded successfully", name) + logger.Infof("Secret %s loaded successfully", name) return secret, true, nil case errors.IsNotFound(err): - log("Secret %s not found in namespace %s", name, namespace) + logger.Infof("Secret %s not found in namespace %s", name, namespace) return nil, false, nil default: @@ -217,7 +217,7 @@ func updateSecretValueK8s(client *kubernetes.Clientset, secret *api.Secret, opts *k8sObjectOptions, overwrite bool, content string) error { if len(secret.Data) == 0 { - log("Data of secret %s is empty, initializing", opts.Name) + logger.Infof("Data of secret %s is empty, initializing", opts.Name) secret.Data = make(map[string][]byte) } @@ -233,7 +233,7 @@ func updateSecretValueK8s(client *kubernetes.Clientset, secret *api.Secret, } secret.Data[opts.KeyName] = []byte(content) - log("Attempting to update key %s of secret %s in namespace %s", + logger.Infof("Attempting to update key %s of secret %s in namespace %s", opts.KeyName, opts.Name, opts.Namespace) updatedSecret, err := client.CoreV1().Secrets(opts.Namespace).Update( context.Background(), secret, metav1.UpdateOptions{}, @@ -247,7 +247,7 @@ func updateSecretValueK8s(client *kubernetes.Clientset, secret *api.Secret, TypeMeta: updatedSecret.TypeMeta, ObjectMeta: updatedSecret.ObjectMeta, }) - log("Updated secret: %s", jsonSecret) + logger.Infof("Updated secret: %s", jsonSecret) return nil } @@ -295,7 +295,7 @@ func createSecretK8s(client *kubernetes.Clientset, opts *k8sObjectOptions, TypeMeta: updatedSecret.TypeMeta, ObjectMeta: updatedSecret.ObjectMeta, }) - log("Created secret: %s", jsonSecret) + logger.Infof("Created secret: %s", jsonSecret) return nil } @@ -320,18 +320,18 @@ func secretToString(rawSecret []byte, doubleBase64 bool) (string, error) { func getConfigMapK8s(client *kubernetes.Clientset, namespace, name string) (*api.ConfigMap, bool, error) { - log("Attempting to load configmap %s from namespace %s", name, namespace) + logger.Infof("Attempting to load configmap %s from namespace %s", name, namespace) configMap, err := client.CoreV1().ConfigMaps(namespace).Get( context.Background(), name, metav1.GetOptions{}, ) switch { case err == nil: - log("ConfigMap %s loaded successfully", name) + logger.Infof("ConfigMap %s loaded successfully", name) return configMap, true, nil case errors.IsNotFound(err): - log("ConfigMap %s not found in namespace %s", name, namespace) + logger.Infof("ConfigMap %s not found in namespace %s", name, namespace) return nil, false, nil default: @@ -345,7 +345,7 @@ func updateConfigMapValueK8s(client *kubernetes.Clientset, overwrite bool, content string) error { if configMap.Data == nil { - log("Data of configmap %s is empty, initializing", opts.Name) + logger.Infof("Data of configmap %s is empty, initializing", opts.Name) configMap.Data = make(map[string]string) } @@ -354,7 +354,7 @@ func updateConfigMapValueK8s(client *kubernetes.Clientset, opts.KeyName, opts.Name) } - log("Attempting to update key %s of configmap %s in namespace %s", + logger.Infof("Attempting to update key %s of configmap %s in namespace %s", opts.KeyName, opts.Name, opts.Namespace) configMap.Data[opts.KeyName] = content @@ -370,7 +370,7 @@ func updateConfigMapValueK8s(client *kubernetes.Clientset, TypeMeta: updatedConfigMap.TypeMeta, ObjectMeta: updatedConfigMap.ObjectMeta, }) - log("Updated configmap: %s", jsonConfigMap) + logger.Infof("Updated configmap: %s", jsonConfigMap) return nil } @@ -412,7 +412,7 @@ func createConfigMapK8s(client *kubernetes.Clientset, TypeMeta: updatedConfigMap.TypeMeta, ObjectMeta: updatedConfigMap.ObjectMeta, }) - log("Created configmap: %s", jsonConfigMap) + logger.Infof("Created configmap: %s", jsonConfigMap) return nil } diff --git a/log.go b/log.go index 8409162..fc3e222 100644 --- a/log.go +++ b/log.go @@ -1,19 +1,20 @@ package main import ( - "fmt" "os" - "time" + + "github.com/btcsuite/btclog/v2" ) -type logger func(format string, args ...interface{}) +var ( + // backend is the logging backend used to create all loggers. + backend = btclog.NewDefaultHandler(os.Stderr) -func stderrLogger(format string, args ...interface{}) { - formattedMsg := fmt.Sprintf(format, args...) - now := time.Now().Format("2006-01-02 15:04:05.000") - _, _ = fmt.Fprintf( - os.Stderr, "%s LNDINIT: %s\n", now, formattedMsg, - ) -} + // logger is logger for the main package of the lndinit tool. + logger = btclog.NewSLogger(backend).WithPrefix("LNDINIT") +) -func noopLogger(_ string, _ ...interface{}) {} +// NewSubLogger creates a new sub logger with the given prefix. +func NewSubLogger(prefix string) btclog.Logger { + return logger.SubSystem(prefix) +} diff --git a/main.go b/main.go index 97ca0f1..4ea05b3 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/btcsuite/btclog/v2" "github.com/jessevdk/go-flags" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" @@ -30,13 +31,9 @@ const ( defaultRPCServer = "localhost:" + defaultRPCPort ) -var ( - log logger = noopLogger -) - type globalOptions struct { - ErrorOnExisting bool `long:"error-on-existing" short:"e" description:"Exit with code EXIT_CODE_TARGET_EXISTS (128) instead of 0 if the result of an action is already present"` - Verbose bool `long:"verbose" short:"v" description:"Turn on logging to stderr"` + ErrorOnExisting bool `long:"error-on-existing" short:"e" description:"Exit with code EXIT_CODE_TARGET_EXISTS (128) instead of 0 if the result of an action is already present"` + DebugLevel string `long:"debuglevel" short:"d" description:"Set the log level (Off, Critical, Error, Warn, Info, Debug, Trace)"` } func main() { @@ -46,17 +43,25 @@ func main() { // just the global options, we do a pre-parsing without any commands // registered yet. We ignore any errors as that'll be handled later. _, _ = flags.NewParser(globalOpts, flags.IgnoreUnknown).Parse() - if globalOpts.Verbose { - log = stderrLogger - } - log("Version %s commit=%s, debuglevel=debug", Version(), Commit) + logger.Info("Version %s commit=%s, debuglevel=debug", Version(), Commit) + + if globalOpts.DebugLevel != "" { + level, ok := btclog.LevelFromString(globalOpts.DebugLevel) + if !ok { + logger.Errorf("Invalid debug level %s, "+ + "using info", globalOpts.DebugLevel) + level = btclog.LevelInfo + } + + logger.SetLevel(level) + } parser := flags.NewParser( globalOpts, flags.HelpFlag|flags.PassDoubleDash, ) if err := registerCommands(parser); err != nil { - stderrLogger("Command parser error: %v", err) + logger.Errorf("Command parser error: %v", err) os.Exit(ExitCodeFailure) } @@ -66,7 +71,7 @@ func main() { case isFlagErr: if flagErr.Type != flags.ErrHelp { // Print error if not due to help request. - stderrLogger("Config error: %v", err) + logger.Errorf("Config error: %v", err) os.Exit(ExitCodeFailure) } else { // Help was requested, print without any log @@ -82,21 +87,21 @@ func main() { // logging here. The default is quietly aborting if the // target already exists. if globalOpts.ErrorOnExisting { - log("Failing on state error: %v", err) + logger.Errorf("Failing on state error: %v", err) os.Exit(ExitCodeTargetExists) } - log("Ignoring non-fatal error: %v", err) + logger.Errorf("Ignoring non-fatal error: %v", err) os.Exit(ExitCodeSuccess) // Ugh, can't use errors.Is() here because the flag parser does // not wrap the returned errors properly. case strings.Contains(err.Error(), errInputMissing): - stderrLogger("Input error: %v", err) + logger.Errorf("Input error: %v", err) os.Exit(ExitCodeInputMissing) default: - stderrLogger("Runtime error: %v", err) + logger.Errorf("Runtime error: %v", err) os.Exit(ExitCodeFailure) } } @@ -112,8 +117,8 @@ func registerCommands(parser *flags.Parser) error { commands := []subCommand{ newGenPasswordCommand(), newGenSeedCommand(), - newLoadSecretCommand(), newInitWalletCommand(), + newLoadSecretCommand(), newStoreSecretCommand(), newStoreConfigmapCommand(), newWaitReadyCommand(), From a76d92ebcfc295e10bb675869d2990037a17816e Mon Sep 17 00:00:00 2001 From: ziggie Date: Mon, 7 Apr 2025 16:26:18 +0200 Subject: [PATCH 2/6] migration: add migration functionality Support the migration of bbolt db database to SQL databases (SQLITE/POSTGRES) --- Makefile | 12 +- README.md | 6 + cmd_migrate_db.go | 975 +++++++++++++++++++++++++++++ go.mod | 48 +- go.sum | 346 ++--------- main.go | 4 +- migratekvdb/bucket_path.go | 104 ++++ migratekvdb/errors.go | 27 + migratekvdb/helper.go | 28 + migratekvdb/migration.go | 1076 +++++++++++++++++++++++++++++++++ migratekvdb/migration_test.go | 192 ++++++ migratekvdb/state.go | 207 +++++++ 12 files changed, 2701 insertions(+), 324 deletions(-) create mode 100644 cmd_migrate_db.go create mode 100644 migratekvdb/bucket_path.go create mode 100644 migratekvdb/errors.go create mode 100644 migratekvdb/helper.go create mode 100644 migratekvdb/migration.go create mode 100644 migratekvdb/migration_test.go create mode 100644 migratekvdb/state.go diff --git a/Makefile b/Makefile index 0191007..1173189 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,11 @@ XARGS := xargs -L 1 VERSION_TAG = $(shell git describe --tags) +DEV_TAGS = kvdb_etcd kvdb_postgres kvdb_sqlite +RELEASE_TAGS = $(DEV_TAGS) + BUILD_SYSTEM = darwin-amd64 \ +darwin-arm64 \ linux-386 \ linux-amd64 \ linux-armv6 \ @@ -49,7 +53,7 @@ endif make_ldflags = $(2) -X main.Commit=$(COMMIT) DEV_GCFLAGS := -gcflags "all=-N -l" -LDFLAGS := -ldflags "$(call make_ldflags, ${tags}, -s -w)" +LDFLAGS := -ldflags "$(call make_ldflags, $(DEV_TAGS), -s -w)" DEV_LDFLAGS := -ldflags "$(call make_ldflags, $(DEV_TAGS))" # For the release, we want to remove the symbol table and debug information (-s) @@ -83,7 +87,7 @@ build: install: @$(call print, "Installing lndinit.") - $(GOINSTALL) -tags="${tags}" $(LDFLAGS) $(PKG) + $(GOINSTALL) -tags="$(DEV_TAGS)" $(LDFLAGS) $(PKG) release-install: @$(call print, "Installing release lndinit.") @@ -105,7 +109,7 @@ scratch: build unit: @$(call print, "Running unit tests.") - $(GOTEST) ./... + $(GOTEST) -tags="$(DEV_TAGS)" ./... fmt: $(GOIMPORTS_BIN) @$(call print, "Fixing imports.") @@ -115,7 +119,7 @@ fmt: $(GOIMPORTS_BIN) lint: docker-tools @$(call print, "Linting source.") - $(DOCKER_TOOLS) golangci-lint run -v $(LINT_WORKERS) + $(DOCKER_TOOLS) golangci-lint run -v --build-tags="$(DEV_TAGS)"$(LINT_WORKERS) vendor: @$(call print, "Re-creating vendor directory.") diff --git a/README.md b/README.md index bfaec4f..4cdc2b4 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ initialization, including seed and password generation. - [`store-configmap`](#store-configmap) - [`init-wallet`](#init-wallet) - [`wait-ready`](#wait-ready) + - [`migrate-db`](#migrate-db) - [Example usage](#example-usage) - [Basic setup](#example-use-case-1-basic-setup) - [Kubernetes](#example-use-case-2-kubernetes) @@ -64,6 +65,11 @@ No `lnd` needed, but seed will be in `lnd`-specific [`aezeed` format](https://gi `wait-ready` waits for `lnd` to be ready by connecting to `lnd`'s status RPC - Needs `lnd` to run, eventually +### migrate-db +`migrate-db` migrates the content of one `lnd` database to another, for example +from `bbolt` to Postgres. See [data migration guide](docs/data-migration.md) for +more information. + --- ## Example Usage diff --git a/cmd_migrate_db.go b/cmd_migrate_db.go new file mode 100644 index 0000000..66a74fe --- /dev/null +++ b/cmd_migrate_db.go @@ -0,0 +1,975 @@ +package main + +import ( + "context" + "errors" + "fmt" + "net/http" + _ "net/http/pprof" // Register pprof handlers + "os" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btclog/v2" + "github.com/jessevdk/go-flags" + "github.com/lightninglabs/lndinit/migratekvdb" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/kvdb/postgres" + "github.com/lightningnetwork/lnd/kvdb/sqlbase" + "github.com/lightningnetwork/lnd/kvdb/sqlite" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/signal" + "github.com/lightningnetwork/lnd/watchtower/wtdb" + "go.etcd.io/bbolt" +) + +var ( + // alreadyMigratedKey is the key under which we add a tag in the target/ + // destination DB after we've successfully and completely migrated it + // from a source DB. + alreadyMigratedKey = []byte("data-migration-already-migrated") + + // defaultDataDir is the default data directory for lnd. + defaultDataDir = filepath.Join(btcutil.AppDataDir("lnd", false), "data") +) + +const ( + // walletMetaBucket is the name of the meta bucket in the wallet db + // for the wallet ready marker. + walletMetaBucket = "lnwallet" + + // walletReadyKey is the key in the wallet meta bucket for the wallet + // ready marker. + walletReadyKey = "ready" +) + +// Bolt is the configuration for a bolt database. +type Bolt struct { + DBTimeout time.Duration `long:"dbtimeout" description:"Specify the timeout value used when opening the database."` + DataDir string `long:"data-dir" description:"Lnd data dir where bolt dbs are located."` + TowerDir string `long:"tower-dir" description:"Lnd watchtower dir where bolt dbs for the watchtower server are located."` +} + +// Sqlite is the configuration for a sqlite database. +type Sqlite struct { + DataDir string `long:"data-dir" description:"Lnd data dir where sqlite dbs are located."` + TowerDir string `long:"tower-dir" description:"Lnd watchtower dir where sqlite dbs for the watchtower server are located."` + Config *sqlite.Config `group:"sqlite-config" namespace:"sqlite-config" description:"Sqlite config."` +} + +// SourceDB represents the source database, which can only be bolt for now. +type SourceDB struct { + Backend string `long:"backend" description:"The source database backend." choice:"bolt"` + Bolt *Bolt `group:"bolt" namespace:"bolt" description:"Bolt settings."` +} + +// DestDB represents the destination database, which can be either postgres or +// sqlite. +type DestDB struct { + Backend string `long:"backend" description:"The destination database backend." choice:"postgres" choice:"sqlite"` + Postgres *postgres.Config `group:"postgres" namespace:"postgres" description:"Postgres settings."` + Sqlite *Sqlite `group:"sqlite" namespace:"sqlite" description:"Sqlite settings."` +} + +// Init should be called upon start to pre-initialize database for sql +// backends. If max connections are not set, the amount of connections will be +// unlimited however we only use one connection during the migration. +func (db *DestDB) Init() error { + switch { + case db.Backend == lncfg.PostgresBackend: + sqlbase.Init(db.Postgres.MaxConnections) + + case db.Backend == lncfg.SqliteBackend: + sqlbase.Init(db.Sqlite.Config.MaxConnections) + } + + return nil +} + +type migrateDBCommand struct { + Source *SourceDB `group:"source" namespace:"source" long:"" short:"" description:""` + Dest *DestDB `group:"dest" namespace:"dest" long:"" short:"" description:""` + Network string `long:"network" short:"n" description:"Network of the db files to migrate (used to navigate into the right directory)"` + PprofPort int `long:"pprof-port" description:"Enable pprof profiling on the specified port"` + ForceNewMigration bool `long:"force-new-migration" description:"Force a new migration from the beginning of the source DB so the resume state will be discarded"` + ForceVerifyDB bool `long:"force-verify-db" description:"Force a verification verifies two already marked (tombstoned and already migrated) dbs to make sure that the source db equals the content of the destination db"` + ChunkSize uint64 `long:"chunk-size" description:"Chunk size for the migration in bytes"` +} + +func newMigrateDBCommand() *migrateDBCommand { + return &migrateDBCommand{ + Source: &SourceDB{ + Backend: lncfg.BoltBackend, + Bolt: &Bolt{ + DBTimeout: kvdb.DefaultDBTimeout, + TowerDir: defaultDataDir, + DataDir: defaultDataDir, + }, + }, + Dest: &DestDB{ + Backend: lncfg.PostgresBackend, + Postgres: &postgres.Config{}, + Sqlite: &Sqlite{ + Config: &sqlite.Config{}, + TowerDir: defaultDataDir, + DataDir: defaultDataDir, + }, + }, + Network: "mainnet", + } +} + +func (x *migrateDBCommand) Register(parser *flags.Parser) error { + _, err := parser.AddCommand( + "migrate-db", + "Migrate the complete database state of lnd to a new backend", + ` + Migrate the full database state of lnd from a source (for example the + set of bolt database files such as channel.db and wallet.db) database + to a SQL destination database. + + IMPORTANT: Please read the data migration guide located in the file + docs/data-migration.md of the main lnd repository before using this + command! + + NOTE: The migration can take a long time depending on the amount of data + that needs to be written! The migration happens in chunks therefore it + can be resumed in case of an interruption. The migration also includes + a verification to assure that the migration is consistent. + As long as NEITHER the source nor destination database has been started/ + run with lnd, the migration can be repeated/resumed in case of an error + since the data will just be overwritten again in the destination. + + Once a database was successfully and completely migrated from the source + to the destination, the source will be marked with a 'tombstone' tag + while the destination will get an 'already migrated' tag. + A database with a tombstone cannot be started with lnd anymore to + prevent from an old state being used by accident. + To prevent overwriting a destination database by accident, the same + database/namespace pair cannot be used as the target of a data migration + twice, which is checked through the 'already migrated' tag.`, + x, + ) + return err +} + +// optionalDBs are the databases that can be skipped if they don't +// exist. +var ( + optionalDBs = map[string]bool{ + lncfg.NSTowerClientDB: true, + lncfg.NSTowerServerDB: true, + lncfg.NSNeutrinoDB: true, + } + + // allDBPrefixes defines all databases that should be migrated. + allDBPrefixes = []string{ + lncfg.NSChannelDB, + lncfg.NSMacaroonDB, + lncfg.NSDecayedLogDB, + lncfg.NSTowerClientDB, + lncfg.NSTowerServerDB, + lncfg.NSWalletDB, + lncfg.NSNeutrinoDB, + } +) + +func (x *migrateDBCommand) Execute(_ []string) error { + // We currently only allow migrations from bolt to sqlite/postgres. + if err := x.validateDBBackends(); err != nil { + return fmt.Errorf("invalid database configuration: %w", err) + } + + // We keep track of the DBs that we have migrated. + migratedDBs := []string{} + + // Add pprof server if enabled. + if x.PprofPort > 0 { + go func() { + pprofAddr := fmt.Sprintf("localhost:%d", x.PprofPort) + logger.Infof("Starting pprof server on %s", pprofAddr) + err := http.ListenAndServe(pprofAddr, nil) + if err != nil { + logger.Errorf("Error starting pprof "+ + "server: %v", err) + } + }() + } + + // get the context for the migration which is tied to the signal + // interceptor. + ctx := getContext() + + for _, prefix := range allDBPrefixes { + logger.Infof("Attempting to migrate DB with prefix `%s`", prefix) + + // Create a separate meta db for each db to store the + // migration/verification state. This db will be deleted + // after the migration is successful. + metaDBPath := filepath.Join( + x.Source.Bolt.DataDir, prefix+"-migration-meta.db", + ) + + srcDb, err := openSourceDb( + x.Source, prefix, x.Network, true, + ) + if err == kvdb.ErrDbDoesNotExist { + // Only skip if it's an optional because it's not + // required to run a wtclient or wtserver for example. + if optionalDBs[prefix] { + logger.Warnf("Skipping optional DB %s: not "+ + "found", prefix) + continue + } + } + if err != nil { + return fmt.Errorf("failed to open source db with "+ + "prefix `%s`: %w", prefix, err) + } + defer srcDb.Close() + logger.Infof("Opened source DB with prefix `%s` successfully", + prefix) + + // We open the destination DB as well to make sure both that + // both DBs are either marked or not. + destDb, err := openDestDb(ctx, x.Dest, prefix, x.Network) + if err != nil { + return fmt.Errorf("failed to open destination "+ + "db with prefix `%s`: %w", prefix, err) + } + defer destDb.Close() + + logger.Infof("Opened destination DB with prefix `%s` "+ + "successfully", prefix) + + // Check that the source database and the destination database + // are either both marked with a tombstone or a migrated marker. + logger.Infof("Checking tombstone marker on source DB and "+ + "migrated marker on destination DB with prefix `%s`", + prefix) + + sourceMarker, err := checkMarkerPresent( + srcDb, channeldb.TombstoneKey, + ) + sourceDbTombstone := err == nil + if err != nil && !errors.Is(err, channeldb.ErrMarkerNotPresent) { + return err + } + + // Also make sure that the destination DB hasn't been marked as + // successfully having been the target of a migration. We only + // mark a destination DB as successfully migrated at the end of + // a successful and complete migration. + destMarker, err := checkMarkerPresent( + destDb, alreadyMigratedKey, + ) + destDbMigrated := err == nil + if err != nil && !errors.Is(err, channeldb.ErrMarkerNotPresent) { + return err + } + switch { + case sourceDbTombstone && destDbMigrated: + if x.ForceVerifyDB { + // Make sure the meta db is not deleted so we + // can get the migration stats. + // We delete the verification complete marker + // only when this marker is set we clear the + // verification state. then we should be good + // to go. + if !lnrpc.FileExists(metaDBPath) { + return fmt.Errorf("cannot verify migration "+ + "for db with prefix `%s` because the "+ + "migration meta db does not exist", + prefix) + } + + // Open the db where we store the migration/verification state. + metaDB, err := bbolt.Open(metaDBPath, 0600, nil) + if err != nil { + logger.Errorf("Error opening db: %v", err) + } + defer metaDB.Close() + + logger.Infof("Opened meta db at path: %s", metaDBPath) + + // Verify the migration. + migrator, err := migratekvdb.New(migratekvdb.Config{ + Logger: logger.SubSystem("MIGKV-" + prefix), + ChunkSize: x.ChunkSize, + MetaDB: metaDB, + DBPrefixName: prefix, + }) + if err != nil { + return err + } + + err = migrator.VerifyMigration(ctx, srcDb, destDb, true) + if err != nil { + return err + } + + logger.Infof("Verification of migration of db with prefix "+ + "`%s` completed", prefix) + + } + logger.Infof("Skipping DB with prefix `%s` because the "+ + "source DB is marked with a tombstone and the "+ + "destination DB is marked as already migrated. "+ + "Tag reads: source: `%s`, destination: `%s`", + prefix, sourceMarker, destMarker) + + migratedDBs = append(migratedDBs, prefix) + + continue + + case sourceDbTombstone && !destDbMigrated: + return fmt.Errorf("DB with prefix `%s` source DB is "+ + "marked with a tombstone but the "+ + "destination DB is not marked as already "+ + "migrated. This is not allowed. Tag reads: "+ + "source: `%s`, destination: `%s`", + prefix, sourceMarker, destMarker) + + case !sourceDbTombstone && destDbMigrated: + return fmt.Errorf("DB with prefix `%s` source DB is "+ + "not marked with a tombstone but the "+ + "destination DB is marked as already migrated. "+ + "This is not allowed. Tag reads: source: `%s`, "+ + "destination: `%s`", + prefix, sourceMarker, destMarker) + } + + // Check that the source DB has had all its schema migrations + // applied before we migrate any of its data. Currently only + // migration of the channel.db and the watchtower.db exist. + // Check channel.db migrations. + if prefix == lncfg.NSChannelDB { + logger.Info("Checking DB version of source DB " + + "(channel.db)") + + err := checkChannelDBMigrationsApplied(srcDb) + if err != nil { + return err + } + } + + // Check watchtower client DB migrations. + if prefix == lncfg.NSTowerClientDB { + logger.Info("Checking DB version of source DB " + + "(wtclient.db)") + + err := checkWTClientDBMigrationsApplied(srcDb) + if err != nil { + return err + } + } + + // In case we want to start a new migration we delete the + // migration meta db if it exits. This can only be done if + // the db is not already successfully migrated otherwise + // previous marker checks will prevent us to reach this point. + if x.ForceNewMigration { + // Before proceeding with the migration we check that + // the destination db is empty. + var topLevelBuckets [][]byte + err := kvdb.View(destDb, func(tx kvdb.RTx) error { + return tx.ForEachBucket(func(bucket []byte) error { + bucketCopy := make([]byte, len(bucket)) + copy(bucketCopy, bucket) + topLevelBuckets = append( + topLevelBuckets, bucketCopy, + ) + + return nil + }) + }, func() {}) + if err != nil { + return err + } + + if len(topLevelBuckets) > 0 { + logger.Infof("Cannot force new migration "+ + "of db with prefix `%s` because the "+ + "destination db has data - delete "+ + "it manually first", prefix) + + return fmt.Errorf("destination db with prefix `%s` "+ + "has data, refusing to overwrite it", prefix) + } + + logger.Info("Forcing new migration, deleting " + + "migration meta db and all previous data from" + + " the destination db") + + if lnrpc.FileExists(metaDBPath) { + err := os.Remove(metaDBPath) + if err != nil { + return fmt.Errorf("failed to delete "+ + "migration meta db: %v", err) + } + + logger.Infof("Deleted migration meta db at "+ + "path: %s", metaDBPath) + } + } + + // Open the db where we store the migration/verification state. + metaDB, err := bbolt.Open(metaDBPath, 0600, nil) + if err != nil { + logger.Errorf("Error opening db: %v", err) + } + defer metaDB.Close() + + logger.Infof("Opened meta db at path: %s", metaDBPath) + + // Configure and run migration. + cfg := migratekvdb.Config{ + Logger: logger.SubSystem("MIGKV-" + prefix), + ChunkSize: x.ChunkSize, + MetaDB: metaDB, + DBPrefixName: prefix, + } + + migrator, err := migratekvdb.New(cfg) + if err != nil { + return err + } + + err = migrator.Migrate(ctx, srcDb, destDb) + if err != nil { + return err + } + logger.Infof("Migration of db with prefix %s completed", prefix) + + // We migrated the DB successfully, now we verify the migration. + err = migrator.VerifyMigration( + ctx, srcDb, destDb, false, + ) + if err != nil { + return err + } + + logger.Infof("Verification of migration of db with prefix "+ + "`%s` completed", prefix) + + // Migrate wallet created marker. This is done after the + // migration to ensure the verification of the migration + // succeeds. + // + // NOTE: We always need to add the wallet marker if the db is + // not a `bolt` db, which is already resticted by the + // destination db config. + if prefix == lncfg.NSWalletDB { + err := createWalletMarker(destDb, logger) + if err != nil { + return err + } + } + + // If we get here, we've successfully migrated the DB and can + // now set the tombstone marker on the source database and the + // already migrated marker on the target database. + // We need to reopen the db in write mode. + srcDb.Close() + logger.Infof("We are now opening the source db with prefix `%s` "+ + "in write mode to set the tombstone marker. This "+ + "might take a while (~10 minutes for large databases) "+ + "to sync the freelist..., so please be patient it is the "+ + "final step for this db.", prefix) + + srcDb, err = openSourceDb(x.Source, prefix, x.Network, false) + if err != nil { + return err + } + + if err := addMarker(srcDb, channeldb.TombstoneKey); err != nil { + return err + } + + // Add already migrated marker to the destination DB. + if err := addMarker(destDb, alreadyMigratedKey); err != nil { + return err + } + + logger.Infof("Migration of DB with prefix `%s` completed "+ + "successfully", prefix) + + migratedDBs = append(migratedDBs, prefix) + + // Removing meta db. + err = metaDB.Close() + if err != nil { + logger.Errorf("Error closing meta db: %v", err) + } + + // Close the db connection to cleanup the state. + err = srcDb.Close() + if err != nil { + logger.Errorf("Error closing source db: %v", err) + } + err = destDb.Close() + if err != nil { + logger.Errorf("Error closing destination db: %v", err) + } + + // Create migration completed file, this will only create the + // file for bolt databases. + if err := createMigrationCompletedFile(x.Source, prefix, + x.Network, x.Dest.Backend); err != nil { + return err + } + } + + logger.Info("!!!Migration of all mandatory db parts completed " + + "successfully!!!") + + logger.Infof("Migrated DBs: %v", migratedDBs) + + return nil +} + +// validateDBBackends ensures that only migrations from bolt to sqlite/postgres +// are allowed. +func (x *migrateDBCommand) validateDBBackends() error { + // Source must be bolt + if x.Source.Backend != lncfg.BoltBackend { + return fmt.Errorf("source database must be bolt, got: %s", + x.Source.Backend) + } + + // Destination must be sqlite or postgres. + switch x.Dest.Backend { + case lncfg.SqliteBackend, lncfg.PostgresBackend: + return nil + default: + return fmt.Errorf("destination database must be sqlite or "+ + "postgres, got: %s", x.Dest.Backend) + } +} + +// openSourceDb opens the source database and also checks if there is enough +// free space on the source directory to hold a copy of the database. +func openSourceDb(cfg *SourceDB, prefix, network string, + readonly bool) (kvdb.Backend, error) { + + path := getBoltDBPath(cfg, prefix, network) + if path == "" { + return nil, fmt.Errorf("unknown prefix: %s", prefix) + } + + const ( + noFreelistSync = true + timeout = time.Minute + ) + + args := []interface{}{ + path, noFreelistSync, timeout, readonly, + } + backend := kvdb.BoltBackendName + logger.Infof("Opening bolt backend at %s for prefix '%s'", + path, prefix) + + db, err := kvdb.Open(backend, args...) + if err != nil { + return nil, err + } + + // Get the size of the database. + fi, err := os.Stat(path) + if err != nil { + return nil, fmt.Errorf("error determining source database "+ + "with prefix %s: %v", prefix, err) + } + dbSize := fi.Size() + + // Because the destination can also just be a postgres dsn, we just + // check if the source dir has enough free space to hold a copy of the + // db. + freeSpace, err := availableDiskSpace(cfg.Bolt.DataDir) + if err != nil { + return nil, fmt.Errorf("error determining source directory "+ + "free space: %v", err) + } + + if freeSpace < uint64(dbSize) { + return nil, fmt.Errorf("not enough free space on source "+ + "directory to migrate db: %d bytes required, "+ + "%d bytes available", dbSize, freeSpace) + } + + logger.Debugf("Source DB size: %d bytes", dbSize) + + return db, nil +} + +// openDestDb opens the different types of databases. +func openDestDb(ctx context.Context, cfg *DestDB, prefix, + network string) (kvdb.Backend, error) { + + backend := cfg.Backend + + // Init the db connections for sql backends. + err := cfg.Init() + if err != nil { + return nil, err + } + + // Settings to open a particular db backend. + var args []interface{} + + switch backend { + case kvdb.PostgresBackendName: + args = []interface{}{ + ctx, + &postgres.Config{ + Dsn: cfg.Postgres.Dsn, + Timeout: time.Minute, + MaxConnections: 10, + }, + prefix, + } + + logger.Infof("Opening postgres backend at `%s` with prefix `%s`", + cfg.Postgres.Dsn, prefix) + + case kvdb.SqliteBackendName: + // Directories where the db files are located. + graphDir := lncfg.CleanAndExpandPath( + filepath.Join(cfg.Sqlite.DataDir, "graph", network), + ) + walletDir := lncfg.CleanAndExpandPath( + filepath.Join( + cfg.Sqlite.DataDir, "chain", "bitcoin", network, + ), + ) + + // In case the data directory was set but the watchtower is + // still the default one, we use the data directory for the + // watchtower as well. + towerServerDir := lncfg.CleanAndExpandPath( + filepath.Join( + cfg.Sqlite.TowerDir, "watchtower", "bitcoin", + network, + ), + ) + if cfg.Sqlite.DataDir != defaultDataDir && + cfg.Sqlite.TowerDir == defaultDataDir { + + towerServerDir = lncfg.CleanAndExpandPath( + filepath.Join( + cfg.Sqlite.DataDir, "watchtower", + "bitcoin", network, + ), + ) + } + + var dbName string + var path string + switch prefix { + case lncfg.NSChannelDB: + path = graphDir + dbName = lncfg.SqliteChannelDBName + + case lncfg.NSWalletDB: + path = walletDir + dbName = lncfg.SqliteChainDBName + + case lncfg.NSMacaroonDB: + path = walletDir + dbName = lncfg.SqliteChainDBName + + case lncfg.NSDecayedLogDB: + path = graphDir + dbName = lncfg.SqliteChannelDBName + + case lncfg.NSTowerClientDB: + path = graphDir + dbName = lncfg.SqliteChannelDBName + + case lncfg.NSTowerServerDB: + path = towerServerDir + dbName = lncfg.SqliteChannelDBName + + case lncfg.NSNeutrinoDB: + path = walletDir + dbName = lncfg.SqliteNeutrinoDBName + } + + // We check if the path exists to avoid receiving sqlite + // misleading errors. Because sqlite will report out of + // memory issues if the path does not exist. + if err := checkPathExists(path); err != nil { + return nil, fmt.Errorf("destination directory (%s) "+ + "not found: %v", path, err) + } + + args = []interface{}{ + ctx, + &sqlite.Config{ + Timeout: time.Minute, + }, + path, + dbName, + prefix, + } + + logger.Infof("Opening sqlite backend at %s "+ + "for prefix '%s'", filepath.Join(path, dbName), + prefix) + + default: + return nil, fmt.Errorf("unknown backend: %v", backend) + } + + return kvdb.Open(backend, args...) +} + +// checkMarkerPresent checks if a marker is present in the database. +func checkMarkerPresent(db kvdb.Backend, markerKey []byte) ([]byte, error) { + var ( + markerValue []byte + err error + ) + err = kvdb.View(db, func(tx kvdb.RTx) error { + markerValue, err = channeldb.CheckMarkerPresent(tx, markerKey) + return err + }, func() {}) + if err != nil { + return nil, err + } + + return markerValue, nil +} + +// addMarker adds a marker to the database. +func addMarker(db kvdb.Backend, markerKey []byte) error { + err := kvdb.Update(db, func(tx kvdb.RwTx) error { + markerValue := []byte( + fmt.Sprintf("lndinit migrate-db %s", time.Now(). + Format(time.RFC3339)), + ) + + return channeldb.AddMarker(tx, markerKey, markerValue) + }, func() {}) + if err != nil { + return err + } + + return nil +} + +// createWalletMarker creates a marker in the wallet database to indicate it's +// ready for use. This is only needed for non-bolt databases. +func createWalletMarker(db kvdb.Backend, logger btclog.Logger) error { + logger.Info("Creating 'wallet created' marker") + + err := kvdb.Update(db, func(tx kvdb.RwTx) error { + metaBucket, err := tx.CreateTopLevelBucket( + []byte(walletMetaBucket), + ) + if err != nil { + return fmt.Errorf("failed to create meta "+ + "bucket: %w", err) + } + + return metaBucket.Put( + []byte(walletReadyKey), []byte(walletReadyKey), + ) + }, func() {}) + if err != nil { + return fmt.Errorf("failed to create wallet marker: %w", err) + } + + logger.Info("Successfully created 'wallet created' marker") + + return nil +} + +// checkChannelDBMigrationsApplied checks if the channel DB migrations are +// applied. +func checkChannelDBMigrationsApplied(db kvdb.Backend) error { + var meta channeldb.Meta + err := kvdb.View(db, func(tx kvdb.RTx) error { + return channeldb.FetchMeta(&meta, tx) + }, func() { + meta = channeldb.Meta{} + }) + if err != nil { + return err + } + + if meta.DbVersionNumber != channeldb.LatestDBVersion() { + return fmt.Errorf("refusing to migrate source database with "+ + "version %d while latest known DB version is %d; "+ + "please upgrade the DB before using the data "+ + "migration tool", meta.DbVersionNumber, + channeldb.LatestDBVersion()) + } + + return nil +} + +// checkWTClientDBMigrationsApplied checks if the watchtower client DB +// migrations are applied. +func checkWTClientDBMigrationsApplied(db kvdb.Backend) error { + version, err := wtdb.CurrentDatabaseVersion(db) + if err != nil { + return err + } + + if version != wtdb.LatestDBMigrationVersion() { + return fmt.Errorf("refusing to migrate source database with "+ + "version %d while latest known DB version is %d; "+ + "please upgrade the DB before using the data "+ + "migration tool", version, wtdb.LatestDBMigrationVersion()) + } + + return nil +} + +// getBoltDBPath returns the full path for a given database type and prefix. +func getBoltDBPath(cfg *SourceDB, prefix, network string) string { + // Directories where the db files are located. + graphDir := lncfg.CleanAndExpandPath( + filepath.Join(cfg.Bolt.DataDir, "graph", network), + ) + walletDir := lncfg.CleanAndExpandPath( + filepath.Join( + cfg.Bolt.DataDir, "chain", "bitcoin", network, + ), + ) + + towerServerDir := lncfg.CleanAndExpandPath( + filepath.Join( + cfg.Bolt.TowerDir, "watchtower", "bitcoin", + network, + ), + ) + if cfg.Bolt.DataDir != defaultDataDir && + cfg.Bolt.TowerDir == defaultDataDir { + + towerServerDir = lncfg.CleanAndExpandPath( + filepath.Join( + cfg.Bolt.DataDir, "watchtower", + "bitcoin", network, + ), + ) + } + + switch prefix { + case lncfg.NSChannelDB: + return filepath.Join(graphDir, lncfg.ChannelDBName) + + case lncfg.NSWalletDB: + return filepath.Join(walletDir, lncfg.WalletDBName) + + case lncfg.NSMacaroonDB: + return filepath.Join(walletDir, lncfg.MacaroonDBName) + + case lncfg.NSDecayedLogDB: + return filepath.Join(graphDir, lncfg.DecayedLogDbName) + + case lncfg.NSTowerClientDB: + return filepath.Join(graphDir, lncfg.TowerClientDBName) + + case lncfg.NSTowerServerDB: + return filepath.Join(towerServerDir, lncfg.TowerServerDBName) + + case lncfg.NSNeutrinoDB: + // TODO(ziggie): Can be updated as soon as new LND vesion is + // available. + return filepath.Join(walletDir, "neutrino.db") + } + + return "" +} + +// createMigrationCompletedFile creates an empty file indicating that a bolt +// database was successfully migrated to a different backend. This is only +// created when migrating FROM a bolt database TO another backend type. +func createMigrationCompletedFile(sourceDB *SourceDB, prefix, + network, targetType string) error { + + // Only create completion file when migrating FROM bolt. + if sourceDB.Backend != lncfg.BoltBackend { + return nil + } + + dbPath := getBoltDBPath(sourceDB, prefix, network) + dir := filepath.Dir(dbPath) + dbName := filepath.Base(dbPath) + + timestamp := time.Now().Format("2006-01-02-15-04") + markerName := fmt.Sprintf( + "%s.migrated-to-%s-%s", dbName, targetType, timestamp, + ) + markerPath := filepath.Join(dir, markerName) + + f, err := os.Create(markerPath) + if err != nil { + return fmt.Errorf("failed to create migration completed "+ + "file at %s: %w", markerPath, err) + } + defer f.Close() + + logger.Infof("Created migration completed file at %s", markerPath) + + return nil +} + +// availableDiskSpace returns the available disk space in bytes of the given +// file system. +func availableDiskSpace(path string) (uint64, error) { + s := syscall.Statfs_t{} + err := syscall.Statfs(path, &s) + if err != nil { + return 0, err + } + + // Some OSes have s.Bavail defined as int64, others as uint64, so we + // need the explicit type conversion here. + return uint64(s.Bavail) * uint64(s.Bsize), nil // nolint:unconvert +} + +// checkPathExists verifies that the directory exists. +func checkPathExists(path string) error { + dir := filepath.Dir(path) + + if _, err := os.Stat(dir); os.IsNotExist(err) { + return fmt.Errorf("directory %s does not exist, "+ + "please create it first", dir) + } else if err != nil { + return fmt.Errorf("failed to check directory %s: %v", dir, err) + } + + return nil +} + +func getContext() context.Context { + // Hook interceptor for os signals. We need to except the case where + // we call the function multiple times. + shutdownInterceptor, err := signal.Intercept() + shutdownInterceptor.ShutdownChannel() + + // TODO(ziggie): This is a hack to avoid an error when running the + // migration tests for sqlite and postgres, because both are using the + // same main routine. + if err != nil && !strings.Contains(err.Error(), "intercept "+ + "already started") { + + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + ctxc, cancel := context.WithCancel(context.Background()) + go func() { + <-shutdownInterceptor.ShutdownChannel() + cancel() + }() + return ctxc +} diff --git a/go.mod b/go.mod index 68f4517..391a9b2 100644 --- a/go.mod +++ b/go.mod @@ -2,14 +2,19 @@ module github.com/lightninglabs/lndinit require ( github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 - github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c + github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 github.com/btcsuite/btcwallet v0.16.12 + github.com/btcsuite/btcwallet/walletdb v1.5.1 // indirect + github.com/fergusstrange/embedded-postgres v1.25.0 github.com/jessevdk/go-flags v1.4.0 github.com/kkdai/bstream v1.0.0 github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display + // TODO(ziggie): Use the tagged LND 19 version here. github.com/lightningnetwork/lnd v0.19.0-beta.rc1 + github.com/lightningnetwork/lnd/kvdb v1.4.15 github.com/stretchr/testify v1.9.0 + go.etcd.io/bbolt v1.3.11 google.golang.org/grpc v1.59.0 k8s.io/api v0.18.3 k8s.io/apimachinery v0.18.3 @@ -25,18 +30,17 @@ require ( github.com/aead/siphash v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c // indirect github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 // indirect github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 // indirect - github.com/btcsuite/btcwallet/walletdb v1.4.5-0.20250311184728-a7bb395324e8 // indirect github.com/btcsuite/btcwallet/wtxmgr v1.5.4 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/winsvc v1.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect @@ -51,8 +55,9 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fergusstrange/embedded-postgres v1.25.0 // indirect github.com/go-errors/errors v1.0.1 // indirect + github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect @@ -68,7 +73,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect @@ -103,7 +108,6 @@ require ( github.com/lightningnetwork/lnd/clock v1.1.1 // indirect github.com/lightningnetwork/lnd/fn/v2 v2.0.8 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.6 // indirect - github.com/lightningnetwork/lnd/kvdb v1.4.12 // indirect github.com/lightningnetwork/lnd/queue v1.1.1 // indirect github.com/lightningnetwork/lnd/sqldb v1.0.7 // indirect github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect @@ -142,21 +146,21 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect - go.etcd.io/bbolt v1.3.11 // indirect - go.etcd.io/etcd/api/v3 v3.5.7 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect - go.etcd.io/etcd/client/v2 v2.305.7 // indirect - go.etcd.io/etcd/client/v3 v3.5.7 // indirect - go.etcd.io/etcd/pkg/v3 v3.5.7 // indirect - go.etcd.io/etcd/raft/v3 v3.5.7 // indirect - go.etcd.io/etcd/server/v3 v3.5.7 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0 // indirect - go.opentelemetry.io/otel v1.0.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1 // indirect - go.opentelemetry.io/otel/sdk v1.0.1 // indirect - go.opentelemetry.io/otel/trace v1.0.1 // indirect - go.opentelemetry.io/proto/otlp v0.9.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.12 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect + go.etcd.io/etcd/client/v2 v2.305.12 // indirect + go.etcd.io/etcd/client/v3 v3.5.12 // indirect + go.etcd.io/etcd/pkg/v3 v3.5.12 // indirect + go.etcd.io/etcd/raft/v3 v3.5.12 // indirect + go.etcd.io/etcd/server/v3 v3.5.12 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect + go.opentelemetry.io/otel v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect + go.opentelemetry.io/otel/metric v1.20.0 // indirect + go.opentelemetry.io/otel/sdk v1.20.0 // indirect + go.opentelemetry.io/otel/trace v1.20.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect diff --git a/go.sum b/go.sum index b4d443e..73d326e 100644 --- a/go.sum +++ b/go.sum @@ -1,41 +1,11 @@ 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= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= @@ -47,7 +17,6 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= @@ -55,7 +24,6 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= @@ -107,8 +75,8 @@ github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsm github.com/btcsuite/btcwallet/wallet/txrules v1.2.2/go.mod h1:4v+grppsDpVn91SJv+mZT7B8hEV4nSmpREM4I8Uohws= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 h1:93o5Xz9dYepBP4RMFUc9RGIFXwqP2volSWRkYJFrNtI= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw= -github.com/btcsuite/btcwallet/walletdb v1.4.5-0.20250311184728-a7bb395324e8 h1:itzwCi4TYQFyyCu7qYLXxAZ0APYo1cFiEJuPX1Hv8Z4= -github.com/btcsuite/btcwallet/walletdb v1.4.5-0.20250311184728-a7bb395324e8/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= +github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI= +github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= github.com/btcsuite/btcwallet/wtxmgr v1.5.4 h1:hJjHy1h/dJwSfD9uDsCwcH21D1iOrus6OrI5gR9E/O0= github.com/btcsuite/btcwallet/wtxmgr v1.5.4/go.mod h1:lAv0b1Vj9Ig5U8QFm0yiJ9WqPl8yGO/6l7JxdHY1PKE= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= @@ -122,34 +90,20 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3 github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5 h1:xD/lrqdvwsc+O2bjSSi3YqY73Ke3LAiSCx49aCesA0E= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcKDqs= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= @@ -196,10 +150,6 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= @@ -212,15 +162,10 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -228,6 +173,11 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/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-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= @@ -248,33 +198,19 @@ github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.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/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -288,26 +224,15 @@ github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzr 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= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -318,7 +243,6 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ 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/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -332,8 +256,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 h1:ajue7SzQMywqRjg2fK7dcpc0QhFGpTR2plWfV4EZWR4= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -344,7 +268,6 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -414,7 +337,6 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a h1:Az/6CM/P5guGHNy7r6TkOCctv3lDmN3W1uhku7QMupk= github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a/go.mod h1:GZ/FY8Cqw3KHG6DwRVPUKbSPTAwyrU28xFi5cqZnLsc= @@ -450,8 +372,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -483,8 +405,8 @@ 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/healthcheck v1.2.6 h1:1sWhqr93GdkWy4+6U7JxBfcyZIE78MhIHTJZfPx7qqI= github.com/lightningnetwork/lnd/healthcheck v1.2.6/go.mod h1:Mu02um4CWY/zdTOvFje7WJgJcHyX2zq/FG3MhOAiGaQ= -github.com/lightningnetwork/lnd/kvdb v1.4.12 h1:Y0WY5Tbjyjn6eCYh068qkWur5oFtioJlfxc8w5SlJeQ= -github.com/lightningnetwork/lnd/kvdb v1.4.12/go.mod h1:hx9buNcxsZpZwh8m1sjTQwy2SOeBoWWOZ3RnOQkMsxI= +github.com/lightningnetwork/lnd/kvdb v1.4.15 h1:3eN6uGcubvGB5itPp1D0D4uEEkIMYht3w0LDnqLzAWI= +github.com/lightningnetwork/lnd/kvdb v1.4.15/go.mod h1:HW+bvwkxNaopkz3oIgBV6NEnV4jCEZCACFUcNg4xSjM= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= github.com/lightningnetwork/lnd/sqldb v1.0.7 h1:wQ4DdHY++uwxwth2CHL7s+duGqmMLaoIRBOQCa9HPTk= @@ -607,7 +529,6 @@ github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -641,58 +562,52 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY= -go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA= -go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg= -go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY= -go.etcd.io/etcd/client/v2 v2.305.7 h1:AELPkjNR3/igjbO7CjyF1fPuVPjrblliiKj+Y6xSGOU= -go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s= -go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4= -go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw= -go.etcd.io/etcd/pkg/v3 v3.5.7 h1:obOzeVwerFwZ9trMWapU/VjDcYUJb5OfgC1zqEGWO/0= -go.etcd.io/etcd/pkg/v3 v3.5.7/go.mod h1:kcOfWt3Ov9zgYdOiJ/o1Y9zFfLhQjylTgL4Lru8opRo= -go.etcd.io/etcd/raft/v3 v3.5.7 h1:aN79qxLmV3SvIq84aNTliYGmjwsW6NqJSnqmI1HLJKc= -go.etcd.io/etcd/raft/v3 v3.5.7/go.mod h1:TflkAb/8Uy6JFBxcRaH2Fr6Slm9mCPVdI2efzxY96yU= -go.etcd.io/etcd/server/v3 v3.5.7 h1:BTBD8IJUV7YFgsczZMHhMTS67XuA4KpRquL0MFOJGRk= -go.etcd.io/etcd/server/v3 v3.5.7/go.mod h1:gxBgT84issUVBRpZ3XkW1T55NjOb4vZZRI4wVvNhf4A= +go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v2 v2.305.12 h1:0m4ovXYo1CHaA/Mp3X/Fak5sRNIWf01wk/X1/G3sGKI= +go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= +go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.etcd.io/etcd/pkg/v3 v3.5.12 h1:OK2fZKI5hX/+BTK76gXSTyZMrbnARyX9S643GenNGb8= +go.etcd.io/etcd/pkg/v3 v3.5.12/go.mod h1:UVwg/QIMoJncyeb/YxvJBJCE/NEwtHWashqc8A1nj/M= +go.etcd.io/etcd/raft/v3 v3.5.12 h1:7r22RufdDsq2z3STjoR7Msz6fYH8tmbkdheGfwJNRmU= +go.etcd.io/etcd/raft/v3 v3.5.12/go.mod h1:ERQuZVe79PI6vcC3DlKBukDCLja/L7YMu29B74Iwj4U= +go.etcd.io/etcd/server/v3 v3.5.12 h1:EtMjsbfyfkwZuA2JlKOiBfuGkFCekv5H178qjXypbG8= +go.etcd.io/etcd/server/v3 v3.5.12/go.mod h1:axB0oCjMy+cemo5290/CutIjoxlfA6KVYKD1w0uue10= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -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/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0 h1:Wx7nFnvCaissIUZxPkBqDz2963Z+Cl+PkYbDKzTxDqQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0/go.mod h1:E5NNboN0UqSAki0Atn9kVwaN7I+l25gGxDqBueo/74E= -go.opentelemetry.io/otel v1.0.1 h1:4XKyXmfqJLOQ7feyV5DB6gsBFZ0ltB8vLtp6pj4JIcc= -go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1 h1:ofMbch7i29qIUf7VtF+r0HRF6ac0SBaPSziSsKp7wkk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1/go.mod h1:Kv8liBeVNFkkkbilbgWRpV+wWuu+H5xdOT6HAgd30iw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1 h1:CFMFNoz+CGprjFAFy+RJFrfEe4GBia3RRm2a4fREvCA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1/go.mod h1:xOvWoTOrQjxjW61xtOmD/WKGRYb/P4NzRo3bs65U6Rk= -go.opentelemetry.io/otel/sdk v1.0.1 h1:wXxFEWGo7XfXupPwVJvTBOaPBC9FEg0wB8hMNrKk+cA= -go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= -go.opentelemetry.io/otel/trace v1.0.1 h1:StTeIH6Q3G4r0Fiw34LTokUFESZgIDUr0qIJ7mKmAfw= -go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.9.0 h1:C0g6TWmQYvjKRnljRULLWUVJGy8Uvu0NEL/5frY2/t4= -go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= +go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= +go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0= +go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= +go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= +go.opentelemetry.io/otel/sdk v1.20.0 h1:5Jf6imeFZlZtKv9Qbo6qt2ZkmWtdWx/wzcCbNUlAWGM= +go.opentelemetry.io/otel/sdk v1.20.0/go.mod h1:rmkSx1cZCm/tn16iWDn1GQbLtsW/LvsdEEFzCSRM6V0= +go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= +go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -710,7 +625,6 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -721,39 +635,17 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 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= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -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-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -766,43 +658,25 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 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= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -811,8 +685,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -826,51 +698,28 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -883,7 +732,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -895,7 +743,6 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -904,50 +751,17 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/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-20191029190741-b9c20aec41a5/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= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -957,61 +771,17 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/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= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -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 v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= @@ -1019,23 +789,11 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= 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= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -1078,11 +836,8 @@ gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0= k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk= @@ -1125,9 +880,6 @@ 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= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= diff --git a/main.go b/main.go index 4ea05b3..1b23c80 100644 --- a/main.go +++ b/main.go @@ -44,7 +44,8 @@ func main() { // registered yet. We ignore any errors as that'll be handled later. _, _ = flags.NewParser(globalOpts, flags.IgnoreUnknown).Parse() - logger.Info("Version %s commit=%s, debuglevel=debug", Version(), Commit) + logger.Infof("Version %s commit=%s, debuglevel=%s", Version(), Commit, + globalOpts.DebugLevel) if globalOpts.DebugLevel != "" { level, ok := btclog.LevelFromString(globalOpts.DebugLevel) @@ -122,6 +123,7 @@ func registerCommands(parser *flags.Parser) error { newStoreSecretCommand(), newStoreConfigmapCommand(), newWaitReadyCommand(), + newMigrateDBCommand(), } for _, command := range commands { diff --git a/migratekvdb/bucket_path.go b/migratekvdb/bucket_path.go new file mode 100644 index 0000000..8415b96 --- /dev/null +++ b/migratekvdb/bucket_path.go @@ -0,0 +1,104 @@ +package migratekvdb + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +// BucketPath represents a path in the database with both string and raw +// representations. +type BucketPath struct { + // StringPath is the hex-encoded path with / delimiters for logging. + StringPath string + + // RawPath contains the original bucket names as raw bytes. + RawPath [][]byte +} + +// NewBucketPath creates a bucket path from raw bucket names. +func NewBucketPath(buckets [][]byte) BucketPath { + // Create hex encoded version for string representation. + stringParts := make([]string, len(buckets)) + for i, bucket := range buckets { + stringParts[i] = loggableKeyName(bucket) + } + + return BucketPath{ + StringPath: strings.Join(stringParts, "/"), + RawPath: buckets, + } +} + +// HasPath returns true if the BucketPath contains any path elements. +func (bp BucketPath) HasPath() bool { + return len(bp.RawPath) > 0 +} + +// AppendBucket creates a new BucketPath with an additional bucket. +func (bp BucketPath) AppendBucket(bucket []byte) BucketPath { + newRawPath := make([][]byte, len(bp.RawPath)+1) + copy(newRawPath, bp.RawPath) + newRawPath[len(bp.RawPath)] = bucket + + // Create new string path. + newStringPath := bp.StringPath + if newStringPath != "" { + newStringPath += "/" + } + newStringPath += loggableKeyName(bucket) + + return BucketPath{ + StringPath: newStringPath, + RawPath: newRawPath, + } +} + +// Equal compares two bucket paths for equality. +func (bp BucketPath) Equal(other BucketPath) bool { + if len(bp.RawPath) != len(other.RawPath) { + return false + } + + for i := range bp.RawPath { + if !bytes.Equal(bp.RawPath[i], other.RawPath[i]) { + return false + } + } + + return true +} + +// String implements the Stringer interface. +func (bp BucketPath) String() string { + return bp.StringPath +} + +// MarshalJSON implements the json.Marshaler interface by marshaling +// the raw byte arrays directly. +func (bp BucketPath) MarshalJSON() ([]byte, error) { + // Marshal the raw paths directly. + return json.Marshal(bp.RawPath) +} + +// UnmarshalJSON implements the json.Unmarshaler interface by unmarshaling +// directly into the raw byte arrays. +func (bp *BucketPath) UnmarshalJSON(data []byte) error { + // Unmarshal into raw paths. + var rawPath [][]byte + if err := json.Unmarshal(data, &rawPath); err != nil { + return fmt.Errorf("failed to unmarshal bucket path: %w", err) + } + + // Create the string path from the raw paths. + stringParts := make([]string, len(rawPath)) + for i, part := range rawPath { + stringParts[i] = loggableKeyName(part) + } + + // Set both representations. + bp.RawPath = rawPath + bp.StringPath = strings.Join(stringParts, "/") + return nil +} diff --git a/migratekvdb/errors.go b/migratekvdb/errors.go new file mode 100644 index 0000000..1587aa2 --- /dev/null +++ b/migratekvdb/errors.go @@ -0,0 +1,27 @@ +package migratekvdb + +import "errors" + +var ( + // errNoMetaBucket is returned when the migration metadata bucket is + // not found. + errNoMetaBucket = errors.New("migration metadata bucket not " + + "found") + + // errNoStateFound is returned when the migration state is not found. + errNoStateFound = errors.New("no migration state found") + + // errChunkSizeExceeded is returned when the chunk size limit is reached + // during migration, indicating that the migration should continue + // with a new transaction. It should close the reading and write + // transaction and continue where it stopped. + errChunkSizeExceeded = errors.New("chunk size exceeded") + + // errMigrationComplete is returned when the migration is already + // completed. + errMigrationComplete = errors.New("migration already completed") + + // errVerificationComplete is returned when the verification is already + // completed. + errVerificationComplete = errors.New("verification already completed") +) diff --git a/migratekvdb/helper.go b/migratekvdb/helper.go new file mode 100644 index 0000000..877fb07 --- /dev/null +++ b/migratekvdb/helper.go @@ -0,0 +1,28 @@ +package migratekvdb + +import ( + "encoding/hex" +) + +// loggableKeyName returns a string representation of a bucket key suitable for +// logging. +func loggableKeyName(key []byte) string { + // For known bucket names, return as string if printable ASCII. + if isPrintableASCII(key) { + return string(key) + } + + // Otherwise return hex encoding. + return "0x" + hex.EncodeToString(key) +} + +// hasSpecialChars returns true if any of the characters in the given string +// cannot be printed. +func isPrintableASCII(b []byte) bool { + for _, c := range b { + if c < 32 || c > 126 { + return false + } + } + return true +} diff --git a/migratekvdb/migration.go b/migratekvdb/migration.go new file mode 100644 index 0000000..3c8ff32 --- /dev/null +++ b/migratekvdb/migration.go @@ -0,0 +1,1076 @@ +package migratekvdb + +import ( + "bytes" + "context" + "errors" + "fmt" + "time" + + "github.com/btcsuite/btclog/v2" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/kvdb" + "go.etcd.io/bbolt" +) + +// Database bucket and key constants. +const ( + // migrationMetaBucket is the top-level bucket that stores the migration + // metadata. + migrationMetaBucket = "migration_meta" + + verificationMetaBucket = "verification_meta" + + // migrationStateKey is the key of the current state of the migration. + migrationStateKey = "migration_state" + + // verificationStateKey is the key of the verification state of the + // migration. + verificationStateKey = "verification_state" +) + +// Size constants. +const ( + // DefaultChunkSize is 20MB (leaving room for overhead). + DefaultChunkSize = 20 * 1024 * 1024 + + // MaxChunkSize is 500MB to prevent excessive memory usage. + MaxChunkSize = 500 * 1024 * 1024 +) + +// Config holds the configuration for the migrator. +type Config struct { + // ChunkSize is the number of items (key-value pairs or buckets) to + // process in a single transaction in bytes. + ChunkSize uint64 + + // Logger is the logger to use for logging. + Logger btclog.Logger + + // MetaDB is the database that stores the migration/verification state. + // We store it separately to not clutter the source or destination + // databases. + MetaDB *bbolt.DB + + // DBPrefixName is the prefix of the database name. + DBPrefixName string +} + +// validateConfig ensures the configuration is valid and sets defaults. +func validateConfig(cfg *Config) error { + if cfg == nil { + return fmt.Errorf("config cannot be nil") + } + + // Validate and set defaults for chunk size. + switch { + case cfg.ChunkSize == 0: + cfg.ChunkSize = DefaultChunkSize + + cfg.Logger.Infof("Chunk size set to default: %d bytes", + cfg.ChunkSize) + + case cfg.ChunkSize > MaxChunkSize: + return fmt.Errorf("chunk size too large: %d bytes, maximum "+ + "is %d bytes", cfg.ChunkSize, MaxChunkSize) + } + + // Ensure logger is set. + if cfg.Logger == nil { + cfg.Logger = btclog.Disabled + } + + return nil +} + +// Migrator handles the chunked migration of bolt databases. It supports: +// - Resumable migrations through state tracking +// - Chunk-based processing to handle large databases +// - Verification of migrated data (also resumable) +// - Progress logging +type Migrator struct { + // cfg is the configuration for the migrator. + cfg Config + + // migration is the migration state which is used to track and also + // resume the migration. + migration *MigrationState + + // verification is the verification state which is used to track and + // also resume the verification. + verification *VerificationState + + // Rate tracking fields to track the progress of the migration and + // verification. + startTimeMigration time.Time + startTimeVerification time.Time + lastLogTime time.Time + logInterval time.Duration + lastMigratedKeyCount int64 + lastVerifiedKeyCount int64 + migratedKeysSinceStart int64 + verifiedKeysSinceStart int64 +} + +// New creates a new Migrator with the given configuration. +func New(cfg Config) (*Migrator, error) { + // Validate and set defaults for the config. + if err := validateConfig(&cfg); err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + return &Migrator{ + cfg: cfg, + startTimeMigration: time.Now(), + lastLogTime: time.Now(), + logInterval: 5 * time.Second, + migration: newMigrationState(cfg.MetaDB), + verification: newVerificationState(cfg.MetaDB), + }, nil +} + +// checkBucketMigrated checks if a bucket have already been processed +// in our depth-first traversal based on the current path we're processing. +// In also returns the next bucket to fast-forward so that we can immediately +// resume the migration from the same point we left off. +// +// NOTE: This works because we can be sure that the nested bucket paths are +// lexicographically ordered and are always walked through in the same order. +func checkBucketMigrated(currentPath BucketPath, + checkPath BucketPath) (bool, []byte) { + // Find the minimum length of the paths. + minLen := min(len(currentPath.RawPath), len(checkPath.RawPath)) + + // Compare each bucket's raw bytes. + for i := range minLen { + // Compare the raw bytes of each bucket + comp := bytes.Compare( + checkPath.RawPath[i], currentPath.RawPath[i], + ) + if comp != 0 { + // If buckets differ, path is migrated if it's + // lexicographically smaller. + return comp < 0, nil + } + } + + // If we get here, paths were equal up to minLen. + // We want to return the next bucket after the current path + // so we can fast-forward to the same point we left off. + if len(currentPath.RawPath) > minLen { + // Return the next bucket after the current path at minLen + // level. + return false, currentPath.RawPath[minLen] + } + + // If current path is shorter or equal we are not done yet. + return false, nil +} + +// initMigrationState reads and initializes the migration state. +func (m *Migrator) initMigrationState(_ context.Context) error { + err := m.migration.read() + if err != nil && !errors.Is(err, errNoMetaBucket) && + !errors.Is(err, errNoStateFound) { + return fmt.Errorf("failed to read migration "+ + "state: %w", err) + } + + // In case the migration is alredy complete we return an error. + if m.migration.persistedState.Finished { + m.cfg.Logger.Infof("Migration already finished at time: %v", + m.migration.persistedState.FinishedTime.Format(time.RFC3339)) + + return errMigrationComplete + } + + // We do not have a bucket path yet set so this is a fresh migration. + // + // NOTE: There is a chance that the user deletes the meta db and starts + // a new migration. There is currently no check that the destination db + // is empty so it will just overwrite the existing data. However that + // is not a problem because the verification will still + if !m.migration.persistedState.CurrentBucketPath.HasPath() { + m.migration.persistedState = newPersistedState() + + m.cfg.Logger.Infof("No previous migration state found, " + + "starting fresh") + + return nil + } + + // otherwise we have a state to resume from. + // Otherwise we are ready to resume the migration. + m.migration.resuming = true + m.cfg.Logger.Infof("Resuming migration from "+ + "path %v, total processed keys: %d", + m.migration.persistedState.CurrentBucketPath, + m.migration.persistedState.ProcessedKeys) + + return nil +} + +// logProgress logs the progress of the migration. +func (m *Migrator) logMigrationProgress(path BucketPath) { + if time.Since(m.lastLogTime) < m.logInterval { + return + } + + totalKeys := m.migration.persistedState.ProcessedKeys + totalBuckets := m.migration.persistedState.ProcessedBuckets + elapsed := time.Since(m.startTimeMigration) + intervalElapsed := time.Since(m.lastLogTime) + + intervalKeys := m.migratedKeysSinceStart - m.lastMigratedKeyCount + currentRate := float64(intervalKeys) / intervalElapsed.Seconds() + + m.cfg.Logger.Infof("Migration progress at path %v: "+ + "processed %d keys total, %d buckets total "+ + "(current rate %.2f keys/sec), elapsed: %v", + path, totalKeys, totalBuckets, currentRate, + elapsed.Round(time.Second)) + + m.lastLogTime = time.Now() + m.lastMigratedKeyCount = m.migratedKeysSinceStart +} + +// logProgress logs the progress of the migration. +func (m *Migrator) logVerificationProgress(path BucketPath) { + if time.Since(m.lastLogTime) < m.logInterval { + return + } + + totalMigratedKeys := m.migration.persistedState.ProcessedKeys + + totalKeys := m.verification.persistedState.ProcessedKeys + totalBuckets := m.verification.persistedState.ProcessedBuckets + elapsed := time.Since(m.startTimeVerification) + intervalElapsed := time.Since(m.lastLogTime) + + intervalKeys := m.verifiedKeysSinceStart - m.lastVerifiedKeyCount + currentRate := float64(intervalKeys) / intervalElapsed.Seconds() + + // Calculate percentage of total keys from migration. + percentComplete := float64(totalKeys) / float64(totalMigratedKeys) * 100 + + m.cfg.Logger.Infof("Verification progress at path %v: "+ + "processed %d keys total, %d buckets total "+ + "(current rate %.2f keys/sec, %.1f%% complete), elapsed: %v", + path, totalKeys, totalBuckets, currentRate, + percentComplete, elapsed.Round(time.Second)) + + m.lastLogTime = time.Now() + m.lastVerifiedKeyCount = m.verifiedKeysSinceStart +} + +// Migrate performs the migration of the source database to the target database. +func (m *Migrator) Migrate(ctx context.Context, sourceDB, + targetDB kvdb.Backend) error { + + m.cfg.Logger.Infof("Migrating database with prefix `%s`", + m.cfg.DBPrefixName) + + // Initialize or resume migration state. + if err := m.initMigrationState(ctx); err != nil { + if err == errMigrationComplete { + m.cfg.Logger.Infof("Migration already completed") + + return nil + } + return err + } + + if !m.migration.resuming { + // Before proceeding with the migration we check that + // the destination db is empty. + var topLevelBuckets [][]byte + err := kvdb.View(targetDB, func(tx kvdb.RTx) error { + return tx.ForEachBucket(func(bucket []byte) error { + bucketCopy := make([]byte, len(bucket)) + copy(bucketCopy, bucket) + topLevelBuckets = append( + topLevelBuckets, bucketCopy, + ) + + return nil + }) + }, func() {}) + if err != nil { + return err + } + + if len(topLevelBuckets) > 0 { + return fmt.Errorf("Target database for prefix `%s` "+ + "is not empty, refusing to start a new "+ + "migration - delete the target database "+ + "manually first", m.cfg.DBPrefixName) + } + } + + var moreChunks bool + // Process chunks until complete. + for { + moreChunks = false + err := sourceDB.View(func(sourceTx kvdb.RTx) error { + return targetDB.Update(func(targetTx kvdb.RwTx) error { + // Process the chunk. + err := m.processChunk(ctx, sourceTx, targetTx) + + // We need to exclude this error because it is + // expected and we want to continue processing + // the next chunk. + if err != nil && err == errChunkSizeExceeded { + moreChunks = true + return nil + } + return err + }, func() {}) + }, func() {}) + + if err != nil { + return err + } + + if moreChunks { + // Save state and hash for the chunk. We already + // updated the migration where we checked for the + // chunk size limit. + err = m.migration.write() + if err != nil { + return fmt.Errorf("failed to save "+ + "migration state: %w", err) + } + + m.cfg.Logger.InfoS( + ctx, "Committed chunk successfully:", + "bucket", m.migration.persistedState.CurrentBucketPath, + "processed_keys", m.migration.persistedState.ProcessedKeys, + "processed_buckets", m.migration.persistedState.ProcessedBuckets, + "current_chunk_size(B)", m.migration.currentChunkBytes, + ) + + m.cfg.Logger.Debug("Migration progress saved, " + + "continuing with next chunk") + + m.migration.newChunk() + + continue + } + + // Migration is complete, finalize everything in this + // transaction. + m.cfg.Logger.Infof("Migration complete, processed in "+ + "total %d keys", + m.migration.persistedState.ProcessedKeys) + + // Update final state. This also makes sure the migration state + // is marked as finished. + m.migration.setFinalState() + + // Write the migration state. + err = m.migration.write() + if err != nil { + return fmt.Errorf("failed to write migration "+ + "state: %w", err) + } + + if m.migration.resuming { + return fmt.Errorf("no keys were migrated, " + + "the migration state path was not " + + "found") + } + + elapsed := time.Since(m.migration.persistedState.StartTime) + + m.cfg.Logger.Infof("Migration for db with prefix `%s` "+ + "completed in %v (since inital start)", + m.cfg.DBPrefixName, elapsed) + + return nil + } + +} + +// processChunk processes a single chunk of the migration by walking through +// the nested bucket structure and migrating the key-value pairs up to the +// specified chunk size. +func (m *Migrator) processChunk(ctx context.Context, + sourceTx kvdb.RTx, targetTx kvdb.RwTx) error { + + // We start the iteration by looping through the root buckets. + err := sourceTx.ForEachBucket(func(name []byte) error { + sourceRootBucket := sourceTx.ReadBucket(name) + if sourceRootBucket == nil { + return fmt.Errorf("source root bucket not found for "+ + "key %x", name) + } + + // We create the root bucket if it does not exist. + targetRootBucket, err := targetTx.CreateTopLevelBucket( + name, + ) + if err != nil { + return fmt.Errorf("failed to create target root "+ + "bucket: %w", err) + } + + if !m.migration.resuming { + m.cfg.Logger.Infof("Migrating root bucket: %v", + loggableKeyName(name)) + + // Increase the number of processed buckets. + m.migration.persistedState.ProcessedBuckets++ + } + + // Start with the root bucket name as the initial path. + initialPath := NewBucketPath([][]byte{name}) + err = m.migrateBucket( + ctx, sourceRootBucket, targetRootBucket, initialPath, + ) + + return err + }) + if err != nil { + return err + } + + return nil +} + +// migrateBucket migrates a bucket from the source database to the target +// database. +func (m *Migrator) migrateBucket(ctx context.Context, + sourceB kvdb.RBucket, targetB kvdb.RwBucket, + path BucketPath) error { + + // Helper variables to shorten the names. + currentPath := m.migration.persistedState.CurrentBucketPath + lastUnprocessedKey := m.migration.persistedState.LastUnprocessedKey + + var alreadyMigrated bool + var fastForwardBucket []byte + + // We skip the bucket if we already migrated it or fast-forward to the + // next bucket if we can. + switch m.migration.resuming { + case true: + alreadyMigrated, fastForwardBucket = checkBucketMigrated( + currentPath, path, + ) + + if alreadyMigrated { + m.cfg.Logger.Debugf("Skipping already migrated "+ + "bucket: %v", path) + + return nil + } + + case false: + // Only set sequence if source bucket has a non-zero sequence + // otherwise we keep for the sql case the sequence is a NULL + // value. + + sourceSeq := sourceB.Sequence() + if sourceSeq != 0 { + if err := targetB.SetSequence(sourceSeq); err != nil { + return fmt.Errorf("error copying sequence "+ + "number %d: %v", sourceSeq, err) + } + + m.cfg.Logger.Debugf("Successfully copied sequence "+ + "number for bucket %v: %d", path, sourceSeq) + } + } + + // We iterate over the source bucket. + cursor := sourceB.ReadCursor() + + // We default to the first key in the bucket. + k, v := cursor.First() + + // In case the fast-forward bucket is not nil we seek to it, but only + // if we are resuming. + if m.migration.resuming && fastForwardBucket != nil { + m.cfg.Logger.Debugf("Resuming migration, fast-forwarding to "+ + "bucket: %s", loggableKeyName(fastForwardBucket)) + + k, v = cursor.Seek(fastForwardBucket) + } + + isResumptionPoint := currentPath.Equal(path) + if isResumptionPoint { + m.cfg.Logger.Debugf("Found migration resumption point "+ + "at bucket %v with last key: %s", + path, loggableKeyName(lastUnprocessedKey)) + + m.migration.resuming = false + + // In case we have a last unprocessed key we seek to it. + if lastUnprocessedKey != nil { + k, v = cursor.Seek(lastUnprocessedKey) + if k == nil { + return fmt.Errorf("failed to seek to last "+ + "unprocessed key: %x", + lastUnprocessedKey) + } + } + } + + // Now we position the cursor to the best known state now we iterate + // over the bucket. + for ; k != nil; k, v = cursor.Next() { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + // We only log when not resuming to not spam with bucket paths + // which are already migrated. + if !m.migration.resuming { + m.logMigrationProgress(path) + } + + // We might slightly exceed the chunk size limit here but it + // also allows us to use very small chunk sizes otherwise we + // would need a minimum chunk size of the maximum key-value + // size. + if m.migration.currentChunkBytes >= m.cfg.ChunkSize { + m.cfg.Logger.DebugS(ctx, "Chunk size exceeded, "+ + "committing current chunk:", + "current_bucket", path, + "last_key", loggableKeyName(lastUnprocessedKey), + "chunk_size (Bytes)", + m.migration.currentChunkBytes, + ) + + // We reached the chunk size limit, so we update the + // current bucket path and return the error so the + // caller can handle the transaction commit/rollback. + m.migration.persistedState.CurrentBucketPath = path + m.migration.persistedState.LastUnprocessedKey = k + + return errChunkSizeExceeded + } + + // We to not count the size of the key-value pair if we are + // resuming. + if !m.migration.resuming { + size := uint64(len(k) + len(v)) + + m.migration.currentChunkBytes += size + } + + // In case the value is nil this is a nested bucket so we go + // into recursion here. + if v == nil { + sourceNestedBucket := sourceB.NestedReadBucket(k) + if sourceNestedBucket == nil { + return fmt.Errorf("source nested bucket not "+ + "found for key %x", k) + } + targetNestedBucket := targetB.NestedReadWriteBucket(k) + + // If the target bucket does not exist we create it. + // Because we do the migration in chunks we might have + // already created this bucket in a previous chunk. + if targetNestedBucket == nil { + // This is a safety check and should never + // happen. If we are resuming it means we + // already created this bucket in a previous + // chunk. + if m.migration.resuming { + return fmt.Errorf("target nested "+ + "bucket not found for key %x", + k) + } + + var err error + targetNestedBucket, err = targetB.CreateBucket(k) + if err != nil { + return fmt.Errorf("failed to create "+ + "bucket %x: %w", k, err) + } + + m.cfg.Logger.Debugf("Created new nested "+ + "bucket at path %v: %s", path, + loggableKeyName(k)) + } + + // We append the bucket to the path. + newPath := path.AppendBucket(k) + + if !m.migration.resuming { + // Increase the number of processed buckets. + m.migration.persistedState.ProcessedBuckets++ + } + + // Recursively migrate the nested bucket. + err := m.migrateBucket( + ctx, sourceNestedBucket, targetNestedBucket, + newPath, + ) + if err != nil { + return err + } + + // We continue processing the other keys in the same + // bucket. + continue + } + + // When resuming we skip the key-value pair. + if m.migration.resuming { + m.cfg.Logger.DebugS(ctx, "Resuming migration, "+ + "skipping key-value pair:", + "bucket", path, + "key", loggableKeyName(k), + "value", loggableKeyName(v)) + + continue + } + + m.cfg.Logger.DebugS(ctx, "Migrating key-value pair:", + "bucket", path, + "key", loggableKeyName(k), + "value", loggableKeyName(v)) + + // We copy the key-value pair to the target bucket but we are + // not committing the transaction here. This will accumulate. + if err := targetB.Put(k, v); err != nil { + return fmt.Errorf("failed to migrate key %x: %w", k, + err) + } + + // We process another key-value pair. + m.migration.persistedState.ProcessedKeys++ + + // Needed in case of a restart to have the correct rate. + m.migratedKeysSinceStart++ + } + + m.cfg.Logger.Debugf("Migration of bucket %v completed", path) + + return nil +} + +// VerifyMigration verifies the migration by comparing the source and target +// databases. +func (m *Migrator) VerifyMigration(ctx context.Context, + sourceDB, targetDB kvdb.Backend, force bool) error { + + m.startTimeVerification = time.Now() + + // Initialize the verification state so we resume in the right way. + err := m.initVerificationState(ctx, force) + if err != nil { + if err == errVerificationComplete { + m.cfg.Logger.Infof("Verification already completed for "+ + "db with prefix `%s`", m.cfg.DBPrefixName) + + return nil + } + return err + } + + err = m.compareDBs(ctx, sourceDB, targetDB) + if err != nil { + return err + } + + // This should never happen we should always have a fnished migration + // when executing the verification. + if !m.migration.persistedState.Finished { + return fmt.Errorf("migration not finished, " + + "please run the migration again") + } + + if m.migration.persistedState.ProcessedKeys != + m.verification.persistedState.ProcessedKeys { + + return fmt.Errorf("processed keys mismatch: %d != %d", + m.migration.persistedState.ProcessedKeys, + m.verification.persistedState.ProcessedKeys) + } + + if m.migration.persistedState.ProcessedBuckets != + m.verification.persistedState.ProcessedBuckets { + + return fmt.Errorf("processed buckets mismatch: %d != %d", + m.migration.persistedState.ProcessedBuckets, + m.verification.persistedState.ProcessedBuckets) + } + + if m.verification.resuming { + + return fmt.Errorf("no keys were verified, " + + "the verification state path was not " + + "found") + } + + m.logFinalStats() + + return nil +} + +// compareDBs compares the source and target databases value by value. +func (m *Migrator) compareDBs(ctx context.Context, srcDB, + targetDB kvdb.Backend) error { + + // It is curcial that the source database is read here first because + // for the sqlite case databases were combined so the targetDB might + // have more buckets (top-level-buckets) than the sourceDB. + return srcDB.View(func(srcTx kvdb.RTx) error { + return targetDB.View(func(targetTx kvdb.RTx) error { + // First compare root buckets. + err := srcTx.ForEachBucket(func(name []byte) error { + srcBucket := srcTx.ReadBucket(name) + + // In case we force a verification of a + // tombstoned db we need to skip the tombstone + // top-level bucket. + if bytes.Equal(name, channeldb.TombstoneKey) { + m.cfg.Logger.Info("Tombstone key " + + "root bucket found, skipping") + + return nil + } + + // Check if target has this root bucket + targetBucket := targetTx.ReadBucket(name) + if targetBucket == nil { + return fmt.Errorf("root bucket missing "+ + "in target: %x", name) + } + + // Walk and compare this bucket's hierarchy. + newPath := NewBucketPath([][]byte{name}) + + err := m.walkAndCompare(ctx, srcBucket, + targetBucket, newPath) + if err != nil { + return err + } + + // We checkpoint the state here in case we + // shutdown/crash in the middle of the + // verification. + if !m.verification.resuming { + m.verification.persistedState. + CurrentBucketPath = newPath + + m.verification.persistedState. + ProcessedBuckets++ + + m.verification.persistedState. + LastUnprocessedKey = nil + + // We persist the state here. + err := m.verification.write() + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return err + } + + m.verification.setFinalState() + err = m.verification.write() + if err != nil { + return err + } + + return nil + }, func() { + }) + }, func() {}) +} + +// walkAndCompare walks source and target buckets in parallel, +// comparing their contents. +func (m *Migrator) walkAndCompare(ctx context.Context, srcBucket, + targetBucket kvdb.RBucket, bucketPath BucketPath) error { + + currentPath := m.verification.persistedState.CurrentBucketPath + var nextBucket []byte + + switch m.verification.resuming { + case false: + m.cfg.Logger.Debugf("Walking and comparing bucket: %v", + bucketPath) + + // Check the sequence number of the source and target buckets. + if srcBucket.Sequence() != targetBucket.Sequence() { + return fmt.Errorf("sequence number mismatch at "+ + "path %v: source=%d target=%d", bucketPath, + srcBucket.Sequence(), targetBucket.Sequence()) + } + + case true: + var alreadyMigrated bool + alreadyMigrated, nextBucket = checkBucketMigrated( + currentPath, bucketPath, + ) + if alreadyMigrated { + return nil + } + } + + srcCursor := srcBucket.ReadCursor() + targetCursor := targetBucket.ReadCursor() + + sk, sv := srcCursor.First() + tk, tv := targetCursor.First() + + if m.verification.resuming && nextBucket != nil { + m.cfg.Logger.Debugf("Fast-forwarding verification to "+ + "bucket: %s", loggableKeyName(nextBucket)) + + sk, sv = srcCursor.Seek(nextBucket) + tk, tv = targetCursor.Seek(nextBucket) + } + + // This resume is different from the migration resume because we are + // only resuming in the verification at the beginning because read + // transaction do not need to be closed regularly. We only read the + // data here from both databases and compare. We still keep the resume + // in case we shutdown/crash in the middle of the verification. + if m.verification.resuming { + currentPath := m.verification.persistedState.CurrentBucketPath + isResumptionPoint := currentPath.Equal(bucketPath) + lastKey := m.verification.persistedState.LastUnprocessedKey + if isResumptionPoint { + m.cfg.Logger.Infof("Found verification "+ + "resumption point "+ + "at bucket %v with last unprocessed key: %v", + bucketPath, loggableKeyName(lastKey)) + + m.verification.resuming = false + + if lastKey != nil { + // Position the cursor to the last unprocessed + // key. + sk, sv = srcCursor.Seek(lastKey) + tk, tv = targetCursor.Seek(lastKey) + } + } + } + + for ; sk != nil; sk, sv = srcCursor.Next() { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + if !m.verification.resuming { + m.logVerificationProgress(bucketPath) + } + + // We only increment the chunk size if we are not resuming. + if !m.verification.resuming { + size := len(sk) + len(sv) + m.verification.currentChunkBytes += uint64(size) + } + + if m.verification.currentChunkBytes > m.cfg.ChunkSize { + m.cfg.Logger.Debugf("Chunk size exceeded, "+ + "checkpointing at bucket %v with last "+ + "unprocessed key: %s", bucketPath, + loggableKeyName(sk)) + + m.verification.currentChunkBytes = 0 + m.verification.persistedState.CurrentBucketPath = bucketPath + m.verification.persistedState.LastUnprocessedKey = sk + + err := m.verification.write() + if err != nil { + return err + } + + // We do not chuck but we still checkpoint to resume + // in case of of a shutdown/crash. + } + + // We also check it in case we are resuming. Check if target + // has fewer entries than source. + if tk == nil { + return fmt.Errorf("target bucket has fewer entries "+ + "at path %v, missing key %v", bucketPath, + loggableKeyName(sk)) + } + + // Compare keys of both databases. + if !bytes.Equal(sk, tk) { + return fmt.Errorf("key mismatch at path %v: "+ + "source=%v target=%v", bucketPath, + loggableKeyName(sk), loggableKeyName(tk)) + } + + // Handle nested buckets. + if sv == nil { + newPath := bucketPath.AppendBucket(sk) + + // Both must be buckets + if tv != nil { + return fmt.Errorf("type mismatch at path %v: "+ + "source is nested bucket, target is "+ + "single value", bucketPath) + } + + srcNestedBucket := srcBucket.NestedReadBucket(sk) + targetNestedBucket := targetBucket.NestedReadBucket(tk) + if srcNestedBucket == nil { + return fmt.Errorf("source nested bucket "+ + "access failed at path %v", bucketPath) + } + if targetNestedBucket == nil { + return fmt.Errorf("target nested bucket "+ + "access failed at path %v", bucketPath) + } + + // Recurse into nested buckets. + err := m.walkAndCompare( + ctx, srcNestedBucket, targetNestedBucket, + newPath) + if err != nil { + return err + } + + tk, tv = targetCursor.Next() + + // We only increase the counter if we are not resuming. + if !m.verification.resuming { + m.verification.persistedState.ProcessedBuckets++ + } + + continue + } + + if m.verification.resuming { + m.cfg.Logger.Debugf("Skipping key %x at path %v", sk, + bucketPath) + + continue + } + + // Handle key-value pairs. + if !bytes.Equal(sv, tv) { + return fmt.Errorf("value mismatch at path %v key %s, "+ + "source=%s target=%s", bucketPath, + loggableKeyName(sk), loggableKeyName(sv), + loggableKeyName(tv)) + } + + m.verification.persistedState.ProcessedKeys++ + m.verifiedKeysSinceStart++ + + // Move the target cursor to the next entry. + tk, tv = targetCursor.Next() + } + + // Check if target has more entries than source + if tk, _ := targetCursor.Next(); tk != nil { + return fmt.Errorf("target bucket has extra entries "+ + "at path %v, extra key %x", bucketPath, tk) + } + + return nil +} + +// initVerificationState reads and initializes the verification state. +func (m *Migrator) initVerificationState(ctx context.Context, force bool) error { + // Read the existing states. + err := m.migration.read() + if err != nil { + return fmt.Errorf("failed to read migration "+ + "state: %w", err) + } + err = m.verification.read() + if err != nil && !errors.Is(err, errNoMetaBucket) && + !errors.Is(err, errNoStateFound) { + + return fmt.Errorf("failed to read migration "+ + "state: %w", err) + } + + // We need to handle different cases when starting the verification + // process. + switch { + // In case we already finished the verification and we are not forcing + case m.verification.persistedState.Finished && !force: + m.cfg.Logger.Infof("Verification already finished at time: %v", + m.verification.persistedState.FinishedTime.Format( + time.RFC3339), + ) + + return errVerificationComplete + + // In case we already finished the verification and we are forcing + // a new one we need to start fresh. + case m.verification.persistedState.Finished && force: + m.verification.persistedState = newPersistedState() + + m.cfg.Logger.Infof("Forcing new verification, deleting " + + "verification state and starting fresh") + + return nil + + // In case we do not have a current path set we need to start fresh, + // we do not have a previous verification state which means we haven't + // commited a state yet. + case !m.verification.persistedState.CurrentBucketPath.HasPath(): + m.verification.persistedState = newPersistedState() + + m.cfg.Logger.Infof("No previous verification state found, " + + "starting fresh") + + return nil + + // Otherwise we have a previous verification state so we will resume. + default: + m.verification.resuming = true + m.cfg.Logger.InfoS(ctx, "Resuming verification", + "bucket", m.verification.persistedState.CurrentBucketPath, + "processed_keys", m.verification.persistedState.ProcessedKeys, + ) + + return nil + } +} + +// logFinalStats logs the complete statistics of the migration and +// verification process. +// +// TODO(ziggie): Make sure the right times are logged in case of a +// restart. +func (m *Migrator) logFinalStats() { + migrationTime := time.Since(m.migration.persistedState.StartTime) - + time.Since(m.verification.persistedState.StartTime) + + verificationTime := time.Since(m.verification.persistedState.StartTime) + totalTime := time.Since(m.migration.persistedState.StartTime) + + migrationRate := float64(m.migration.persistedState.ProcessedKeys) / + migrationTime.Seconds() + + verificationRate := float64(m.verification.persistedState.ProcessedKeys) / + verificationTime.Seconds() + + m.cfg.Logger.Infof("*** FINAL MIGRATION/VERIFICATION STATISTICS for "+ + "DB with prefix `%s` ***", m.cfg.DBPrefixName) + m.cfg.Logger.Infof("Total time: %v", totalTime.Round(time.Second)) + m.cfg.Logger.Infof(" ├── Migration time: %v (%.2f keys/sec)", + migrationTime.Round(time.Second), migrationRate) + m.cfg.Logger.Infof(" └── Verification time: %v (%.2f keys/sec)", + verificationTime.Round(time.Second), verificationRate) + m.cfg.Logger.Infof("Processed items:") + m.cfg.Logger.Infof(" ├── Keys: %d", + m.migration.persistedState.ProcessedKeys) + m.cfg.Logger.Infof(" └── Buckets: %d", + m.migration.persistedState.ProcessedBuckets) +} diff --git a/migratekvdb/migration_test.go b/migratekvdb/migration_test.go new file mode 100644 index 0000000..975ae53 --- /dev/null +++ b/migratekvdb/migration_test.go @@ -0,0 +1,192 @@ +package migratekvdb + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strconv" + "testing" + "time" + + "github.com/btcsuite/btclog/v2" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/stretchr/testify/require" + "go.etcd.io/bbolt" +) + +// TestMigration tests the migration of a test database including the +// verification of the migration. +func TestMigration(t *testing.T) { + // Create temporary directory for test databases. + tempDir, err := os.MkdirTemp("", "boltdb_migration_test") + require.NoError(t, err) + defer os.RemoveAll(tempDir) + + // Create source and target database paths. + sourceDBPath := filepath.Join(tempDir, "source.db") + targetDBPath := filepath.Join(tempDir, "target.db") + + // Create and populate source database. + sourceDB, err := createTestDatabase(sourceDBPath) + require.NoError(t, err) + defer sourceDB.Close() + + // Cleanup the test database files. + defer os.Remove(sourceDBPath) + defer os.Remove(targetDBPath) + + const ( + noFreelistSync = true + timeout = time.Minute + readonly = false + ) + + args := []interface{}{ + targetDBPath, noFreelistSync, timeout, readonly, + } + backend := kvdb.BoltBackendName + + // Create empty target database. + targetDB, err := kvdb.Create(backend, args...) + require.NoError(t, err) + defer targetDB.Close() + + consoleLogHandler := btclog.NewDefaultHandler( + os.Stdout, + ) + consoleLogger := btclog.NewSLogger(consoleLogHandler) + consoleLogger.SetLevel(btclog.LevelDebug) + + dbPath := filepath.Join(tempDir, "migration-meta.db") + metaDb, err := bbolt.Open(dbPath, 0600, nil) + require.NoError(t, err) + defer metaDb.Close() + + // Configure and run migration. + cfg := Config{ + // Chunksize in bytes. + ChunkSize: 2, + Logger: consoleLogger, + MetaDB: metaDb, + } + + migrator, err := New(cfg) + require.NoError(t, err) + + err = migrator.Migrate(context.Background(), sourceDB, targetDB) + require.NoError(t, err) + + err = migrator.VerifyMigration(context.Background(), sourceDB, targetDB, false) + require.NoError(t, err) + + // Verify migration by comparing values in the source and target + // databases as a sanity check that the previous hash verification has + // no errors. + err = verifyDatabases(t, sourceDB, targetDB) + require.NoError(t, err) +} + +// createTestDatabase creates a test database with some test data. +func createTestDatabase(dbPath string) (kvdb.Backend, error) { + + fmt.Println("creating test database") + + args := []interface{}{ + dbPath, true, time.Minute, false, + } + backend := kvdb.BoltBackendName + + db, err := kvdb.Create(backend, args...) + if err != nil { + return nil, err + } + + // Create test data structure. + err = db.Update(func(tx kvdb.RwTx) error { + fmt.Println("Creating test data structure...") + // Create root bucket "accounts" + accounts, err := tx.CreateTopLevelBucket([]byte("accounts")) + if err != nil { + fmt.Print("bucket creation failed.") + } + + // Create nested buckets and add some key-value pairs. + for i := 1; i <= 3; i++ { + userBucket, err := accounts.CreateBucketIfNotExists( + []byte("user" + strconv.Itoa(i)), + ) + if err != nil { + return err + } + + err = userBucket.Put([]byte("name"), []byte("Alice")) + if err != nil { + return err + } + + err = userBucket.Put( + []byte("email"), + []byte("alice@example.com"), + ) + if err != nil { + return err + } + + // Create a nested bucket for transactions. + txBucket, err := userBucket.CreateBucketIfNotExists( + []byte("transactions"), + ) + if err != nil { + return err + } + + err = txBucket.Put([]byte("tx1"), []byte("100 BTC")) + if err != nil { + return err + } + } + + return nil + }, func() {}) + + return db, err +} + +// verifyDatabases verifies the migration by comparing the values in the +// source and target databases. This checks every value to make sure we do not +// have an error in our resume logic. So it walks the entire database without +// any chunking, so we have a redundant check. +func verifyDatabases(t *testing.T, sourceDB, targetDB kvdb.Backend) error { + return sourceDB.View(func(sourceTx kvdb.RTx) error { + return targetDB.View(func(targetTx kvdb.RTx) error { + // Helper function to compare buckets recursively. + var compareBuckets func(source, target kvdb.RBucket) error + compareBuckets = func(source, target kvdb.RBucket) error { + // Compare all key-value pairs. + return source.ForEach(func(k, v []byte) error { + if v == nil { + // This is a nested bucket. + sourceBucket := source.NestedReadBucket(k) + targetBucket := target.NestedReadBucket(k) + require.NotNil(t, targetBucket) + return compareBuckets(sourceBucket, targetBucket) + } + + // This is a key-value pair. + targetValue := target.Get(k) + require.Equal(t, v, targetValue) + return nil + }) + } + + // Compare root buckets. + return sourceTx.ForEachBucket(func(name []byte) error { + sourceBucket := sourceTx.ReadBucket(name) + targetBucket := targetTx.ReadBucket(name) + require.NotNil(t, targetBucket) + return compareBuckets(sourceBucket, targetBucket) + }) + }, func() {}) + }, func() {}) +} diff --git a/migratekvdb/state.go b/migratekvdb/state.go new file mode 100644 index 0000000..7b05cd9 --- /dev/null +++ b/migratekvdb/state.go @@ -0,0 +1,207 @@ +package migratekvdb + +import ( + "encoding/json" + "fmt" + "time" + + "go.etcd.io/bbolt" +) + +// persistedState tracks the migration or verification progress for +// resumability of the process. +type persistedState struct { + // Currently processing bucket + CurrentBucketPath BucketPath `json:"current_bucket_path"` + + // Last key processed in current bucket + LastUnprocessedKey []byte `json:"last_unprocessed_key"` + + // Total processed keys + ProcessedKeys int64 `json:"processed_keys"` + + // Number of buckets processed + ProcessedBuckets int64 `json:"processed_buckets"` + + // Timestamp of the migration only set when the migration is started + // from the beginning. In case of a resume the start time will still + // be the initial time when the migration was started. + StartTime time.Time `json:"start_time"` + + // Finished is set to true in case the verification is finished. + Finished bool `json:"finished"` + + // FinishedTime is the time when the verification is finished. + FinishedTime time.Time `json:"finished_time"` +} + +// String returns a string representation of the persisted state. +func (s *persistedState) String() string { + return fmt.Sprintf("Path: %s, LastKey: %x, ProcessedKeys: %d, "+ + "ProcessedBuckets: %d, Time: %s", + s.CurrentBucketPath, + s.LastUnprocessedKey, + s.ProcessedKeys, + s.ProcessedBuckets, + s.StartTime.Format(time.RFC3339), + ) +} + +// newPersistedState creates a new persisted state with the required chunk size. +// The chunk size needs to be persisted because the verification depends on the +// same chunk size. +func newPersistedState() persistedState { + return persistedState{ + CurrentBucketPath: NewBucketPath([][]byte{}), + LastUnprocessedKey: nil, + StartTime: time.Now(), + } +} + +// MigrationState holds migration-specific state for resumability of the +// process. +type MigrationState struct { + persistedState + currentChunkBytes uint64 + resuming bool + + db *bbolt.DB +} + +// newMigrationState creates a new migration state. +func newMigrationState(db *bbolt.DB) *MigrationState { + return &MigrationState{ + db: db, + } +} + +// read reads the migration state from the database. +func (m *MigrationState) read() error { + return m.db.View(func(tx *bbolt.Tx) error { + metaBucket := tx.Bucket([]byte(migrationMetaBucket)) + if metaBucket == nil { + return errNoMetaBucket + } + + stateBytes := metaBucket.Get([]byte(migrationStateKey)) + if stateBytes == nil { + return errNoStateFound + } + + var state persistedState + if err := json.Unmarshal(stateBytes, &state); err != nil { + return fmt.Errorf("failed to unmarshal state: %w", err) + } + + m.persistedState = state + + return nil + }) +} + +func (m *MigrationState) write() error { + return m.db.Update(func(tx *bbolt.Tx) error { + metaBucket, err := tx.CreateBucketIfNotExists( + []byte(migrationMetaBucket), + ) + if err != nil { + return fmt.Errorf("failed to create meta "+ + "bucket: %w", err) + } + + encoded, err := json.Marshal(m.persistedState) + if err != nil { + return err + } + + return metaBucket.Put([]byte(migrationStateKey), encoded) + }) +} + +// setFinalState sets the final state of the migration. +func (m *MigrationState) setFinalState() { + m.persistedState.CurrentBucketPath = NewBucketPath([][]byte{ + []byte("complete"), + }) + m.persistedState.LastUnprocessedKey = []byte("complete") + m.currentChunkBytes = 0 + m.persistedState.Finished = true + m.persistedState.FinishedTime = time.Now() +} + +// newChunk creates a new chunk for the migration by resetting the +// non-persistent state. +func (m *MigrationState) newChunk() { + m.currentChunkBytes = 0 + m.resuming = true +} + +// VerificationState holds verification-specific state for resumability of the +// process. +type VerificationState struct { + persistedState persistedState + currentChunkBytes uint64 + resuming bool + + db *bbolt.DB +} + +// newVerificationState creates a new verification state. +func newVerificationState(db *bbolt.DB) *VerificationState { + return &VerificationState{ + db: db, + } +} + +// read reads the verification state from the database. +func (v *VerificationState) read() error { + return v.db.View(func(tx *bbolt.Tx) error { + metaBucket := tx.Bucket([]byte(verificationMetaBucket)) + if metaBucket == nil { + return errNoMetaBucket + } + + stateBytes := metaBucket.Get([]byte(verificationStateKey)) + if stateBytes == nil { + return errNoStateFound + } + + var state persistedState + if err := json.Unmarshal(stateBytes, &state); err != nil { + return fmt.Errorf("failed to unmarshal state: %w", err) + } + + v.persistedState = state + return nil + }) +} + +// write writes the verification state to the database. +func (v *VerificationState) write() error { + return v.db.Update(func(tx *bbolt.Tx) error { + metaBucket, err := tx.CreateBucketIfNotExists( + []byte(verificationMetaBucket), + ) + if err != nil { + return fmt.Errorf("failed to get meta bucket: %w", err) + } + + encoded, err := json.Marshal(v.persistedState) + if err != nil { + return err + } + + return metaBucket.Put([]byte(verificationStateKey), encoded) + }) +} + +// setFinalState sets the final state of the verification. +func (v *VerificationState) setFinalState() { + v.persistedState.CurrentBucketPath = NewBucketPath([][]byte{ + []byte("complete"), + }) + v.persistedState.LastUnprocessedKey = []byte("complete") + v.currentChunkBytes = 0 + v.persistedState.Finished = true + v.persistedState.FinishedTime = time.Now() +} From 2ec992e16c658a3097e02d641bb83c47ebee0bc5 Mon Sep 17 00:00:00 2001 From: ziggie Date: Mon, 7 Apr 2025 16:26:23 +0200 Subject: [PATCH 3/6] test: add db migration tests for postgres and sqlite --- cmd_migrate_db_postgres_test.go | 144 ++++++++++++++++++ cmd_migrate_db_sqlite_test.go | 60 ++++++++ cmd_migrate_db_test.go | 22 +++ migratekvdb/migration_test.go | 2 +- .../data/chain/bitcoin/regtest/macaroons.db | Bin 0 -> 131072 bytes testdata/data/chain/bitcoin/regtest/wallet.db | Bin 0 -> 1048576 bytes testdata/data/graph/regtest/channel.db | Bin 0 -> 1048576 bytes testdata/data/graph/regtest/sphinxreplay.db | Bin 0 -> 131072 bytes testdata/data/graph/regtest/wtclient.db | Bin 0 -> 131072 bytes .../watchtower/bitcoin/regtest/watchtower.db | Bin 0 -> 131072 bytes utils.go | 48 ++++++ 11 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 cmd_migrate_db_postgres_test.go create mode 100644 cmd_migrate_db_sqlite_test.go create mode 100644 cmd_migrate_db_test.go create mode 100644 testdata/data/chain/bitcoin/regtest/macaroons.db create mode 100644 testdata/data/chain/bitcoin/regtest/wallet.db create mode 100644 testdata/data/graph/regtest/channel.db create mode 100644 testdata/data/graph/regtest/sphinxreplay.db create mode 100644 testdata/data/graph/regtest/wtclient.db create mode 100644 testdata/data/watchtower/bitcoin/regtest/watchtower.db create mode 100644 utils.go diff --git a/cmd_migrate_db_postgres_test.go b/cmd_migrate_db_postgres_test.go new file mode 100644 index 0000000..d53e4e5 --- /dev/null +++ b/cmd_migrate_db_postgres_test.go @@ -0,0 +1,144 @@ +//go:build kvdb_postgres + +package main + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "fmt" + "testing" + + embeddedpostgres "github.com/fergusstrange/embedded-postgres" + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/kvdb/postgres" + "github.com/lightningnetwork/lnd/kvdb/sqlbase" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/stretchr/testify/require" +) + +const ( + testMaxConnections = 100 + testDsnTemplate = "postgres://postgres:postgres@127.0.0.1:9877/%s?sslmode=disable" +) + +// PostgresTestSetup holds the test configuration for Postgres. +type PostgresTestSetup struct { + tempDir string + dbName string + postgres *embeddedpostgres.EmbeddedPostgres + stopFunc func() error +} + +// setupEmbeddedPostgres initializes and starts the embedded postgres instance. +func setupEmbeddedPostgres(t *testing.T) *PostgresTestSetup { + sqlbase.Init(testMaxConnections) + + setup := &PostgresTestSetup{} + + // Initialize embedded postgres + setup.postgres = embeddedpostgres.NewDatabase( + embeddedpostgres.DefaultConfig(). + Port(9877). + StartParameters(map[string]string{ + "max_connections": fmt.Sprintf("%d", testMaxConnections), + }), + ) + + // Start postgres + err := setup.postgres.Start() + require.NoError(t, err, "failed to start postgres") + + setup.stopFunc = setup.postgres.Stop + + return setup +} + +// createTestDatabase creates a new random test database. +func (p *PostgresTestSetup) createTestDatabase(t *testing.T) { + // Generate random database name + randBytes := make([]byte, 8) + _, err := rand.Read(randBytes) + require.NoError(t, err) + p.dbName = "test_" + hex.EncodeToString(randBytes) + + // Create the database + dbConn, err := sql.Open("pgx", p.getDsn("postgres")) + require.NoError(t, err) + defer dbConn.Close() + + _, err = dbConn.ExecContext( + context.Background(), + "CREATE DATABASE "+p.dbName, + ) + require.NoError(t, err) +} + +// setupTestDir creates and sets up the temporary test directory. +func (p *PostgresTestSetup) setupTestDir(t *testing.T) { + p.tempDir = setupTestData(t) +} + +// getDsn returns the DSN for the specified database. +func (p *PostgresTestSetup) getDsn(dbName string) string { + return fmt.Sprintf(testDsnTemplate, dbName) +} + +// cleanup performs necessary cleanup. +func (p *PostgresTestSetup) cleanup() error { + if p.stopFunc != nil { + return p.stopFunc() + } + return nil +} + +// getDBConfigs returns the source and destination DB configs. +func (p *PostgresTestSetup) getDBConfigs() (*SourceDB, *DestDB) { + sourceDB := &SourceDB{ + Backend: lncfg.BoltBackend, + Bolt: &Bolt{ + DBTimeout: kvdb.DefaultDBTimeout, + DataDir: p.tempDir, + TowerDir: p.tempDir, + }, + } + + destDB := &DestDB{ + Backend: lncfg.PostgresBackend, + Postgres: &postgres.Config{ + Dsn: p.getDsn(p.dbName), + }, + } + + return sourceDB, destDB +} + +// TestMigrateDBPostgres tests the migration of a database from Bolt to +// Postgres. +func TestMigrateDBPostgres(t *testing.T) { + t.Parallel() + + // Setup postgres. + setup := setupEmbeddedPostgres(t) + defer func() { + require.NoError(t, setup.cleanup()) + }() + + // Setup test environment. + setup.setupTestDir(t) + setup.createTestDatabase(t) + + sourceDB, destDB := setup.getDBConfigs() + + // Create and run migration command. + cmd := &migrateDBCommand{ + Source: sourceDB, + Dest: destDB, + Network: "regtest", + ChunkSize: 1024, + } + + err := cmd.Execute(nil) + require.NoError(t, err, "failed to execute migration") +} diff --git a/cmd_migrate_db_sqlite_test.go b/cmd_migrate_db_sqlite_test.go new file mode 100644 index 0000000..26a6af6 --- /dev/null +++ b/cmd_migrate_db_sqlite_test.go @@ -0,0 +1,60 @@ +//go:build kvdb_sqlite + +package main + +import ( + "fmt" + "testing" + + "github.com/lightningnetwork/lnd/kvdb" + "github.com/lightningnetwork/lnd/kvdb/sqlite" + "github.com/lightningnetwork/lnd/lncfg" + "github.com/stretchr/testify/require" +) + +// TestMigrateDBSqlite tests the migration of a database from Bolt to SQLite. +func TestMigrateDBSqlite(t *testing.T) { + t.Parallel() + + // Create temp dir for test databases. + tempDir := setupTestData(t) + + fmt.Println("tempDir", tempDir) + + // Copy entire test directory structure. + err := copyTestDataDir("testdata/data", tempDir) + require.NoError(t, err, "failed to copy test data") + + // Set up source DB config (bolt). + sourceDB := &SourceDB{ + Backend: lncfg.BoltBackend, + Bolt: &Bolt{ + DBTimeout: kvdb.DefaultDBTimeout, + DataDir: tempDir, + TowerDir: tempDir, + }, + } + + // Set up destination DB config (sqlite). + destDB := &DestDB{ + Backend: lncfg.SqliteBackend, + Sqlite: &Sqlite{ + DataDir: tempDir, + TowerDir: tempDir, + Config: &sqlite.Config{}, + }, + } + + // Create and run migration command. + cmd := &migrateDBCommand{ + Source: sourceDB, + Dest: destDB, + Network: "regtest", + // Select a small chunk size to test the chunking. + ChunkSize: 1024, + } + + err = cmd.Execute(nil) + + require.NoError(t, err, "migration failed") +} diff --git a/cmd_migrate_db_test.go b/cmd_migrate_db_test.go new file mode 100644 index 0000000..bc8c3a0 --- /dev/null +++ b/cmd_migrate_db_test.go @@ -0,0 +1,22 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// testDataPath is the source directory containing test data. +const testDataPath = "testdata/data" + +// setupTestData creates a new temp directory and copies test data into it. +// It returns the path to the new temp directory. +func setupTestData(t *testing.T) string { + // Create unique temp dir for this test. + tempDir := t.TempDir() + err := copyTestDataDir(testDataPath, tempDir) + + require.NoError(t, err, "failed to copy test data") + + return tempDir +} diff --git a/migratekvdb/migration_test.go b/migratekvdb/migration_test.go index 975ae53..7e85589 100644 --- a/migratekvdb/migration_test.go +++ b/migratekvdb/migration_test.go @@ -105,7 +105,7 @@ func createTestDatabase(dbPath string) (kvdb.Backend, error) { // Create test data structure. err = db.Update(func(tx kvdb.RwTx) error { fmt.Println("Creating test data structure...") - // Create root bucket "accounts" + // Create root bucket "accounts". accounts, err := tx.CreateTopLevelBucket([]byte("accounts")) if err != nil { fmt.Print("bucket creation failed.") diff --git a/testdata/data/chain/bitcoin/regtest/macaroons.db b/testdata/data/chain/bitcoin/regtest/macaroons.db new file mode 100644 index 0000000000000000000000000000000000000000..b6b21468fe73f6be28c5f152bd77e2b1ea9d2666 GIT binary patch literal 131072 zcmeI)OGs2v7y#gDMtjgCV-GNjP(dPa;cNmIl|o2F5Me}F5W`@lQwi3lg#;=@R1+?O zB2aJ>vyc{{D_1RwHi5OViHa5yEi8yO8BzB(cO*hkT29}A|D1bX_uljE{}YRqN>$X( z7nAex@VTutkSH~a_0OnOMu6&w;dgr{(NY# zW`FYfv%xLz>uM%4g~P9Vb`Rffz4M~0r6YT7qG{*3h4!3_1%BJA2oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkLHf&{9fvn-YWUmwh^DF45LJRF4p0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0{^SPFT6~Y A(EtDd literal 0 HcmV?d00001 diff --git a/testdata/data/chain/bitcoin/regtest/wallet.db b/testdata/data/chain/bitcoin/regtest/wallet.db new file mode 100644 index 0000000000000000000000000000000000000000..e9c7feccd3849fe05f91969c3fc7b9b6a0cab877 GIT binary patch literal 1048576 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 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs p0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAn@lE_%8^oNYwxU literal 0 HcmV?d00001 diff --git a/testdata/data/graph/regtest/sphinxreplay.db b/testdata/data/graph/regtest/sphinxreplay.db new file mode 100644 index 0000000000000000000000000000000000000000..0c1b097b24e5ae5a8c4323dda638632f7cf7a063 GIT binary patch literal 131072 zcmeI*KWG$L7y$6u$r><;5R9e?305`|No64`#9+)}A=e0EmAx}&g(HMS0%;U%M2Ll0 z$ThNsr-hvsMppF7^9qTeLXIE-iv-R;g(5~y^bNBslQA6Tuu$&z;LE)4dvAxgztrE` zC`w1M{(Syd+w(Y?_oszMJPaLJQY zvAv5)ed_f>`+>pf3uk6F9eMO(Yv1g?<*uY&+fjU2J6GGYyQ5ls`Si~I?Q_+)N&VTo z!LBE}`oY)V?s_Syha(sS2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5Fqe93N+OZy;lGK?_n+J z|6d5PzW)E!P4(IS|D?(OKj)v_2e9gXfPDXd{=R_xy88jb|H2?ZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z0D*5qpz&k(YxVyxh9f5Z|Nn$oU;lq|_(VE}!z0`8|5fv^d>=qmxSM|A^ytOON_k@B zXm6!FSxGCO|Ba*7R~>8#TNE-hAuS{J^1|XOG`Icz*PBTWiPW z`&XkV9_xN^W%N~PsPkgyo2j0mfsHH2_AVy%sn-kb2L`7voSE5lwdO&sE!qX~j$jZVK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs X0RjXF5FkK+009C72oNCfe--!$XK3%S literal 0 HcmV?d00001 diff --git a/testdata/data/graph/regtest/wtclient.db b/testdata/data/graph/regtest/wtclient.db new file mode 100644 index 0000000000000000000000000000000000000000..5fa3ccb2c0049da75bfcc6434c81fe9c685437d1 GIT binary patch literal 131072 zcmeI*&uY{_7y$6NRV*U3ARJM7`tYn8~?qCH_^B7B=`W{1P=ut zpog9cdem=rGZ2=7-M#eC-@up2m(1);e%b3}ieg!=clGJ+*))>-vjs@u@^Cn zNcs1OdlAEkl%GXBj2K3w{81e4_VQ2>b*7ke93g literal 0 HcmV?d00001 diff --git a/testdata/data/watchtower/bitcoin/regtest/watchtower.db b/testdata/data/watchtower/bitcoin/regtest/watchtower.db new file mode 100644 index 0000000000000000000000000000000000000000..21cc969293bad857364707a7d28a8e4eca6eeec5 GIT binary patch literal 131072 zcmeI)F>cgA6aY|XlPC=$E4V-|uzjR7+yJiNWL=mg>uBwbI0-Empr^_SQbtMtPs_J)c`*~th8{=vF z>F3qW?QyBo6Cgl<009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0Rr0#?BvpN&;Q~5|8l>-yz$}v|J}Us>+9Rk zci-QCD|LDT1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZV0(dbzkgEgZ#Jd>0lbTN6>$`i@>RrP#7WM- z>2X4SXdGA;B?pN9RjZ%V~gwZ4dBn_0V@Hy{6M%eDuM$9NJTK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB=EV0=s#t+`fNYVTJ$!0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBoL*8+R_fJwEp8Kvhm;xOVQ;=CE^ zc|Fv#)AiY+8Di$iR<@_bwEZ9=z523Q^=-F|kw$<30RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N I0{^wZ9~WCulmGw# literal 0 HcmV?d00001 diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..643e8b0 --- /dev/null +++ b/utils.go @@ -0,0 +1,48 @@ +package main + +import ( + "io" + "os" + "path/filepath" +) + +// copyTestDataDir copies the entire test directory structure so that we do not +// alter any original files. +func copyTestDataDir(src, dst string) error { + return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + relPath, err := filepath.Rel(src, path) + if err != nil { + return err + } + + dstPath := filepath.Join(dst, relPath) + + if info.IsDir() { + return os.MkdirAll(dstPath, info.Mode()) + } + + return copyFile(path, dstPath) + }) +} + +// copyFile copies a file from src to dst. +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + return err +} From f17924742a6916c2d09276f15b56b44f90ba07b6 Mon Sep 17 00:00:00 2001 From: ziggie Date: Mon, 7 Apr 2025 16:26:28 +0200 Subject: [PATCH 4/6] docs: add migration doc --- docs/data-migration.md | 262 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 docs/data-migration.md diff --git a/docs/data-migration.md b/docs/data-migration.md new file mode 100644 index 0000000..b4ef0c6 --- /dev/null +++ b/docs/data-migration.md @@ -0,0 +1,262 @@ +# Data migration + +This document describes the process of migrating `LND`'s database state from one +type of database backend (for example the `bbolt` based database files `*.db` +such as the `channel.db` or `wallet.db` files) to another (for example the new +`postgres` or `sqlite` databases introduced in `lnd v0.14.0-beta`). + +**Note:** Currently only migrations from `bolt` to either `postgres` or +`sqlite` are supported. KV based databases will be phased out in the future. We + are planning to add additonal support to migrate all `etcd` databases to `sql` + databases. Moreover it is currently also not supported to move the database + from `sqlite` to `postgres` or vice versa, because this migration only takes + care of the key value data and there already exist the possiblity to have the + invoices in native sql which this tool does not support. + + +## Prepare the destination database + +To be able to execute the migration successfully you have to update to [LND +v0.19.0](https://github.com/lightningnetwork/lnd/releases/tag/untagged-6824d65b23dc77e94e22) +which makes sure that the intial pre-migration state of the kv-db is up to date. + +### Using postgres as the destination remote database + +Prepare a user and database as described in the [Postgres]( + https://github.com/lightningnetwork/lnd/blob/master/docs/postgres.md) +documentation. You'll need the Data Source Name (DSN) for both the data +migration and then the `lnd` configuration, so keep that string somewhere +(should be something with the format of `postgres://xx:yy@localhost:5432/zz`). + +No additional steps are required to prepare the Postgres database for the data +migration. The migration tool will create the database schema automatically, so +no DDL scripts need to be run in advance. But to speed up the migration process +you should take a look at the following [postgres server tuning guide](https://gist.github.com/djkazic/526fa3e032aea9578997f88b45b91fb9) + +### Using sqlite as the destination remote database + +No particular preparation is needed for `sqlite` compared to the `postgres` +case. Similar to the `bolt` case there will be separated db files created for +each individual `bolt` database. + + + +## Prepare the source database + +Assuming we want to migrate the database state from the pre-0.19.0 individual +`bbolt` based `*.db` files to a remote database, we first need to make sure the +source files are in the correct state. + +The following steps should be performed *before* running the data migration ( +some of them are marked as optional and can be neglected for small databases +e.g. 200MB +): +1. Stop `lnd` +2. Upgrade the `lnd` binary to the latest version (e.g. `v0.19.0-beta` or later) +3. (optional) Make sure to add config options like + `gc-canceled-invoices-on-startup=true` and `db.bolt.auto-compact=true` to + your `lnd.conf` to optimize the source database size by removing canceled + invoices and compacting it on startup. +4. Remove any data from the source database that you can. The fewer + entries are in the source database, the quicker the migration will complete. + For example failed payments (or their failed HTLC attempts) can be removed + with `lncli deletepayments --all`. This can make a huge difference for + routing nodes which rebalance a lot. Make sure you restart LND and compact + the db after the failed payments were deleted so it has an effect on the size + of the db. +5. (optional) Also make sure to migrate the revocation log for all channels + active prior to `lnd@0.15.0` by activating the config setting `--db.prune-revocation`. + This version introduced an optimized revocation log storage system that + reduces the storage footprint. All channels will be automatically migrated + to this new format when the setting is enabled. +6. Start `lnd` normally, using the flags mentioned above but not yet changing + any database backend related configuration options. Check the log that the + database schema was migrated successfully, for example: `Checking for + schema update: latest_version=XX, db_version=XX`. This relates to the + kv-schema NOT any SQL schema, this makes sure all inital migration of the + old database were performed. This migration tool is only allowed for DBs + which have all the latest db migrations applied up to LND 19. This makes sure + that all LND nodes which want to migrate to the SQL world have the latest db + modifications in place before migrating to a different DB type. If that is + not the case the migration will be refused. +7. Stop `lnd` again and make sure it isn't started again by accident during the + data migration (e.g. disable any `systemd` or other scripts that start/stop + `lnd`). + +## Run the migration + +Depending on the destination database type, run the migration with a command +similar to one of the following examples: + +**Example: Migrate from `bbolt` to `sqlite`:** + +```shell +lndinit --debuglevel info migrate-db \ +--source.bolt.data-dir /home/myuser/.lnd/data \ +--dest.backend sqlite \ +--dest.sqlite.data-dir /home/myuser/.lnd/data --network mainnet +``` +If you were running a watchtower server, and it had a different directory set +compared to the default `LND` directory make sure you also add the tower dir +setting. It has to be the directory to the `watchtower` dir, excluding the +`watchtower` name e.g.: +`--source.bolt.tower-dir /home/myuser/towerdir`. + + +**Example: Migrate from `bbolt` to `postgres`:** + +```shell +lndinit --debuglevel info migrate-db \ + --source.bolt.data-dir /home/myuser/.lnd/data \ + --dest.backend postgres \ + --dest.postgres.dsn=postgres://postgres:postgres@localhost:5432/postgres +``` + +Also set the watchtower directory in case you used a different path, see above. + +This migration tool depends on the directory structure of +the LND software. This means make sure you link the correct folder because the +`bolt` database has several database files in serveral subfolders. This +migration tool will make sure that all the required databases are migrated. It +will not require a `wtclient.db` or a `watchtower.db`. + +The migration is resumable and happens in chunks of default 20MB to make it +compatible with most of the systems including low power devices like +raspberry pis. However if you have better setup with way more RAM feel free to +increase the `--chunk-size` to something like +200MB which should speed up the migration. You can also change the chunk size +during the migration as well. If you want to start the migration from the +beginning (can only be done if the migration did still not succeed) use the +flag `--force-new-migration` which can be used in combination with a +new `chunksize` limit. + +In case you have successfully migrated several nodes and are not sure anymore +which source db corresponds to which destination db there is a flag called +`force-verify-db` which only works if both dbs are marked as successfully +migrated. It will verify the contents of the db. + + +## After the migration was successful + +Make sure the whole migration process succeeds before starting the new node with +the new underlying database. As mentioned above there are several database files +at play here so all of them have to succeed to garantee a successful migration. + +If the migration succeeded successfully and you see the following log entry of +the migration tool you are good to go to start-up your lnd node with the new +database. + +```shell +[INF]: LNDINIT !!!Migration of all mandatory db parts completed successfully!!! +``` + +The mandatory dbs are: +* `channel.db` +* `macaroons.db` +* `sphinxreplay.db` +* `wallet.db` + +The optional dbs are: + +* `wtclient.db` +* `watchtower.db` +* `neutrino.db` + +### LND config setting for `sqlite` backend + +```shell +[db] +db.backend=sqlite +``` + +There are several other sqlite setttings you can tweak to make it fit your +needs, take a look at the [lnd-sample-config](https://github.com/lightningnetwork/lnd/blob/b6d8ecc7479f7517368814c398b0fbe0e8c52fed/sample-lnd.conf). + +### LND config setting for `postgres` backend + +```shell +[db] +db.backend=postgres + +[postgres] + +db.postgres.dsn=postgres://xx:yy@localhost:5432/zz +``` + +Use the same connection string you used for the migration. Also take a look at +the postgres knobs in the [lnd-sample-config](https://github.com/lightningnetwork/lnd/blob/b6d8ecc7479f7517368814c398b0fbe0e8c52fed/sample-lnd.conf). + + +This is the output of the migration cmd help settings: + +```shell +lndinit migrate-db -h +Usage: + lndinit [OPTIONS] migrate-db [migrate-db-OPTIONS] + +Migrate the full database state of lnd from a source (for example the +set of bolt database files such as channel.db and wallet.db) database +to a SQL destination database. + +IMPORTANT: Please read the data migration guide located in the file +docs/data-migration.md of the main lnd repository before using this +command! + +NOTE: The migration can take a long time depending on the amount of data +that needs to be written! The migration happens in chunks therefore it +can be resumed in case of an interruption. The migration also includes +a verification to assure that the migration is consistent. +As long as NEITHER the source nor destination database has been started/ +run with lnd, the migration can be repeated/resumed in case of an error +since the data will just be overwritten again in the destination. + +Once a database was successfully and completely migrated from the source +to the destination, the source will be marked with a 'tombstone' tag +while the destination will get an 'already migrated' tag. +A database with a tombstone cannot be started with lnd anymore to +prevent from an old state being used by accident. +To prevent overwriting a destination database by accident, the same +database/namespace pair cannot be used as the target of a data migration +twice, which is checked through the 'already migrated' tag. + +Application Options: + -e, --error-on-existing Exit with code EXIT_CODE_TARGET_EXISTS (128) instead of 0 if the result of an action is already present + -d, --debuglevel= Set the log level (Off, Critical, Error, Warn, Info, Debug, Trace) + +Help Options: + -h, --help Show this help message + +[migrate-db command options] + -n, --network= Network of the db files to migrate (used to navigate into the right directory) (default: mainnet) + --pprof-port= Enable pprof profiling on the specified port + --force-new-migration Force a new migration from the beginning of the source DB so the resume state will be discarded + --force-verify-db Force a verification verifies two already marked (tombstoned and already migrated) dbs to make sure that the source db equals the + content of the destination db + --chunk-size= Chunk size for the migration in bytes + + source: + --source.backend=[bolt] The source database backend. (default: bolt) + + bolt: + --source.bolt.dbtimeout= Specify the timeout value used when opening the database. (default: 1m0s) + --source.bolt.data-dir= Lnd data dir where bolt dbs are located. + --source.bolt.tower-dir= Lnd watchtower dir where bolt dbs for the watchtower server are located. + + dest: + --dest.backend=[postgres|sqlite] The destination database backend. (default: postgres) + + postgres: + --dest.postgres.dsn= Database connection string. + --dest.postgres.timeout= Database connection timeout. Set to zero to disable. + --dest.postgres.maxconnections= The maximum number of open connections to the database. Set to zero for unlimited. + + sqlite: + --dest.sqlite.data-dir= Lnd data dir where sqlite dbs are located. + --dest.sqlite.tower-dir= Lnd watchtower dir where sqlite dbs for the watchtower server are located. + + sqlite-config: + --dest.sqlite.sqlite-config.timeout= The time after which a database query should be timed out. + --dest.sqlite.sqlite-config.busytimeout= The maximum amount of time to wait for a database connection to become available for a query. + --dest.sqlite.sqlite-config.maxconnections= The maximum number of open connections to the database. Set to zero for unlimited. + --dest.sqlite.sqlite-config.pragmaoptions= A list of pragma options to set on a database connection. For example, 'auto_vacuum=incremental'. Note that the flag must be specified multiple times if multiple options are to be set. +``` \ No newline at end of file From d47f07b44587866790b12088b3053f620004b0a4 Mon Sep 17 00:00:00 2001 From: ziggie Date: Thu, 10 Apr 2025 16:13:56 +0200 Subject: [PATCH 5/6] gitignore: add testdata --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ae19b4d..ae088e5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ _obj _test +# Test data directories +testdata/ + # Architecture specific extensions/prefixes *.[568vq] [568vq].out From d18f5d373892671ec8615af737bc835f620e5d6a Mon Sep 17 00:00:00 2001 From: ziggie Date: Sun, 13 Apr 2025 10:47:45 +0200 Subject: [PATCH 6/6] make: add tags to release build --- Makefile | 2 +- release.sh | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1173189..3471d46 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ release-install: release: @$(call print, "Creating release of lndinit.") - ./release.sh build-release "$(VERSION_TAG)" "$(BUILD_SYSTEM)" "$(RELEASE_LDFLAGS)" + ./release.sh build-release "$(VERSION_TAG)" "$(BUILD_SYSTEM)" "$(RELEASE_LDFLAGS)" "$(RELEASE_TAGS)" docker-tools: @$(call print, "Building tools docker image.") diff --git a/release.sh b/release.sh index 996b666..6ca279a 100755 --- a/release.sh +++ b/release.sh @@ -28,6 +28,7 @@ function build_release() { local tag=$1 local sys=$2 local ldflags=$3 + local tags=$4 green " - Packaging vendor" go mod vendor @@ -64,7 +65,7 @@ function build_release() { pushd "${dir}" green " - Building: ${os} ${arch} ${arm}" - env CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" ${PKG} + env CGO_ENABLED=0 GOOS=$os GOARCH=$arch GOARM=$arm go build -v -trimpath -ldflags="${ldflags}" -tags="${tags}" ${PKG} popd if [[ $os == "windows" ]]; then